pkg/controllers/common_utils.go (112 lines of code) (raw):
// Copyright (c) 2021, 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 controllers
import (
"fmt"
"strconv"
"strings"
v1 "github.com/mysql/ndb-operator/pkg/apis/ndbcontroller/v1"
"github.com/mysql/ndb-operator/pkg/resources/statefulset"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
PodInitializing = "PodInitializing"
ContainerCreating = "ContainerCreating"
Separator = "/"
)
// getNamespacedName returns the name of the object
// along with the Namespace of form <namespace>/<name>.
func getNamespacedName(obj metav1.Object) string {
return obj.GetNamespace() + Separator + obj.GetName()
}
// getNamespacedName2 combines and returns the name and
// namespace in the form <namespace>/<name>.
func getNamespacedName2(namespace, name string) string {
return namespace + Separator + name
}
// getNdbClusterKey returns a key for the
// given NdbCluster of form <namespace>/<name>.
func getNdbClusterKey(nc *v1.NdbCluster) string {
return getNamespacedName(nc)
}
// statefulsetUpdateComplete returns true when all the pods
// controlled by the given statefulSet have been updated to
// the latest version and are ready.
func statefulsetUpdateComplete(statefulset *appsv1.StatefulSet) bool {
return statefulset.Status.Replicas == *(statefulset.Spec.Replicas) &&
statefulset.Status.ReadyReplicas == *(statefulset.Spec.Replicas) &&
statefulset.Status.UpdatedReplicas == *(statefulset.Spec.Replicas) &&
statefulset.Status.ObservedGeneration >= statefulset.Generation &&
// CurrentRevision/Replicas not updated if OnDelete update strategy is used.
// So skip checking statefulset.Status.CurrentReplicas for OnDelete
// https://github.com/kubernetes/kubernetes/issues/106055
(statefulset.Spec.UpdateStrategy.Type == appsv1.OnDeleteStatefulSetStrategyType ||
statefulset.Status.CurrentReplicas == *(statefulset.Spec.Replicas))
}
// isInitialFlagSet() returns true when the StatefulSet container
// specification includes the --initial flag and returns false in all other cases
func isInitialFlagSet(dataNodeSfSet *appsv1.StatefulSet) bool {
// check if the StatefulSet is valid
if dataNodeSfSet == nil {
return false
}
container := dataNodeSfSet.Spec.Template.Spec.Containers[0]
// Check if the Command of the container contains "--initial"
for _, arg := range container.Command {
if strings.Contains(arg, "--initial") {
return true
}
}
return false
}
// statefulsetReady considers a StatefulSet to be ready if all the pods
// created by the statefulSet are ready. Note that this doesn't check if
// the pods created by the StatefulSet are running the latest revision of
// pod spec.
func statefulsetReady(statefulset *appsv1.StatefulSet) bool {
return statefulset.Status.ReadyReplicas == *(statefulset.Spec.Replicas) &&
statefulset.Status.ObservedGeneration == statefulset.Generation
}
// workloadHasConfigGeneration returns true if the expectedConfigGeneration
// has already been applied to the given Deployment/StatefulSet.
func workloadHasConfigGeneration(obj metav1.Object, expectedConfigGeneration int64) bool {
// Get the last applied Config Generation
annotations := obj.GetAnnotations()
existingConfigGeneration, _ := strconv.ParseInt(annotations[statefulset.LastAppliedConfigGeneration], 10, 64)
return existingConfigGeneration == expectedConfigGeneration
}
// getPodCondition returns the PodCondition of given type.
func getPodCondition(pod *corev1.Pod, conditionType corev1.PodConditionType) *corev1.PodCondition {
for _, condition := range pod.Status.Conditions {
if condition.Type == conditionType {
return &condition
}
}
return nil
}
// checkContainersForError returns any errors in the given container statuses
func checkContainersForError(containerStatuses []corev1.ContainerStatus, podName string) (errs []string) {
for _, containerStatus := range containerStatuses {
containerState := containerStatus.State
if containerStatus.Ready ||
containerState.Running != nil {
// Container is either ready or running
// No errors yet in the current run.
continue
}
if containerState.Waiting != nil {
if containerState.Waiting.Reason != PodInitializing &&
containerState.Waiting.Reason != ContainerCreating {
msg := fmt.Sprintf("pod %q struck in waiting state : %s", podName,
containerState.Waiting.Reason)
if containerState.Waiting.Message != "" {
// Append the message to err
msg += " : " + containerState.Waiting.Message
}
errs = append(errs, msg)
}
// Container is waiting, check for failure in LastTerminationState if any
containerState = containerStatus.LastTerminationState
}
// Check if the container was terminated and if terminated, check the exit code :
// only 0 (graceful shutdown) and 137 (killed by SIGKILL) are allowed.
if containerState.Terminated != nil &&
containerState.Terminated.ExitCode != 0 && containerState.Terminated.ExitCode != 137 {
msg := fmt.Sprintf("pod %q failed : container %q terminated with exit code %d",
podName, containerStatus.Name, containerState.Terminated.ExitCode)
if containerState.Terminated.Message != "" {
// Append the message to err
msg += " : " + containerState.Terminated.Message
}
errs = append(errs, msg)
}
}
return errs
}
// getPodErrors returns all errors currently faced by the pod or the containers running in it.
func getPodErrors(pod *corev1.Pod) (errs []string) {
podReadyCondition := getPodCondition(pod, corev1.PodReady)
if podReadyCondition != nil && podReadyCondition.Status == corev1.ConditionTrue {
// Pod is ready => no error
return nil
}
// Check if the pod has been scheduled
podScheduledCondition := getPodCondition(pod, corev1.PodScheduled)
if podScheduledCondition == nil {
// Pod has been just added
return nil
} else if podScheduledCondition.Status != corev1.ConditionTrue {
// Pod is not scheduled yet.
// Check if it is unschedulable.
if podScheduledCondition.Reason == corev1.PodReasonUnschedulable {
// This pod is unschedulable due to some reason
return []string{fmt.Sprintf("pod %q is unschedulable : %s",
getNamespacedName(pod), podScheduledCondition.Message)}
}
// Pod has not been scheduled yet, but it is not unschedulable => no error
return nil
}
// Pod has been scheduled but not ready.
// Some or all of the containers must have been started.
// Retrieve and return any errors from them
errs = append(errs, checkContainersForError(pod.Status.InitContainerStatuses, pod.Name)...)
errs = append(errs, checkContainersForError(pod.Status.ContainerStatuses, pod.Name)...)
return errs
}