...

Source file src/helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go

Documentation: helm.sh/helm/v3/internal/third_party/k8s.io/kubernetes/deployment/util

     1  /*
     2  Copyright 2016 The Kubernetes 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 util
    18  
    19  import (
    20  	"context"
    21  	"sort"
    22  
    23  	apps "k8s.io/api/apps/v1"
    24  	v1 "k8s.io/api/core/v1"
    25  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
    28  	appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
    29  )
    30  
    31  // deploymentutil contains a copy of a few functions from Kubernetes controller code to avoid a dependency on k8s.io/kubernetes.
    32  // This code is copied from https://github.com/kubernetes/kubernetes/blob/e856613dd5bb00bcfaca6974431151b5c06cbed5/pkg/controller/deployment/util/deployment_util.go
    33  // No changes to the code were made other than removing some unused functions
    34  
    35  // RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions.
    36  type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error)
    37  
    38  // ListReplicaSets returns a slice of RSes the given deployment targets.
    39  // Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
    40  // because only the controller itself should do that.
    41  // However, it does filter out anything whose ControllerRef doesn't match.
    42  func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) {
    43  	// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
    44  	//       should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
    45  	namespace := deployment.Namespace
    46  	selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	options := metav1.ListOptions{LabelSelector: selector.String()}
    51  	all, err := getRSList(namespace, options)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	// Only include those whose ControllerRef matches the Deployment.
    56  	owned := make([]*apps.ReplicaSet, 0, len(all))
    57  	for _, rs := range all {
    58  		if metav1.IsControlledBy(rs, deployment) {
    59  			owned = append(owned, rs)
    60  		}
    61  	}
    62  	return owned, nil
    63  }
    64  
    65  // ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
    66  type ReplicaSetsByCreationTimestamp []*apps.ReplicaSet
    67  
    68  func (o ReplicaSetsByCreationTimestamp) Len() int      { return len(o) }
    69  func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
    70  func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool {
    71  	if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
    72  		return o[i].Name < o[j].Name
    73  	}
    74  	return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
    75  }
    76  
    77  // FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
    78  func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet {
    79  	sort.Sort(ReplicaSetsByCreationTimestamp(rsList))
    80  	for i := range rsList {
    81  		if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
    82  			// In rare cases, such as after cluster upgrades, Deployment may end up with
    83  			// having more than one new ReplicaSets that have the same template as its template,
    84  			// see https://github.com/kubernetes/kubernetes/issues/40415
    85  			// We deterministically choose the oldest new ReplicaSet.
    86  			return rsList[i]
    87  		}
    88  	}
    89  	// new ReplicaSet does not exist.
    90  	return nil
    91  }
    92  
    93  // EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
    94  // We ignore pod-template-hash because:
    95  //  1. The hash result would be different upon podTemplateSpec API changes
    96  //     (e.g. the addition of a new field will cause the hash code to change)
    97  //  2. The deployment template won't have hash labels
    98  func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
    99  	t1Copy := template1.DeepCopy()
   100  	t2Copy := template2.DeepCopy()
   101  	// Remove hash labels from template.Labels before comparing
   102  	delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
   103  	delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
   104  	return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
   105  }
   106  
   107  // GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
   108  // Returns nil if the new replica set doesn't exist yet.
   109  func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) {
   110  	rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	return FindNewReplicaSet(deployment, rsList), nil
   115  }
   116  
   117  // RsListFromClient returns an rsListFunc that wraps the given client.
   118  func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc {
   119  	return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) {
   120  		rsList, err := c.ReplicaSets(namespace).List(context.Background(), options)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		var ret []*apps.ReplicaSet
   125  		for i := range rsList.Items {
   126  			ret = append(ret, &rsList.Items[i])
   127  		}
   128  		return ret, err
   129  	}
   130  }
   131  
   132  // IsRollingUpdate returns true if the strategy type is a rolling update.
   133  func IsRollingUpdate(deployment *apps.Deployment) bool {
   134  	return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType
   135  }
   136  
   137  // MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
   138  func MaxUnavailable(deployment apps.Deployment) int32 {
   139  	if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
   140  		return int32(0)
   141  	}
   142  	// Error caught by validation
   143  	_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
   144  	if maxUnavailable > *deployment.Spec.Replicas {
   145  		return *deployment.Spec.Replicas
   146  	}
   147  	return maxUnavailable
   148  }
   149  
   150  // ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
   151  // step. For example:
   152  //
   153  // 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
   154  // 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
   155  // 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
   156  // 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
   157  // 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
   158  // 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
   159  func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
   160  	surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true)
   161  	if err != nil {
   162  		return 0, 0, err
   163  	}
   164  	unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false)
   165  	if err != nil {
   166  		return 0, 0, err
   167  	}
   168  
   169  	if surge == 0 && unavailable == 0 {
   170  		// Validation should never allow the user to explicitly use zero values for both maxSurge
   171  		// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
   172  		// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
   173  		// theory that surge might not work due to quota.
   174  		unavailable = 1
   175  	}
   176  
   177  	return int32(surge), int32(unavailable), nil
   178  }
   179  

View as plain text