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 }