pkg/resources/statefulset/ndb_statefulset_interface.go (187 lines of code) (raw):
// Copyright (c) 2020, 2024, Oracle and/or its affiliates.
//
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
package statefulset
import (
"fmt"
"os"
"strconv"
"strings"
v1 "github.com/mysql/ndb-operator/pkg/apis/ndbcontroller/v1"
"github.com/mysql/ndb-operator/pkg/constants"
"github.com/mysql/ndb-operator/pkg/ndbconfig"
"github.com/mysql/ndb-operator/pkg/resources"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
klog "k8s.io/klog/v2"
)
// NdbStatefulSetInterface defines the methods to be implemented by the StatefulSets of the MySQL Cluster nodes
type NdbStatefulSetInterface interface {
GetTypeName() constants.NdbNodeType
GetName(nc *v1.NdbCluster) string
NewGoverningService(nc *v1.NdbCluster) *corev1.Service
NewStatefulSet(cs *ndbconfig.ConfigSummary, nc *v1.NdbCluster) (*appsv1.StatefulSet, error)
}
type baseStatefulSet struct {
nodeType constants.NdbNodeType
}
// getContainerName returns the container name
func (bss *baseStatefulSet) getContainerName(initContainer bool) string {
if initContainer {
return bss.GetTypeName() + "-init-container"
} else {
return bss.GetTypeName() + "-container"
}
}
// getStatefulSetLabels returns the labels of the StatefulSet
func (bss *baseStatefulSet) getStatefulSetLabels(nc *v1.NdbCluster) map[string]string {
return nc.GetCompleteLabels(map[string]string{
constants.ClusterResourceTypeLabel: bss.nodeType + "-statefulset",
})
}
// getPodLabels generates the labels of the pods controlled by the StatefulSets
func (bss *baseStatefulSet) getPodLabels(nc *v1.NdbCluster) map[string]string {
return nc.GetCompleteLabels(map[string]string{
constants.ClusterNodeTypeLabel: bss.nodeType,
})
}
// getDataDirVolumeName returns the data dir volume name
func (bss *baseStatefulSet) getDataDirVolumeName() string {
return bss.nodeType + "-data-vol"
}
// getEmptyDirPodVolumes returns an empty directory pod volume
func (bss *baseStatefulSet) getEmptyDirPodVolume(name string) *corev1.Volume {
return &corev1.Volume{
Name: name,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}
}
// getHelperScriptVolumeMount returns the VolumeMount for the helper scripts
func (bss *baseStatefulSet) getHelperScriptVolumeMount() corev1.VolumeMount {
return corev1.VolumeMount{
// Volume mount for helper scripts
Name: helperScriptsVolName,
MountPath: helperScriptsMountPath,
}
}
// createContainer creates a new container for the StatefulSet with default values
func (bss *baseStatefulSet) createContainer(
nc *v1.NdbCluster, containerName string, commandAndArgs []string,
volumeMounts []corev1.VolumeMount, portNumbers []int32) corev1.Container {
// Expose the ports passed via portNumbers
var ports []corev1.ContainerPort
for _, portNumber := range portNumbers {
ports = append(ports, corev1.ContainerPort{
ContainerPort: portNumber,
})
}
klog.Infof("Creating container %q from image %s", containerName, nc.Spec.Image)
return corev1.Container{
Name: containerName,
// Use the image provided in spec
Image: nc.Spec.Image,
ImagePullPolicy: nc.Spec.ImagePullPolicy,
Ports: ports,
// Export the Pod IP, Namespace and connectstring to Pod env
Env: []corev1.EnvVar{
{
Name: "NDB_POD_IP",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIP",
},
},
},
{
Name: "NDB_POD_NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
{
Name: "NDB_CONNECTSTRING",
Value: fmt.Sprintf("nodeid=%d,%s",
constants.NdbOperatorDedicatedAPINodeId, nc.GetConnectstring()),
},
},
Command: []string{"/bin/bash", "-ecx", strings.Join(commandAndArgs, " ")},
VolumeMounts: volumeMounts,
}
}
// getWorkDirVolumeMount returns the VolumeMount for the work directory
func (bss *baseStatefulSet) getWorkDirVolumeMount() corev1.VolumeMount {
return corev1.VolumeMount{
Name: workDirVolName,
MountPath: workDirVolMount,
}
}
// getDefaultInitContainers returns the default init containers to be run
func (bss *baseStatefulSet) getDefaultInitContainers(nc *v1.NdbCluster) []corev1.Container {
// The ndb-pod-initializer is tool is the only default container to be run
cmdAndArgs := []string{
"ndb-pod-initializer",
}
// Load just the work dir volume mount
volumeMounts := []corev1.VolumeMount{
bss.getWorkDirVolumeMount(),
}
container := bss.createContainer(nc, "ndb-pod-init-container", cmdAndArgs, volumeMounts, nil)
// Export connection pool size to env for MySQL type pods
if bss.GetTypeName() == constants.NdbNodeTypeMySQLD {
container.Env = append(container.Env, corev1.EnvVar{
Name: "NDB_CONNECTION_POOL_SIZE",
Value: strconv.Itoa(int(nc.GetMySQLServerConnectionPoolSize())),
})
}
// Append the NDB operator password to the env variable of the ndb-pod-init-container
container.Env = append(container.Env, corev1.EnvVar{
Name: "NDB_OPERATOR_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resources.GetMySQLNDBOperatorPasswordSecretName(nc),
},
Key: corev1.BasicAuthPasswordKey,
},
},
})
// Use the current ndb operator image name in the container
ndbOperatorImageName := os.Getenv("NDB_OPERATOR_IMAGE")
container.Image = ndbOperatorImageName
container.ImagePullPolicy = corev1.PullIfNotPresent
return []corev1.Container{container}
}
// newStatefulSet defines a new StatefulSet that will be
// used by the mgmd and ndbmtd StatefulSets
func (bss *baseStatefulSet) newStatefulSet(
nc *v1.NdbCluster, cs *ndbconfig.ConfigSummary) *appsv1.StatefulSet {
// Fill in the podSpec with any provided ImagePullSecrets
var podSpec corev1.PodSpec
imagePullSecretName := nc.Spec.ImagePullSecretName
if imagePullSecretName != "" {
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, corev1.LocalObjectReference{
Name: imagePullSecretName,
})
}
// Add the default init container and add the ndbOperatorImagePullSecretName
// to the existing ImagePullSecrets list.
podSpec.InitContainers = bss.getDefaultInitContainers(nc)
ndbOperatorImagePullSecretName := os.Getenv("NDB_OPERATOR_IMAGE_PULL_SECRET_NAME")
if ndbOperatorImagePullSecretName != "" {
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, corev1.LocalObjectReference{
Name: ndbOperatorImagePullSecretName,
})
}
// Add the empty dir volume
podSpec.Volumes = []corev1.Volume{*bss.getEmptyDirPodVolume(workDirVolName)}
podSpec.ServiceAccountName = nc.GetServiceAccountName()
// Labels to be used for the statefulset pods
podLabels := bss.getPodLabels(nc)
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: bss.GetName(nc),
Labels: bss.getStatefulSetLabels(nc),
// Owner reference pointing to the Ndb resource
OwnerReferences: nc.GetOwnerReferences(),
Annotations: map[string]string{
// Add the NdbCluster generation this statefulset is based on to the annotation
LastAppliedConfigGeneration: strconv.FormatInt(cs.NdbClusterGeneration, 10),
},
},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
// Use the pods labels as the selector
MatchLabels: podLabels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
// Annotate the spec template with the config.ini version.
// A change in the config will create a new version of the spec template.
Annotations: map[string]string{
LastAppliedMySQLClusterConfigVersion: strconv.FormatInt(int64(cs.MySQLClusterConfigVersion), 10),
},
},
Spec: podSpec,
},
// The services must exist before the StatefulSet,
// and is responsible for the network identity of the set.
ServiceName: nc.GetServiceName(bss.nodeType),
},
}
}
// GetName returns the name of the baseStatefulSet
func (bss *baseStatefulSet) GetName(nc *v1.NdbCluster) string {
return nc.GetWorkloadName(bss.nodeType)
}
// GetTypeName returns the type name of baseStatefulSet
func (bss *baseStatefulSet) GetTypeName() constants.NdbNodeType {
return bss.nodeType
}