Update status check endpoint 
[multicloud/k8s.git] / src / k8splugin / internal / utils / deploymentutil.go
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 utils
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 }