mirror of https://github.com/helm/helm
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							179 lines
						
					
					
						
							7.7 KiB
						
					
					
				
			
		
		
	
	
							179 lines
						
					
					
						
							7.7 KiB
						
					
					
				/*
 | 
						|
Copyright 2016 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package util
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	apps "k8s.io/api/apps/v1"
 | 
						|
	v1 "k8s.io/api/core/v1"
 | 
						|
	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
 | 
						|
	appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
						|
)
 | 
						|
 | 
						|
// deploymentutil contains a copy of a few functions from Kubernetes controller code to avoid a dependency on k8s.io/kubernetes.
 | 
						|
// This code is copied from https://github.com/kubernetes/kubernetes/blob/e856613dd5bb00bcfaca6974431151b5c06cbed5/pkg/controller/deployment/util/deployment_util.go
 | 
						|
// No changes to the code were made other than removing some unused functions
 | 
						|
 | 
						|
// RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions.
 | 
						|
type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error)
 | 
						|
 | 
						|
// ListReplicaSets returns a slice of RSes the given deployment targets.
 | 
						|
// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
 | 
						|
// because only the controller itself should do that.
 | 
						|
// However, it does filter out anything whose ControllerRef doesn't match.
 | 
						|
func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) {
 | 
						|
	// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
 | 
						|
	//       should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
 | 
						|
	namespace := deployment.Namespace
 | 
						|
	selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	options := metav1.ListOptions{LabelSelector: selector.String()}
 | 
						|
	all, err := getRSList(namespace, options)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// Only include those whose ControllerRef matches the Deployment.
 | 
						|
	owned := make([]*apps.ReplicaSet, 0, len(all))
 | 
						|
	for _, rs := range all {
 | 
						|
		if metav1.IsControlledBy(rs, deployment) {
 | 
						|
			owned = append(owned, rs)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return owned, nil
 | 
						|
}
 | 
						|
 | 
						|
// ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
 | 
						|
type ReplicaSetsByCreationTimestamp []*apps.ReplicaSet
 | 
						|
 | 
						|
func (o ReplicaSetsByCreationTimestamp) Len() int      { return len(o) }
 | 
						|
func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
 | 
						|
func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool {
 | 
						|
	if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
 | 
						|
		return o[i].Name < o[j].Name
 | 
						|
	}
 | 
						|
	return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
 | 
						|
}
 | 
						|
 | 
						|
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
 | 
						|
func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet {
 | 
						|
	sort.Sort(ReplicaSetsByCreationTimestamp(rsList))
 | 
						|
	for i := range rsList {
 | 
						|
		if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
 | 
						|
			// In rare cases, such as after cluster upgrades, Deployment may end up with
 | 
						|
			// having more than one new ReplicaSets that have the same template as its template,
 | 
						|
			// see https://github.com/kubernetes/kubernetes/issues/40415
 | 
						|
			// We deterministically choose the oldest new ReplicaSet.
 | 
						|
			return rsList[i]
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// new ReplicaSet does not exist.
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
 | 
						|
// We ignore pod-template-hash because:
 | 
						|
//  1. The hash result would be different upon podTemplateSpec API changes
 | 
						|
//     (e.g. the addition of a new field will cause the hash code to change)
 | 
						|
//  2. The deployment template won't have hash labels
 | 
						|
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
 | 
						|
	t1Copy := template1.DeepCopy()
 | 
						|
	t2Copy := template2.DeepCopy()
 | 
						|
	// Remove hash labels from template.Labels before comparing
 | 
						|
	delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
 | 
						|
	delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
 | 
						|
	return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
 | 
						|
}
 | 
						|
 | 
						|
// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
 | 
						|
// Returns nil if the new replica set doesn't exist yet.
 | 
						|
func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) {
 | 
						|
	rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return FindNewReplicaSet(deployment, rsList), nil
 | 
						|
}
 | 
						|
 | 
						|
// RsListFromClient returns an rsListFunc that wraps the given client.
 | 
						|
func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc {
 | 
						|
	return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) {
 | 
						|
		rsList, err := c.ReplicaSets(namespace).List(context.Background(), options)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		var ret []*apps.ReplicaSet
 | 
						|
		for i := range rsList.Items {
 | 
						|
			ret = append(ret, &rsList.Items[i])
 | 
						|
		}
 | 
						|
		return ret, err
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// IsRollingUpdate returns true if the strategy type is a rolling update.
 | 
						|
func IsRollingUpdate(deployment *apps.Deployment) bool {
 | 
						|
	return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType
 | 
						|
}
 | 
						|
 | 
						|
// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
 | 
						|
func MaxUnavailable(deployment apps.Deployment) int32 {
 | 
						|
	if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
 | 
						|
		return int32(0)
 | 
						|
	}
 | 
						|
	// Error caught by validation
 | 
						|
	_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
 | 
						|
	if maxUnavailable > *deployment.Spec.Replicas {
 | 
						|
		return *deployment.Spec.Replicas
 | 
						|
	}
 | 
						|
	return maxUnavailable
 | 
						|
}
 | 
						|
 | 
						|
// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
 | 
						|
// step. For example:
 | 
						|
//
 | 
						|
// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
 | 
						|
// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
 | 
						|
// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
 | 
						|
// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
 | 
						|
// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
 | 
						|
// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
 | 
						|
func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
 | 
						|
	surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true)
 | 
						|
	if err != nil {
 | 
						|
		return 0, 0, err
 | 
						|
	}
 | 
						|
	unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false)
 | 
						|
	if err != nil {
 | 
						|
		return 0, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	if surge == 0 && unavailable == 0 {
 | 
						|
		// Validation should never allow the user to explicitly use zero values for both maxSurge
 | 
						|
		// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
 | 
						|
		// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
 | 
						|
		// theory that surge might not work due to quota.
 | 
						|
		unavailable = 1
 | 
						|
	}
 | 
						|
 | 
						|
	return int32(surge), int32(unavailable), nil
 | 
						|
}
 |