@@ -25,11 +25,14 @@ import (
25
25
"k8s.io/kubernetes/pkg/api/unversioned"
26
26
"k8s.io/kubernetes/pkg/apis/extensions"
27
27
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
28
+ unversionedcore "k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned"
29
+ unversionedextensions "k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned"
28
30
"k8s.io/kubernetes/pkg/labels"
29
31
"k8s.io/kubernetes/pkg/util/integer"
30
32
intstrutil "k8s.io/kubernetes/pkg/util/intstr"
31
33
labelsutil "k8s.io/kubernetes/pkg/util/labels"
32
34
podutil "k8s.io/kubernetes/pkg/util/pod"
35
+ "k8s.io/kubernetes/pkg/util/wait"
33
36
)
34
37
35
38
const (
@@ -55,28 +58,21 @@ func GetOldReplicaSets(deployment extensions.Deployment, c clientset.Interface)
55
58
})
56
59
}
57
60
61
+ // TODO: switch this to full namespacers
62
+ type rsListFunc func (string , api.ListOptions ) ([]extensions.ReplicaSet , error )
63
+ type podListFunc func (string , api.ListOptions ) (* api.PodList , error )
64
+
58
65
// GetOldReplicaSetsFromLists returns two sets of old replica sets targeted by the given Deployment; get PodList and ReplicaSetList with input functions.
59
66
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
60
- func GetOldReplicaSetsFromLists (deployment extensions.Deployment , c clientset.Interface , getPodList func (string , api.ListOptions ) (* api.PodList , error ), getRSList func (string , api.ListOptions ) ([]extensions.ReplicaSet , error )) ([]* extensions.ReplicaSet , []* extensions.ReplicaSet , error ) {
61
- namespace := deployment .ObjectMeta .Namespace
62
- selector , err := unversioned .LabelSelectorAsSelector (deployment .Spec .Selector )
63
- if err != nil {
64
- return nil , nil , fmt .Errorf ("invalid label selector: %v" , err )
65
- }
66
-
67
- // 1. Find all pods whose labels match deployment.Spec.Selector
68
- options := api.ListOptions {LabelSelector : selector }
69
- podList , err := getPodList (namespace , options )
70
- if err != nil {
71
- return nil , nil , fmt .Errorf ("error listing pods: %v" , err )
72
- }
73
- // 2. Find the corresponding replica sets for pods in podList.
67
+ func GetOldReplicaSetsFromLists (deployment extensions.Deployment , c clientset.Interface , getPodList podListFunc , getRSList rsListFunc ) ([]* extensions.ReplicaSet , []* extensions.ReplicaSet , error ) {
68
+ // Find all pods whose labels match deployment.Spec.Selector, and corresponding replica sets for pods in podList.
69
+ // All pods and replica sets are labeled with pod-template-hash to prevent overlapping
74
70
// TODO: Right now we list all replica sets and then filter. We should add an API for this.
75
71
oldRSs := map [string ]extensions.ReplicaSet {}
76
72
allOldRSs := map [string ]extensions.ReplicaSet {}
77
- rsList , err := getRSList ( namespace , options )
73
+ rsList , podList , err := rsAndPodsWithHashKeySynced ( deployment , c , getRSList , getPodList )
78
74
if err != nil {
79
- return nil , nil , fmt .Errorf ("error listing replica sets: %v" , err )
75
+ return nil , nil , fmt .Errorf ("error labeling replica sets and pods with pod-template-hash : %v" , err )
80
76
}
81
77
newRSTemplate := GetNewReplicaSetTemplate (deployment )
82
78
for _ , pod := range podList .Items {
@@ -113,6 +109,9 @@ func GetOldReplicaSetsFromLists(deployment extensions.Deployment, c clientset.In
113
109
// Returns nil if the new replica set doesn't exist yet.
114
110
func GetNewReplicaSet (deployment extensions.Deployment , c clientset.Interface ) (* extensions.ReplicaSet , error ) {
115
111
return GetNewReplicaSetFromList (deployment , c ,
112
+ func (namespace string , options api.ListOptions ) (* api.PodList , error ) {
113
+ return c .Core ().Pods (namespace ).List (options )
114
+ },
116
115
func (namespace string , options api.ListOptions ) ([]extensions.ReplicaSet , error ) {
117
116
rsList , err := c .Extensions ().ReplicaSets (namespace ).List (options )
118
117
return rsList .Items , err
@@ -121,14 +120,8 @@ func GetNewReplicaSet(deployment extensions.Deployment, c clientset.Interface) (
121
120
122
121
// GetNewReplicaSetFromList returns a replica set that matches the intent of the given deployment; get ReplicaSetList with the input function.
123
122
// Returns nil if the new replica set doesn't exist yet.
124
- func GetNewReplicaSetFromList (deployment extensions.Deployment , c clientset.Interface , getRSList func (string , api.ListOptions ) ([]extensions.ReplicaSet , error )) (* extensions.ReplicaSet , error ) {
125
- namespace := deployment .ObjectMeta .Namespace
126
- selector , err := unversioned .LabelSelectorAsSelector (deployment .Spec .Selector )
127
- if err != nil {
128
- return nil , fmt .Errorf ("invalid label selector: %v" , err )
129
- }
130
-
131
- rsList , err := getRSList (namespace , api.ListOptions {LabelSelector : selector })
123
+ func GetNewReplicaSetFromList (deployment extensions.Deployment , c clientset.Interface , getPodList podListFunc , getRSList rsListFunc ) (* extensions.ReplicaSet , error ) {
124
+ rsList , _ , err := rsAndPodsWithHashKeySynced (deployment , c , getRSList , getPodList )
132
125
if err != nil {
133
126
return nil , fmt .Errorf ("error listing ReplicaSets: %v" , err )
134
127
}
@@ -144,6 +137,166 @@ func GetNewReplicaSetFromList(deployment extensions.Deployment, c clientset.Inte
144
137
return nil , nil
145
138
}
146
139
140
+ // rsAndPodsWithHashKeySynced returns the RSs and pods the given deployment targets, with pod-template-hash information synced.
141
+ func rsAndPodsWithHashKeySynced (deployment extensions.Deployment , c clientset.Interface , getRSList rsListFunc , getPodList podListFunc ) ([]extensions.ReplicaSet , * api.PodList , error ) {
142
+ namespace := deployment .Namespace
143
+ selector , err := unversioned .LabelSelectorAsSelector (deployment .Spec .Selector )
144
+ if err != nil {
145
+ return nil , nil , err
146
+ }
147
+ options := api.ListOptions {LabelSelector : selector }
148
+ rsList , err := getRSList (namespace , options )
149
+ if err != nil {
150
+ return nil , nil , err
151
+ }
152
+ syncedRSList := []extensions.ReplicaSet {}
153
+ for _ , rs := range rsList {
154
+ // Add pod-template-hash information if it's not in the RS.
155
+ // Otherwise, new RS produced by Deployment will overlap with pre-existing ones
156
+ // that aren't constrained by the pod-template-hash.
157
+ syncedRS , err := addHashKeyToRSAndPods (deployment , c , rs , getPodList )
158
+ if err != nil {
159
+ return nil , nil , err
160
+ }
161
+ syncedRSList = append (syncedRSList , * syncedRS )
162
+ }
163
+ syncedPodList , err := getPodList (namespace , options )
164
+ if err != nil {
165
+ return nil , nil , err
166
+ }
167
+ return syncedRSList , syncedPodList , nil
168
+ }
169
+
170
+ // addHashKeyToRSAndPods adds pod-template-hash information to the given rs, if it's not already there, with the following steps:
171
+ // 1. Add hash label to the rs's pod template, and make sure the controller sees this update so that no orphaned pods will be created
172
+ // 2. Add hash label to all pods this rs owns
173
+ // 3. Add hash label to the rs's label and selector
174
+ func addHashKeyToRSAndPods (deployment extensions.Deployment , c clientset.Interface , rs extensions.ReplicaSet , getPodList podListFunc ) (updatedRS * extensions.ReplicaSet , err error ) {
175
+ // If the rs already has the new hash label in its selector, it's done syncing
176
+ namespace := deployment .Namespace
177
+ hash := fmt .Sprintf ("%d" , podutil .GetPodTemplateSpecHash (api.PodTemplateSpec {
178
+ ObjectMeta : rs .Spec .Template .ObjectMeta ,
179
+ Spec : rs .Spec .Template .Spec ,
180
+ }))
181
+ if labelsutil .SelectorHasLabel (rs .Spec .Selector , extensions .DefaultDeploymentUniqueLabelKey ) {
182
+ return & rs , nil
183
+ }
184
+ // 1. Add hash template label to the rs. This ensures that any newly created pods will have the new label.
185
+ if len (rs .Spec .Template .Labels [extensions .DefaultDeploymentUniqueLabelKey ]) == 0 {
186
+ updatedRS , err = updateRSWithRetries (c .Extensions ().ReplicaSets (namespace ), & rs , func (updated * extensions.ReplicaSet ) {
187
+ updated .Spec .Template .Labels = labelsutil .AddLabel (updated .Spec .Template .Labels , extensions .DefaultDeploymentUniqueLabelKey , hash )
188
+ })
189
+ if err != nil {
190
+ return nil , err
191
+ }
192
+ }
193
+ // Make sure rs pod template is updated so that it won't create pods without the new label (orphaned pods).
194
+ if updatedRS .Generation > updatedRS .Status .ObservedGeneration {
195
+ if err = waitForReplicaSetUpdated (c , updatedRS .Generation , namespace , rs .Name ); err != nil {
196
+ return nil , err
197
+ }
198
+ }
199
+
200
+ // 2. Update all pods managed by the rs to have the new hash label, so they will be correctly adopted.
201
+ selector , err := unversioned .LabelSelectorAsSelector (rs .Spec .Selector )
202
+ if err != nil {
203
+ return nil , err
204
+ }
205
+ options := api.ListOptions {LabelSelector : selector }
206
+ podList , err := getPodList (namespace , options )
207
+ if err != nil {
208
+ return nil , err
209
+ }
210
+ if err = labelPodsWithHash (podList , c , namespace , hash ); err != nil {
211
+ return nil , err
212
+ }
213
+
214
+ // 3. Update rs label and selector to include the new hash label
215
+ // Copy the old selector, so that we can scrub out any orphaned pods
216
+ if updatedRS , err = updateRSWithRetries (c .Extensions ().ReplicaSets (namespace ), & rs , func (updated * extensions.ReplicaSet ) {
217
+ updated .Labels = labelsutil .AddLabel (updated .Labels , extensions .DefaultDeploymentUniqueLabelKey , hash )
218
+ updated .Spec .Selector = labelsutil .AddLabelToSelector (updated .Spec .Selector , extensions .DefaultDeploymentUniqueLabelKey , hash )
219
+ }); err != nil {
220
+ return nil , err
221
+ }
222
+
223
+ // TODO: look for orphaned pods and label them in the background somewhere else periodically
224
+
225
+ return updatedRS , nil
226
+ }
227
+
228
+ func waitForReplicaSetUpdated (c clientset.Interface , desiredGeneration int64 , namespace , name string ) error {
229
+ return wait .Poll (10 * time .Millisecond , 1 * time .Minute , func () (bool , error ) {
230
+ rs , err := c .Extensions ().ReplicaSets (namespace ).Get (name )
231
+ if err != nil {
232
+ return false , err
233
+ }
234
+ return rs .Status .ObservedGeneration >= desiredGeneration , nil
235
+ })
236
+ }
237
+
238
+ // labelPodsWithHash labels all pods in the given podList with the new hash label.
239
+ func labelPodsWithHash (podList * api.PodList , c clientset.Interface , namespace , hash string ) error {
240
+ for _ , pod := range podList .Items {
241
+ // Only label the pod that doesn't already have the new hash
242
+ if pod .Labels [extensions .DefaultDeploymentUniqueLabelKey ] != hash {
243
+ if _ , err := updatePodWithRetries (c .Core ().Pods (namespace ), & pod , func (updated * api.Pod ) {
244
+ pod .Labels = labelsutil .AddLabel (pod .Labels , extensions .DefaultDeploymentUniqueLabelKey , hash )
245
+ }); err != nil {
246
+ return err
247
+ }
248
+ }
249
+ }
250
+ return nil
251
+ }
252
+
253
+ // TODO: use client library instead when it starts to support update retries
254
+ // see https://github.com/kubernetes/kubernetes/issues/21479
255
+ type updateRSFunc func (rs * extensions.ReplicaSet )
256
+
257
+ func updateRSWithRetries (rsClient unversionedextensions.ReplicaSetInterface , rs * extensions.ReplicaSet , applyUpdate updateRSFunc ) (* extensions.ReplicaSet , error ) {
258
+ var err error
259
+ oldRs := rs
260
+ err = wait .Poll (10 * time .Millisecond , 1 * time .Minute , func () (bool , error ) {
261
+ // Apply the update, then attempt to push it to the apiserver.
262
+ applyUpdate (rs )
263
+ if rs , err = rsClient .Update (rs ); err == nil {
264
+ // rs contains the latest controller post update
265
+ return true , nil
266
+ }
267
+ // Update the controller with the latest resource version, if the update failed we
268
+ // can't trust rs so use oldRs.Name.
269
+ if rs , err = rsClient .Get (oldRs .Name ); err != nil {
270
+ // The Get failed: Value in rs cannot be trusted.
271
+ rs = oldRs
272
+ }
273
+ // The Get passed: rs contains the latest controller, expect a poll for the update.
274
+ return false , nil
275
+ })
276
+ // If the error is non-nil the returned controller cannot be trusted, if it is nil, the returned
277
+ // controller contains the applied update.
278
+ return rs , err
279
+ }
280
+
281
+ type updatePodFunc func (pod * api.Pod )
282
+
283
+ func updatePodWithRetries (podClient unversionedcore.PodInterface , pod * api.Pod , applyUpdate updatePodFunc ) (* api.Pod , error ) {
284
+ var err error
285
+ oldPod := pod
286
+ err = wait .Poll (10 * time .Millisecond , 1 * time .Minute , func () (bool , error ) {
287
+ // Apply the update, then attempt to push it to the apiserver.
288
+ applyUpdate (pod )
289
+ if pod , err = podClient .Update (pod ); err == nil {
290
+ return true , nil
291
+ }
292
+ if pod , err = podClient .Get (oldPod .Name ); err != nil {
293
+ pod = oldPod
294
+ }
295
+ return false , nil
296
+ })
297
+ return pod , err
298
+ }
299
+
147
300
// Returns the desired PodTemplateSpec for the new ReplicaSet corresponding to the given ReplicaSet.
148
301
func GetNewReplicaSetTemplate (deployment extensions.Deployment ) api.PodTemplateSpec {
149
302
// newRS will have the same template as in deployment spec, plus a unique label in some cases.
0 commit comments