github.com/uhthomas/helm@v3.0.0-beta.3+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  	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/util/intstr"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/client-go/kubernetes"
    37  	"k8s.io/client-go/kubernetes/scheme"
    38  	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
    39  )
    40  
    41  type waiter struct {
    42  	c       kubernetes.Interface
    43  	timeout time.Duration
    44  	log     func(string, ...interface{})
    45  }
    46  
    47  // waitForResources polls to get the current status of all pods, PVCs, and Services
    48  // until all are ready or a timeout is reached
    49  func (w *waiter) waitForResources(created ResourceList) error {
    50  	w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout)
    51  
    52  	return wait.Poll(2*time.Second, w.timeout, func() (bool, error) {
    53  		for _, v := range created {
    54  			var (
    55  				// This defaults to true, otherwise we get to a point where
    56  				// things will always return false unless one of the objects
    57  				// that manages pods has been hit
    58  				ok  = true
    59  				err error
    60  			)
    61  			switch value := AsVersioned(v).(type) {
    62  			case *corev1.Pod:
    63  				pod, err := w.c.CoreV1().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{})
    64  				if err != nil || !w.isPodReady(pod) {
    65  					return false, err
    66  				}
    67  			case *appsv1.Deployment:
    68  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    69  				if err != nil {
    70  					return false, err
    71  				}
    72  				// Find RS associated with deployment
    73  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
    74  				if err != nil || newReplicaSet == nil {
    75  					return false, err
    76  				}
    77  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
    78  					return false, nil
    79  				}
    80  			case *appsv1beta1.Deployment:
    81  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    82  				if err != nil {
    83  					return false, err
    84  				}
    85  				// Find RS associated with deployment
    86  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
    87  				if err != nil || newReplicaSet == nil {
    88  					return false, err
    89  				}
    90  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
    91  					return false, nil
    92  				}
    93  			case *appsv1beta2.Deployment:
    94  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
    95  				if err != nil {
    96  					return false, err
    97  				}
    98  				// Find RS associated with deployment
    99  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
   100  				if err != nil || newReplicaSet == nil {
   101  					return false, err
   102  				}
   103  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
   104  					return false, nil
   105  				}
   106  			case *extensionsv1beta1.Deployment:
   107  				currentDeployment, err := w.c.AppsV1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{})
   108  				if err != nil {
   109  					return false, err
   110  				}
   111  				// Find RS associated with deployment
   112  				newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, w.c.AppsV1())
   113  				if err != nil || newReplicaSet == nil {
   114  					return false, err
   115  				}
   116  				if !w.deploymentReady(newReplicaSet, currentDeployment) {
   117  					return false, nil
   118  				}
   119  			case *corev1.PersistentVolumeClaim:
   120  				claim, err := w.c.CoreV1().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{})
   121  				if err != nil {
   122  					return false, err
   123  				}
   124  				if !w.volumeReady(claim) {
   125  					return false, nil
   126  				}
   127  			case *corev1.Service:
   128  				svc, err := w.c.CoreV1().Services(value.Namespace).Get(value.Name, metav1.GetOptions{})
   129  				if err != nil {
   130  					return false, err
   131  				}
   132  				if !w.serviceReady(svc) {
   133  					return false, nil
   134  				}
   135  			case *extensionsv1beta1.DaemonSet, *appsv1.DaemonSet, *appsv1beta2.DaemonSet:
   136  				ds, err := w.c.AppsV1().DaemonSets(v.Namespace).Get(v.Name, metav1.GetOptions{})
   137  				if err != nil {
   138  					return false, err
   139  				}
   140  				if !w.daemonSetReady(ds) {
   141  					return false, nil
   142  				}
   143  			case *apiextv1beta1.CustomResourceDefinition:
   144  				if err := v.Get(); err != nil {
   145  					return false, err
   146  				}
   147  				crd := &apiextv1beta1.CustomResourceDefinition{}
   148  				if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil {
   149  					return false, err
   150  				}
   151  				if !w.crdReady(*crd) {
   152  					return false, nil
   153  				}
   154  			case *appsv1.StatefulSet, *appsv1beta1.StatefulSet, *appsv1beta2.StatefulSet:
   155  				sts, err := w.c.AppsV1().StatefulSets(v.Namespace).Get(v.Name, metav1.GetOptions{})
   156  				if err != nil {
   157  					return false, err
   158  				}
   159  				if !w.statefulSetReady(sts) {
   160  					return false, nil
   161  				}
   162  
   163  			case *corev1.ReplicationController:
   164  				ok, err = w.podsReadyForObject(value.Namespace, value)
   165  			case *extensionsv1beta1.ReplicaSet:
   166  				ok, err = w.podsReadyForObject(value.Namespace, value)
   167  			case *appsv1beta2.ReplicaSet:
   168  				ok, err = w.podsReadyForObject(value.Namespace, value)
   169  			case *appsv1.ReplicaSet:
   170  				ok, err = w.podsReadyForObject(value.Namespace, value)
   171  			}
   172  			if !ok || err != nil {
   173  				return false, err
   174  			}
   175  		}
   176  		return true, nil
   177  	})
   178  }
   179  
   180  func (w *waiter) podsReadyForObject(namespace string, obj runtime.Object) (bool, error) {
   181  	pods, err := w.podsforObject(namespace, obj)
   182  	if err != nil {
   183  		return false, err
   184  	}
   185  	for _, pod := range pods {
   186  		if !w.isPodReady(&pod) {
   187  			return false, nil
   188  		}
   189  	}
   190  	return true, nil
   191  }
   192  
   193  func (w *waiter) podsforObject(namespace string, obj runtime.Object) ([]corev1.Pod, error) {
   194  	selector, err := SelectorsForObject(obj)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	list, err := getPods(w.c, namespace, selector.String())
   199  	return list, err
   200  }
   201  
   202  // isPodReady returns true if a pod is ready; false otherwise.
   203  func (w *waiter) isPodReady(pod *corev1.Pod) bool {
   204  	for _, c := range pod.Status.Conditions {
   205  		if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
   206  			return true
   207  		}
   208  	}
   209  	w.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName())
   210  	return false
   211  }
   212  
   213  func (w *waiter) serviceReady(s *corev1.Service) bool {
   214  	// ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set)
   215  	if s.Spec.Type == corev1.ServiceTypeExternalName {
   216  		return true
   217  	}
   218  
   219  	// Make sure the service is not explicitly set to "None" before checking the IP
   220  	if (s.Spec.ClusterIP != corev1.ClusterIPNone && s.Spec.ClusterIP == "") ||
   221  		// This checks if the service has a LoadBalancer and that balancer has an Ingress defined
   222  		(s.Spec.Type == corev1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil) {
   223  		w.log("Service does not have IP address: %s/%s", s.GetNamespace(), s.GetName())
   224  		return false
   225  	}
   226  	return true
   227  }
   228  
   229  func (w *waiter) volumeReady(v *corev1.PersistentVolumeClaim) bool {
   230  	if v.Status.Phase != corev1.ClaimBound {
   231  		w.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName())
   232  		return false
   233  	}
   234  	return true
   235  }
   236  
   237  func (w *waiter) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool {
   238  	expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep)
   239  	if !(rs.Status.ReadyReplicas >= expectedReady) {
   240  		w.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady)
   241  		return false
   242  	}
   243  	return true
   244  }
   245  
   246  func (w *waiter) daemonSetReady(ds *appsv1.DaemonSet) bool {
   247  	// If the update strategy is not a rolling update, there will be nothing to wait for
   248  	if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
   249  		return true
   250  	}
   251  
   252  	// Make sure all the updated pods have been scheduled
   253  	if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled {
   254  		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)
   255  		return false
   256  	}
   257  	maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true)
   258  	if err != nil {
   259  		// If for some reason the value is invalid, set max unavailable to the
   260  		// number of desired replicas. This is the same behavior as the
   261  		// `MaxUnavailable` function in deploymentutil
   262  		maxUnavailable = int(ds.Status.DesiredNumberScheduled)
   263  	}
   264  
   265  	expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable
   266  	if !(int(ds.Status.NumberReady) >= expectedReady) {
   267  		w.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady)
   268  		return false
   269  	}
   270  	return true
   271  }
   272  
   273  func (w *waiter) crdReady(crd apiextv1beta1.CustomResourceDefinition) bool {
   274  	for _, cond := range crd.Status.Conditions {
   275  		switch cond.Type {
   276  		case apiextv1beta1.Established:
   277  			if cond.Status == apiextv1beta1.ConditionTrue {
   278  				return true
   279  			}
   280  		case apiextv1beta1.NamesAccepted:
   281  			if cond.Status == apiextv1beta1.ConditionFalse {
   282  				// This indicates a naming conflict, but it's probably not the
   283  				// job of this function to fail because of that. Instead,
   284  				// we treat it as a success, since the process should be able to
   285  				// continue.
   286  				return true
   287  			}
   288  		}
   289  	}
   290  	return false
   291  }
   292  
   293  func (w *waiter) statefulSetReady(sts *appsv1.StatefulSet) bool {
   294  	// If the update strategy is not a rolling update, there will be nothing to wait for
   295  	if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
   296  		return true
   297  	}
   298  
   299  	// Dereference all the pointers because StatefulSets like them
   300  	var partition int
   301  	// 1 is the default for replicas if not set
   302  	var replicas = 1
   303  	// For some reason, even if the update strategy is a rolling update, the
   304  	// actual rollingUpdate field can be nil. If it is, we can safely assume
   305  	// there is no partition value
   306  	if sts.Spec.UpdateStrategy.RollingUpdate != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil {
   307  		partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition)
   308  	}
   309  	if sts.Spec.Replicas != nil {
   310  		replicas = int(*sts.Spec.Replicas)
   311  	}
   312  
   313  	// Because an update strategy can use partitioning, we need to calculate the
   314  	// number of updated replicas we should have. For example, if the replicas
   315  	// is set to 3 and the partition is 2, we'd expect only one pod to be
   316  	// updated
   317  	expectedReplicas := replicas - partition
   318  
   319  	// Make sure all the updated pods have been scheduled
   320  	if int(sts.Status.UpdatedReplicas) != expectedReplicas {
   321  		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)
   322  		return false
   323  	}
   324  
   325  	if int(sts.Status.ReadyReplicas) != replicas {
   326  		w.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
   327  		return false
   328  	}
   329  	return true
   330  }
   331  
   332  func getPods(client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) {
   333  	list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
   334  		LabelSelector: selector,
   335  	})
   336  	return list.Items, err
   337  }
   338  
   339  // SelectorsForObject returns the pod label selector for a given object
   340  //
   341  // Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84
   342  func SelectorsForObject(object runtime.Object) (selector labels.Selector, err error) {
   343  	switch t := object.(type) {
   344  	case *extensionsv1beta1.ReplicaSet:
   345  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   346  	case *appsv1.ReplicaSet:
   347  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   348  	case *appsv1beta2.ReplicaSet:
   349  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   350  	case *corev1.ReplicationController:
   351  		selector = labels.SelectorFromSet(t.Spec.Selector)
   352  	case *appsv1.StatefulSet:
   353  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   354  	case *appsv1beta1.StatefulSet:
   355  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   356  	case *appsv1beta2.StatefulSet:
   357  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   358  	case *extensionsv1beta1.DaemonSet:
   359  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   360  	case *appsv1.DaemonSet:
   361  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   362  	case *appsv1beta2.DaemonSet:
   363  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   364  	case *extensionsv1beta1.Deployment:
   365  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   366  	case *appsv1.Deployment:
   367  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   368  	case *appsv1beta1.Deployment:
   369  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   370  	case *appsv1beta2.Deployment:
   371  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   372  	case *batchv1.Job:
   373  		selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
   374  	case *corev1.Service:
   375  		if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 {
   376  			return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name)
   377  		}
   378  		selector = labels.SelectorFromSet(t.Spec.Selector)
   379  
   380  	default:
   381  		return nil, fmt.Errorf("selector for %T not implemented", object)
   382  	}
   383  
   384  	return selector, errors.Wrap(err, "invalid label selector")
   385  }