github.com/aaronmell/helm@v3.0.0-beta.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 ResourceList) 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  	// For some reason, even if the update strategy is a rolling update, the
   271  	// actual rollingUpdate field can be nil. If it is, we can safely assume
   272  	// there is no partition value
   273  	if sts.Spec.UpdateStrategy.RollingUpdate != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil {
   274  		partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition)
   275  	}
   276  	if sts.Spec.Replicas != nil {
   277  		replicas = int(*sts.Spec.Replicas)
   278  	}
   279  
   280  	// Because an update strategy can use partitioning, we need to calculate the
   281  	// number of updated replicas we should have. For example, if the replicas
   282  	// is set to 3 and the partition is 2, we'd expect only one pod to be
   283  	// updated
   284  	expectedReplicas := replicas - partition
   285  
   286  	// Make sure all the updated pods have been scheduled
   287  	if int(sts.Status.UpdatedReplicas) != expectedReplicas {
   288  		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)
   289  		return false
   290  	}
   291  
   292  	if int(sts.Status.ReadyReplicas) != replicas {
   293  		w.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
   294  		return false
   295  	}
   296  	return true
   297  }
   298  
   299  func getPods(client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) {
   300  	list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
   301  		LabelSelector: selector,
   302  	})
   303  	return list.Items, err
   304  }
   305  
   306  // SelectorsForObject returns the pod label selector for a given object
   307  //
   308  // Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84
   309  func SelectorsForObject(object runtime.Object) (selector labels.Selector, err error) {
   310  	switch t := object.(type) {
   311  	case *extensionsv1beta1.ReplicaSet:
   312  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   313  	case *appsv1.ReplicaSet:
   314  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   315  	case *appsv1beta2.ReplicaSet:
   316  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   317  	case *corev1.ReplicationController:
   318  		selector = labels.SelectorFromSet(t.Spec.Selector)
   319  	case *appsv1.StatefulSet:
   320  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   321  	case *appsv1beta1.StatefulSet:
   322  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   323  	case *appsv1beta2.StatefulSet:
   324  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   325  	case *extensionsv1beta1.DaemonSet:
   326  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   327  	case *appsv1.DaemonSet:
   328  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   329  	case *appsv1beta2.DaemonSet:
   330  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   331  	case *extensionsv1beta1.Deployment:
   332  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   333  	case *appsv1.Deployment:
   334  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   335  	case *appsv1beta1.Deployment:
   336  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   337  	case *appsv1beta2.Deployment:
   338  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   339  	case *batchv1.Job:
   340  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   341  	case *corev1.Service:
   342  		if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 {
   343  			return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name)
   344  		}
   345  		selector = labels.SelectorFromSet(t.Spec.Selector)
   346  
   347  	default:
   348  		return nil, fmt.Errorf("selector for %T not implemented", object)
   349  	}
   350  
   351  	return selector, errors.Wrap(err, "invalid label selector")
   352  }