github.com/verrazzano/verrazzano@v1.7.0/pkg/k8s/ready/deployment_ready.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  package ready
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strconv"
     9  
    10  	pkgConstants "github.com/verrazzano/verrazzano/pkg/constants"
    11  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    12  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    13  	appsv1 "k8s.io/api/apps/v1"
    14  	corev1 "k8s.io/api/core/v1"
    15  	"k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/types"
    18  	clipkg "sigs.k8s.io/controller-runtime/pkg/client"
    19  )
    20  
    21  var veleroPodSelector = &metav1.LabelSelector{
    22  	MatchLabels: map[string]string{
    23  		"name": pkgConstants.Velero,
    24  	},
    25  }
    26  
    27  func DeploymentsReadyBySelectors(log vzlog.VerrazzanoLogger, client clipkg.Client, expectedReplicas int32, prefix string, opts ...clipkg.ListOption) bool {
    28  	deploymentList := &appsv1.DeploymentList{}
    29  	if err := client.List(context.TODO(), deploymentList, opts...); err != nil {
    30  		logErrorf(log, "%s failed listing deployments for selectors %v: %v", prefix, opts, err)
    31  		return false
    32  	}
    33  	if deploymentList.Items == nil || len(deploymentList.Items) < 1 {
    34  		logProgressf(log, "%s is waiting for deployments matching selector %s to exist", prefix, opts)
    35  		return false
    36  	}
    37  	for idx := range deploymentList.Items {
    38  		deployment := &deploymentList.Items[idx]
    39  		namespacedName := types.NamespacedName{
    40  			Namespace: deployment.Namespace,
    41  			Name:      deployment.Name,
    42  		}
    43  		if !deploymentFullyReady(log, client, deployment, namespacedName, expectedReplicas, prefix) {
    44  			return false
    45  		}
    46  	}
    47  	return true
    48  }
    49  
    50  // DeploymentsAreReady check that the named deployments have the minimum number of specified replicas ready and available
    51  func DeploymentsAreReady(log vzlog.VerrazzanoLogger, client clipkg.Client, namespacedNames []types.NamespacedName, expectedReplicas int32, prefix string) bool {
    52  	for _, namespacedName := range namespacedNames {
    53  		deployment := appsv1.Deployment{}
    54  		if err := client.Get(context.TODO(), namespacedName, &deployment); err != nil {
    55  			if errors.IsNotFound(err) {
    56  				logProgressf(log, "%s is waiting for deployment %v to exist", prefix, namespacedName)
    57  				return false
    58  			}
    59  			logErrorf(log, "%s failed getting deployment %v: %v", prefix, namespacedName, err)
    60  			return false
    61  		}
    62  		if !deploymentFullyReady(log, client, &deployment, namespacedName, expectedReplicas, prefix) {
    63  			return false
    64  		}
    65  	}
    66  	return true
    67  }
    68  
    69  // DoDeploymentsExist checks if all the named deployments exist
    70  func DoDeploymentsExist(log vzlog.VerrazzanoLogger, client clipkg.Client, namespacedNames []types.NamespacedName, _ int32, prefix string) bool {
    71  	for _, namespacedName := range namespacedNames {
    72  		exists, err := DoesDeploymentExist(client, namespacedName)
    73  		if err != nil {
    74  			logErrorf(log, "%s failed getting deployment %v: %v", prefix, namespacedName, err)
    75  			return false
    76  		}
    77  		if !exists {
    78  			return false
    79  		}
    80  	}
    81  	return true
    82  }
    83  
    84  // DoesDeploymentsExist checks if the named deployment exists
    85  func DoesDeploymentExist(client clipkg.Client, namespacedName types.NamespacedName) (bool, error) {
    86  	deployment := appsv1.Deployment{}
    87  	if err := client.Get(context.TODO(), namespacedName, &deployment); err != nil {
    88  		if errors.IsNotFound(err) {
    89  			return false, nil
    90  		}
    91  		return false, err
    92  	}
    93  	return true, nil
    94  }
    95  
    96  func deploymentFullyReady(log vzlog.VerrazzanoLogger, client clipkg.Client, deployment *appsv1.Deployment, namespacedName types.NamespacedName, expectedReplicas int32, prefix string) bool {
    97  	if deployment.Spec.Replicas != nil && *(deployment.Spec.Replicas) == 0 && expectedReplicas > 0 {
    98  		logProgressf(log, "%s will not reach ready state since the deployment %s has spec.replicas set to %v. Expecting %v replicas.",
    99  			prefix, namespacedName, *(deployment.Spec.Replicas), expectedReplicas)
   100  		return false
   101  	}
   102  	if deployment.Status.UpdatedReplicas < expectedReplicas {
   103  		logProgressf(log, "%s is waiting for deployment %s replicas to be %v. Current updated replicas is %v", prefix, namespacedName,
   104  			expectedReplicas, deployment.Status.UpdatedReplicas)
   105  		return false
   106  	}
   107  	if deployment.Status.AvailableReplicas < expectedReplicas {
   108  		logProgressf(log, "%s is waiting for deployment %s replicas to be %v. Current available replicas is %v", prefix, namespacedName,
   109  			expectedReplicas, deployment.Status.AvailableReplicas)
   110  		return false
   111  	}
   112  	// Velero install deploys a daemonset and deployment with common labels. The labels need to be adjusted so the pod fetch logic works
   113  	// as expected
   114  	podSelector := deployment.Spec.Selector
   115  	if namespacedName.Namespace == constants.VeleroNameSpace && namespacedName.Name == pkgConstants.Velero {
   116  		podSelector = veleroPodSelector
   117  	}
   118  
   119  	if !PodsReadyDeployment(log, client, namespacedName, podSelector, expectedReplicas, prefix) {
   120  		return false
   121  	}
   122  	logOncef(log, "%s has enough replicas for deployment %v", prefix, namespacedName)
   123  	return true
   124  }
   125  
   126  // PodsReadyDeployment checks for an expected number of pods to be using the latest replicaset revision and are
   127  // running and ready
   128  func PodsReadyDeployment(log vzlog.VerrazzanoLogger, client clipkg.Client, namespacedName types.NamespacedName, selector *metav1.LabelSelector, expectedReplicas int32, prefix string) bool {
   129  	// Get a list of pods for a given namespace and labels selector
   130  	pods := GetPodsList(log, client, namespacedName, selector)
   131  	if pods == nil {
   132  		return false
   133  	}
   134  
   135  	// If no pods found log a progress message and return
   136  	if len(pods.Items) == 0 {
   137  		logProgressf(log, "Found no pods with matching labels selector %v for namespace %s", selector, namespacedName.Namespace)
   138  		return false
   139  	}
   140  
   141  	// Loop through pods identifying pods that are using the latest replicaset revision
   142  	var savedPods []corev1.Pod
   143  	var savedPodTemplateHash string
   144  	var savedRevision int
   145  	for _, pod := range pods.Items {
   146  		// Log error and return if the pod-template-hash label is not found.  This should never happen.
   147  		if _, ok := pod.Labels[podTemplateHashLabel]; !ok {
   148  			logErrorf(log, "Failed to find pod label [pod-template-hash] for pod %s/%s", pod.Namespace, pod.Name)
   149  			return false
   150  		}
   151  
   152  		if pod.Labels[podTemplateHashLabel] == savedPodTemplateHash {
   153  			savedPods = append(savedPods, pod)
   154  			continue
   155  		}
   156  
   157  		// Get the replica set for the pod given the pod-template-hash label
   158  		var rs appsv1.ReplicaSet
   159  		rsName := fmt.Sprintf("%s-%s", namespacedName.Name, pod.Labels[podTemplateHashLabel])
   160  		err := client.Get(context.TODO(), types.NamespacedName{Namespace: namespacedName.Namespace, Name: rsName}, &rs)
   161  		if err != nil {
   162  			logErrorf(log, "Failed to get replicaset %s: %v", namespacedName, err)
   163  			return false
   164  		}
   165  
   166  		// Log error and return if the deployment.kubernetes.io/revision annotation is not found.  This should never happen.
   167  		if _, ok := rs.Annotations[deploymentRevisionAnnotation]; !ok {
   168  			logErrorf(log, "Failed to find pod annotation [deployment.kubernetes.io/revision] for pod %s/%s", pod.Namespace, pod.Name)
   169  			return false
   170  		}
   171  
   172  		revision, _ := strconv.Atoi(rs.Annotations[deploymentRevisionAnnotation])
   173  		if revision > savedRevision {
   174  			savedRevision = revision
   175  			savedPodTemplateHash = pod.Labels[podTemplateHashLabel]
   176  			savedPods = []corev1.Pod{}
   177  			savedPods = append(savedPods, pod)
   178  		}
   179  	}
   180  
   181  	// Make sure pods using the latest replicaset revision are ready.
   182  	podsReady, success := EnsurePodsAreReady(log, savedPods, expectedReplicas, prefix)
   183  	if !success {
   184  		return false
   185  	}
   186  
   187  	if podsReady < expectedReplicas {
   188  		logProgressf(log, "%s is waiting for deployment %s pods to be %v. Current available pods are %v", prefix, namespacedName,
   189  			expectedReplicas, podsReady)
   190  		return false
   191  	}
   192  
   193  	return true
   194  }