github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+incompatible/pkg/kube/wait.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kube // import "helm.sh/helm/pkg/kube"
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/pkg/errors"
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	appsv1beta1 "k8s.io/api/apps/v1beta1"
    26  	appsv1beta2 "k8s.io/api/apps/v1beta2"
    27  	batchv1 "k8s.io/api/batch/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/util/intstr"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	"k8s.io/client-go/kubernetes"
    36  	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
    37  )
    38  
    39  type waiter struct {
    40  	c       kubernetes.Interface
    41  	timeout time.Duration
    42  	log     func(string, ...interface{})
    43  }
    44  
    45  // waitForResources polls to get the current status of all pods, PVCs, and Services
    46  // until all are ready or a timeout is reached
    47  func (w *waiter) waitForResources(created Result) error {
    48  	w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout)
    49  
    50  	return wait.Poll(2*time.Second, w.timeout, func() (bool, error) {
    51  		for _, v := range created {
    52  			var (
    53  				// This defaults to true, otherwise we get to a point where
    54  				// things will always return false unless one of the objects
    55  				// that manages pods has been hit
    56  				ok  = true
    57  				err error
    58  			)
    59  			switch value := asVersioned(v).(type) {
    60  			case *corev1.Pod:
    61  				pod, err := w.c.CoreV1().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{})
    62  				if err != nil || !w.isPodReady(pod) {
    63  					return false, err
    64  				}
    65  			case *appsv1.Deployment:
    66  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    67  				if err != nil {
    68  					return false, err
    69  				}
    70  				// Find RS associated with deployment
    71  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
    72  				if err != nil || newReplicaSet == nil {
    73  					return false, err
    74  				}
    75  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
    76  					return false, nil
    77  				}
    78  			case *appsv1beta1.Deployment:
    79  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    80  				if err != nil {
    81  					return false, err
    82  				}
    83  				// Find RS associated with deployment
    84  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
    85  				if err != nil || newReplicaSet == nil {
    86  					return false, err
    87  				}
    88  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
    89  					return false, nil
    90  				}
    91  			case *appsv1beta2.Deployment:
    92  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    93  				if err != nil {
    94  					return false, err
    95  				}
    96  				// Find RS associated with deployment
    97  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
    98  				if err != nil || newReplicaSet == nil {
    99  					return false, err
   100  				}
   101  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
   102  					return false, nil
   103  				}
   104  			case *extensionsv1beta1.Deployment:
   105  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
   106  				if err != nil {
   107  					return false, err
   108  				}
   109  				// Find RS associated with deployment
   110  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
   111  				if err != nil || newReplicaSet == nil {
   112  					return false, err
   113  				}
   114  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
   115  					return false, nil
   116  				}
   117  			case *corev1.PersistentVolumeClaim:
   118  				claim, err := w.c.CoreV1().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{})
   119  				if err != nil {
   120  					return false, err
   121  				}
   122  				if !w.volumeReady(claim) {
   123  					return false, nil
   124  				}
   125  			case *corev1.Service:
   126  				svc, err := w.c.CoreV1().Services(value.Namespace).Get(value.Name, metav1.GetOptions{})
   127  				if err != nil {
   128  					return false, err
   129  				}
   130  				if !w.serviceReady(svc) {
   131  					return false, nil
   132  				}
   133  			case *extensionsv1beta1.DaemonSet, *appsv1.DaemonSet, *appsv1beta2.DaemonSet:
   134  				ds, err := w.c.AppsV1().DaemonSets(v.Namespace).Get(v.Name, metav1.GetOptions{})
   135  				if err != nil {
   136  					return false, err
   137  				}
   138  				if !w.daemonSetReady(ds) {
   139  					return false, nil
   140  				}
   141  			case *appsv1.StatefulSet, *appsv1beta1.StatefulSet, *appsv1beta2.StatefulSet:
   142  				sts, err := w.c.AppsV1().StatefulSets(v.Namespace).Get(v.Name, metav1.GetOptions{})
   143  				if err != nil {
   144  					return false, err
   145  				}
   146  				if !w.statefulSetReady(sts) {
   147  					return false, nil
   148  				}
   149  
   150  			case *corev1.ReplicationController:
   151  				ok, err = w.podsReadyForObject(value.Namespace, value)
   152  			case *extensionsv1beta1.ReplicaSet:
   153  				ok, err = w.podsReadyForObject(value.Namespace, value)
   154  			case *appsv1beta2.ReplicaSet:
   155  				ok, err = w.podsReadyForObject(value.Namespace, value)
   156  			case *appsv1.ReplicaSet:
   157  				ok, err = w.podsReadyForObject(value.Namespace, value)
   158  			}
   159  			if !ok || err != nil {
   160  				return false, err
   161  			}
   162  		}
   163  		return true, nil
   164  	})
   165  }
   166  
   167  func (w *waiter) podsReadyForObject(namespace string, obj runtime.Object) (bool, error) {
   168  	pods, err := w.podsforObject(namespace, obj)
   169  	if err != nil {
   170  		return false, err
   171  	}
   172  	for _, pod := range pods {
   173  		if !w.isPodReady(&pod) {
   174  			return false, nil
   175  		}
   176  	}
   177  	return true, nil
   178  }
   179  
   180  func (w *waiter) podsforObject(namespace string, obj runtime.Object) ([]corev1.Pod, error) {
   181  	selector, err := selectorsForObject(obj)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	list, err := getPods(w.c, namespace, selector.String())
   186  	return list, err
   187  }
   188  
   189  // isPodReady returns true if a pod is ready; false otherwise.
   190  func (w *waiter) isPodReady(pod *corev1.Pod) bool {
   191  	for _, c := range pod.Status.Conditions {
   192  		if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
   193  			return true
   194  		}
   195  	}
   196  	w.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
   197  	return false
   198  }
   199  
   200  func (w *waiter) serviceReady(s *corev1.Service) bool {
   201  	// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
   202  	if s.Spec.Type == corev1.ServiceTypeExternalName {
   203  		return true
   204  	}
   205  
   206  	// Make sure the service is not explicitly set to "None" before checking the IP
   207  	if (s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "") ||
   208  		// This checks if the service has a LoadBalancer and that balancer has an Ingress defined
   209  		(s.Spec.Type == corev1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil) {
   210  		w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName())
   211  		return false
   212  	}
   213  	return true
   214  }
   215  
   216  func (w *waiter) volumeReady(v *corev1.PersistentVolumeClaim) bool {
   217  	if v.Status.Phase != corev1.ClaimBound {
   218  		w.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName())
   219  		return false
   220  	}
   221  	return true
   222  }
   223  
   224  func (w *waiter) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool {
   225  	expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep)
   226  	if !(rs.Status.ReadyReplicas >= expectedReady) {
   227  		w.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady)
   228  		return false
   229  	}
   230  	return true
   231  }
   232  
   233  func (w *waiter) daemonSetReady(ds *appsv1.DaemonSet) bool {
   234  	// If the update strategy is not a rolling update, there will be nothing to wait for
   235  	if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
   236  		return true
   237  	}
   238  
   239  	// Make sure all the updated pods have been scheduled
   240  	if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled {
   241  		w.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled)
   242  		return false
   243  	}
   244  	maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true)
   245  	if err != nil {
   246  		// If for some reason the value is invalid, set max unavailable to the
   247  		// number of desired replicas. This is the same behavior as the
   248  		// `MaxUnavailable` function in deploymentutil
   249  		maxUnavailable = int(ds.Status.DesiredNumberScheduled)
   250  	}
   251  
   252  	expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable
   253  	if !(int(ds.Status.NumberReady) >= expectedReady) {
   254  		w.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady)
   255  		return false
   256  	}
   257  	return true
   258  }
   259  
   260  func (w *waiter) statefulSetReady(sts *appsv1.StatefulSet) bool {
   261  	// If the update strategy is not a rolling update, there will be nothing to wait for
   262  	if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
   263  		return true
   264  	}
   265  
   266  	// Dereference all the pointers because StatefulSets like them
   267  	var partition int
   268  	// 1 is the default for replicas if not set
   269  	var replicas = 1
   270  	if sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil {
   271  		partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition)
   272  	}
   273  	if sts.Spec.Replicas != nil {
   274  		replicas = int(*sts.Spec.Replicas)
   275  	}
   276  
   277  	// Because an update strategy can use partitioning, we need to calculate the
   278  	// number of updated replicas we should have. For example, if the replicas
   279  	// is set to 3 and the partition is 2, we'd expect only one pod to be
   280  	// updated
   281  	expectedReplicas := replicas - partition
   282  
   283  	// Make sure all the updated pods have been scheduled
   284  	if int(sts.Status.UpdatedReplicas) != expectedReplicas {
   285  		w.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas)
   286  		return false
   287  	}
   288  
   289  	if int(sts.Status.ReadyReplicas) != replicas {
   290  		w.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
   291  		return false
   292  	}
   293  	return true
   294  }
   295  
   296  func getPods(client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) {
   297  	list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
   298  		LabelSelector: selector,
   299  	})
   300  	return list.Items, err
   301  }
   302  
   303  // selectorsForObject returns the pod label selector for a given object
   304  //
   305  // Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84
   306  func selectorsForObject(object runtime.Object) (selector labels.Selector, err error) {
   307  	switch t := object.(type) {
   308  	case *extensionsv1beta1.ReplicaSet:
   309  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   310  	case *appsv1.ReplicaSet:
   311  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   312  	case *appsv1beta2.ReplicaSet:
   313  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   314  	case *corev1.ReplicationController:
   315  		selector = labels.SelectorFromSet(t.Spec.Selector)
   316  	case *appsv1.StatefulSet:
   317  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   318  	case *appsv1beta1.StatefulSet:
   319  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   320  	case *appsv1beta2.StatefulSet:
   321  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   322  	case *extensionsv1beta1.DaemonSet:
   323  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   324  	case *appsv1.DaemonSet:
   325  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   326  	case *appsv1beta2.DaemonSet:
   327  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   328  	case *extensionsv1beta1.Deployment:
   329  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   330  	case *appsv1.Deployment:
   331  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   332  	case *appsv1beta1.Deployment:
   333  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   334  	case *appsv1beta2.Deployment:
   335  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   336  	case *batchv1.Job:
   337  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   338  	case *corev1.Service:
   339  		if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 {
   340  			return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name)
   341  		}
   342  		selector = labels.SelectorFromSet(t.Spec.Selector)
   343  
   344  	default:
   345  		return nil, fmt.Errorf("selector for %T not implemented", object)
   346  	}
   347  
   348  	return selector, errors.Wrap(err, "invalid label selector")
   349  }