pkg/ndbconfig/config_generators.go (179 lines of code) (raw):
// Copyright (c) 2020, 2023, Oracle and/or its affiliates.
//
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
package ndbconfig
import (
"bytes"
"net"
"text/template"
klog "k8s.io/klog/v2"
v1 "github.com/mysql/ndb-operator/pkg/apis/ndbcontroller/v1"
"github.com/mysql/ndb-operator/pkg/constants"
)
// Operator sets NoOfReplicas and ServerPort in the default ndbd section
const numOfOperatorSetConfigs = 2
// MySQL Cluster config template
var mgmtConfigTmpl = `{{- /* Template to generate management config ini */ -}}
# Auto generated config.ini - DO NOT EDIT
[system]
ConfigGenerationNumber={{GetConfigVersion}}
Name={{.Name}}
{{if .Spec.ManagementNode}}{{if .Spec.ManagementNode.Config}}[ndb_mgmd default]{{end}}
{{- range $configKey, $configValue := .Spec.ManagementNode.Config }}
{{$configKey}}={{$configValue}}
{{- end}}{{end}}
[ndbd default]
{{/* update numOfOperatorSetConfigs if a new parameter is added here */ -}}
NoOfReplicas={{.Spec.RedundancyLevel}}
# Use a fixed ServerPort for all data nodes
ServerPort=1186
{{- if .Spec.TDESecretName }}
EncryptedFileSystem=1
{{ end }}
{{- range $configKey, $configValue := .Spec.DataNode.Config }}
{{$configKey}}={{$configValue}}
{{- end}}
[tcp default]
AllowUnresolvedHostnames=1
{{$hostnameSuffix := GetHostnameSuffix -}}
{{range $idx, $nodeId := GetNodeIds NdbNodeTypeMgmd -}}
[ndb_mgmd]
NodeId={{$nodeId}}
Hostname={{$.Name}}-{{NdbNodeTypeMgmd}}-{{$idx}}.{{$.GetServiceName NdbNodeTypeMgmd}}.{{$hostnameSuffix}}
DataDir={{GetDataDir}}
{{end -}}
{{range $idx, $nodeId := GetNodeIds NdbNodeTypeNdbmtd -}}
[ndbd]
NodeId={{$nodeId}}
Hostname={{$.Name}}-{{NdbNodeTypeNdbmtd}}-{{$idx}}.{{$.GetServiceName NdbNodeTypeNdbmtd}}.{{$hostnameSuffix}}
DataDir={{GetDataDir}}
{{if IsNewDataNode $nodeId -}}
NodeGroup=65536
{{end}}
{{end -}}
# Dedicated API section to be used by NDB Operator
[api]
NodeId={{NdbOperatorDedicatedAPINodeId}}
Dedicated=1
# MySQLD sections to be used exclusively by MySQL Servers
{{range $nodeId, $podIdx := GetMySQLServerNodeIds -}}
[mysqld]
NodeId={{$nodeId}}
Hostname={{$.Name}}-{{NdbNodeTypeMySQLD}}-{{$podIdx}}.{{$.GetServiceName NdbNodeTypeMySQLD}}.{{$hostnameSuffix}}
{{end -}}
# API sections to be used by generic NDBAPI applications
{{range $nodeId := GetNodeIds NdbNodeTypeAPI -}}
[api]
NodeId={{$nodeId}}
{{end -}}
`
// GetConfigString generates a new configuration for the
// MySQL Cluster from the given ndb resources Spec.
//
// It is important to note that GetConfigString uses the Spec in its
// actual and consistent state and does not rely on any Status field.
func GetConfigString(ndb *v1.NdbCluster, oldConfigSummary *ConfigSummary) (string, error) {
var (
// Variables that keep track of the first free mgmd/data node id and api nodeId
ndbdMgmdStartNodeId = 1
apiStartNodeId = constants.NdbNodeTypeAPIStartNodeId
// newDataNodeStartId tracks the starting nodeId of the new
// data nodes during an online add data node scenario
newDataNodeStartId = 0
)
if oldConfigSummary != nil && oldConfigSummary.NumOfDataNodes < ndb.Spec.DataNode.NodeCount {
// Data Nodes are being added to the configuration.
newDataNodeStartId = int(ndb.GetManagementNodeCount() + oldConfigSummary.NumOfDataNodes + 1)
}
tmpl := template.New("config.ini")
tmpl.Funcs(template.FuncMap{
// GetNodeIds returns an array of node ids for the given node type
"GetNodeIds": func(nodeType string) []int {
var startNodeId *int
var numberOfNodes int32
switch nodeType {
case constants.NdbNodeTypeMgmd:
startNodeId = &ndbdMgmdStartNodeId
numberOfNodes = ndb.GetManagementNodeCount()
case constants.NdbNodeTypeNdbmtd:
startNodeId = &ndbdMgmdStartNodeId
numberOfNodes = ndb.Spec.DataNode.NodeCount
case constants.NdbNodeTypeMySQLD:
startNodeId = &apiStartNodeId
numberOfNodes = GetNumOfSectionsRequiredForMySQLServers(ndb)
case constants.NdbNodeTypeAPI:
startNodeId = &apiStartNodeId
numberOfNodes = ndb.Spec.FreeAPISlots
default:
panic("Unrecognised node type")
}
// generate nodeIds based on start node id and number of nodes
nodeIds := make([]int, numberOfNodes)
for i := range nodeIds {
nodeIds[i] = *startNodeId
*startNodeId++
}
return nodeIds
},
"GetMySQLServerNodeIds": func() map[int]int {
startNodeId := &apiStartNodeId
numberOfNodeIds := GetNumOfSectionsRequiredForMySQLServers(ndb)
nodeIdToPodIdx := make(map[int]int)
ndbConnectionPoolSize := ndb.GetMySQLServerConnectionPoolSize()
podIdx := 0
for i := 0; int32(i) < numberOfNodeIds; i = i + int(ndbConnectionPoolSize) {
for j := 0; j < int(ndbConnectionPoolSize); j++ {
nodeIdToPodIdx[*startNodeId] = podIdx
*startNodeId++
}
podIdx++
}
return nodeIdToPodIdx
},
"GetDataDir": func() string { return constants.DataDir + "/data" },
"IsNewDataNode": func(nodeId int) bool {
return newDataNodeStartId != 0 && nodeId >= newDataNodeStartId
},
"GetConfigVersion": func() int32 {
if oldConfigSummary == nil {
// First version of the management config based on newly added NdbCluster spec.
return 1
} else {
// Bump up the config version for every change.
return oldConfigSummary.MySQLClusterConfigVersion + 1
}
},
"GetHostnameSuffix": func() string {
// If the K8s Cluster domain can be found, generate the hostname suffix of form :
// '<namespace>.svc.<k8s-cluster-domain>' or else, simply use the namespace as the suffix.
// Deduce K8s cluster domain suffix by looking up the kubernetes server's CNAME.
if k8sCname, err := net.LookupCNAME("kubernetes.default.svc"); err != nil {
klog.Warning("K8s Cluster domain lookup failed :", err.Error())
klog.Warning("Using partial subdomain as Hostnames in management configuration")
return ndb.Namespace
} else {
// Found the FQDN of form "kubernetes.default.svc.<k8s-cluster-domain>."
// Extract the required parts and append them to the suffix
return ndb.Namespace + k8sCname[len("kubernetes.default"):len(k8sCname)-1]
}
},
"NdbNodeTypeMgmd": func() string { return constants.NdbNodeTypeMgmd },
"NdbNodeTypeNdbmtd": func() string { return constants.NdbNodeTypeNdbmtd },
"NdbNodeTypeMySQLD": func() string { return constants.NdbNodeTypeMySQLD },
"NdbNodeTypeAPI": func() string { return constants.NdbNodeTypeAPI },
"NdbOperatorDedicatedAPINodeId": func() int { return constants.NdbOperatorDedicatedAPINodeId },
})
if _, err := tmpl.Parse(mgmtConfigTmpl); err != nil {
// panic to discover any parsing errors during development
panic("Failed to parse mgmt config template : " + err.Error())
}
var configIni bytes.Buffer
if err := tmpl.Execute(&configIni, ndb); err != nil {
return "", err
}
return configIni.String(), nil
}
// MySQL Server config (my.cnf) template
var myCnfTemplate = `{{- /* Template to generate my.cnf config ini */ -}}
# Auto generated config.ini - DO NOT EDIT
# ConfigVersion={{GetConfigVersion}}
{{.GetMySQLCnf}}
`
// GetMySQLConfigString returns the MySQL Server config(my.cnf)
// to be used by the MySQL Server StatefulSet.
func GetMySQLConfigString(nc *v1.NdbCluster, oldConfigSummary *ConfigSummary) (string, error) {
if nc.GetMySQLCnf() == "" {
return "", nil
}
tmpl := template.New("my.cnf")
tmpl.Funcs(template.FuncMap{
"GetConfigVersion": func() int32 {
if oldConfigSummary == nil {
// First version of the my.cnf config based on newly added NdbCluster spec.
return 1
} else {
// Bump up the config version for every change.
return oldConfigSummary.MySQLServerConfigVersion + 1
}
},
})
if _, err := tmpl.Parse(myCnfTemplate); err != nil {
// panic to discover any parsing errors during development
panic("Failed to parse my.cnf config template")
}
var myCnf bytes.Buffer
if err := tmpl.Execute(&myCnf, nc); err != nil {
return "", err
}
return myCnf.String(), nil
}