sync_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package deployment
  14. import (
  15. "math"
  16. "testing"
  17. "time"
  18. apps "k8s.io/api/apps/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/util/intstr"
  21. "k8s.io/client-go/informers"
  22. "k8s.io/client-go/kubernetes/fake"
  23. testclient "k8s.io/client-go/testing"
  24. "k8s.io/client-go/tools/record"
  25. "k8s.io/kubernetes/pkg/controller"
  26. deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
  27. )
  28. func intOrStrP(val int) *intstr.IntOrString {
  29. intOrStr := intstr.FromInt(val)
  30. return &intOrStr
  31. }
  32. func TestScale(t *testing.T) {
  33. newTimestamp := metav1.Date(2016, 5, 20, 2, 0, 0, 0, time.UTC)
  34. oldTimestamp := metav1.Date(2016, 5, 20, 1, 0, 0, 0, time.UTC)
  35. olderTimestamp := metav1.Date(2016, 5, 20, 0, 0, 0, 0, time.UTC)
  36. var updatedTemplate = func(replicas int) *apps.Deployment {
  37. d := newDeployment("foo", replicas, nil, nil, nil, map[string]string{"foo": "bar"})
  38. d.Spec.Template.Labels["another"] = "label"
  39. return d
  40. }
  41. tests := []struct {
  42. name string
  43. deployment *apps.Deployment
  44. oldDeployment *apps.Deployment
  45. newRS *apps.ReplicaSet
  46. oldRSs []*apps.ReplicaSet
  47. expectedNew *apps.ReplicaSet
  48. expectedOld []*apps.ReplicaSet
  49. wasntUpdated map[string]bool
  50. desiredReplicasAnnotations map[string]int32
  51. }{
  52. {
  53. name: "normal scaling event: 10 -> 12",
  54. deployment: newDeployment("foo", 12, nil, nil, nil, nil),
  55. oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
  56. newRS: rs("foo-v1", 10, nil, newTimestamp),
  57. oldRSs: []*apps.ReplicaSet{},
  58. expectedNew: rs("foo-v1", 12, nil, newTimestamp),
  59. expectedOld: []*apps.ReplicaSet{},
  60. },
  61. {
  62. name: "normal scaling event: 10 -> 5",
  63. deployment: newDeployment("foo", 5, nil, nil, nil, nil),
  64. oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
  65. newRS: rs("foo-v1", 10, nil, newTimestamp),
  66. oldRSs: []*apps.ReplicaSet{},
  67. expectedNew: rs("foo-v1", 5, nil, newTimestamp),
  68. expectedOld: []*apps.ReplicaSet{},
  69. },
  70. {
  71. name: "proportional scaling: 5 -> 10",
  72. deployment: newDeployment("foo", 10, nil, nil, nil, nil),
  73. oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
  74. newRS: rs("foo-v2", 2, nil, newTimestamp),
  75. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
  76. expectedNew: rs("foo-v2", 4, nil, newTimestamp),
  77. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
  78. },
  79. {
  80. name: "proportional scaling: 5 -> 3",
  81. deployment: newDeployment("foo", 3, nil, nil, nil, nil),
  82. oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
  83. newRS: rs("foo-v2", 2, nil, newTimestamp),
  84. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
  85. expectedNew: rs("foo-v2", 1, nil, newTimestamp),
  86. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 2, nil, oldTimestamp)},
  87. },
  88. {
  89. name: "proportional scaling: 9 -> 4",
  90. deployment: newDeployment("foo", 4, nil, nil, nil, nil),
  91. oldDeployment: newDeployment("foo", 9, nil, nil, nil, nil),
  92. newRS: rs("foo-v2", 8, nil, newTimestamp),
  93. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
  94. expectedNew: rs("foo-v2", 4, nil, newTimestamp),
  95. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 0, nil, oldTimestamp)},
  96. },
  97. {
  98. name: "proportional scaling: 7 -> 10",
  99. deployment: newDeployment("foo", 10, nil, nil, nil, nil),
  100. oldDeployment: newDeployment("foo", 7, nil, nil, nil, nil),
  101. newRS: rs("foo-v3", 2, nil, newTimestamp),
  102. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
  103. expectedNew: rs("foo-v3", 3, nil, newTimestamp),
  104. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 4, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
  105. },
  106. {
  107. name: "proportional scaling: 13 -> 8",
  108. deployment: newDeployment("foo", 8, nil, nil, nil, nil),
  109. oldDeployment: newDeployment("foo", 13, nil, nil, nil, nil),
  110. newRS: rs("foo-v3", 2, nil, newTimestamp),
  111. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 8, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
  112. expectedNew: rs("foo-v3", 1, nil, newTimestamp),
  113. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 5, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
  114. },
  115. // Scales up the new replica set.
  116. {
  117. name: "leftover distribution: 3 -> 4",
  118. deployment: newDeployment("foo", 4, nil, nil, nil, nil),
  119. oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
  120. newRS: rs("foo-v3", 1, nil, newTimestamp),
  121. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  122. expectedNew: rs("foo-v3", 2, nil, newTimestamp),
  123. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  124. },
  125. // Scales down the older replica set.
  126. {
  127. name: "leftover distribution: 3 -> 2",
  128. deployment: newDeployment("foo", 2, nil, nil, nil, nil),
  129. oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
  130. newRS: rs("foo-v3", 1, nil, newTimestamp),
  131. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  132. expectedNew: rs("foo-v3", 1, nil, newTimestamp),
  133. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
  134. },
  135. // Scales up the latest replica set first.
  136. {
  137. name: "proportional scaling (no new rs): 4 -> 5",
  138. deployment: newDeployment("foo", 5, nil, nil, nil, nil),
  139. oldDeployment: newDeployment("foo", 4, nil, nil, nil, nil),
  140. newRS: nil,
  141. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
  142. expectedNew: nil,
  143. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
  144. },
  145. // Scales down to zero
  146. {
  147. name: "proportional scaling: 6 -> 0",
  148. deployment: newDeployment("foo", 0, nil, nil, nil, nil),
  149. oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
  150. newRS: rs("foo-v3", 3, nil, newTimestamp),
  151. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  152. expectedNew: rs("foo-v3", 0, nil, newTimestamp),
  153. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
  154. },
  155. // Scales up from zero
  156. {
  157. name: "proportional scaling: 0 -> 6",
  158. deployment: newDeployment("foo", 6, nil, nil, nil, nil),
  159. oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
  160. newRS: rs("foo-v3", 0, nil, newTimestamp),
  161. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
  162. expectedNew: rs("foo-v3", 6, nil, newTimestamp),
  163. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
  164. wasntUpdated: map[string]bool{"foo-v2": true, "foo-v1": true},
  165. },
  166. // Scenario: deployment.spec.replicas == 3 ( foo-v1.spec.replicas == foo-v2.spec.replicas == foo-v3.spec.replicas == 1 )
  167. // Deployment is scaled to 5. foo-v3.spec.replicas and foo-v2.spec.replicas should increment by 1 but foo-v2 fails to
  168. // update.
  169. {
  170. name: "failed rs update",
  171. deployment: newDeployment("foo", 5, nil, nil, nil, nil),
  172. oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
  173. newRS: rs("foo-v3", 2, nil, newTimestamp),
  174. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  175. expectedNew: rs("foo-v3", 2, nil, newTimestamp),
  176. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
  177. wasntUpdated: map[string]bool{"foo-v3": true, "foo-v1": true},
  178. desiredReplicasAnnotations: map[string]int32{"foo-v2": int32(3)},
  179. },
  180. {
  181. name: "deployment with surge pods",
  182. deployment: newDeployment("foo", 20, nil, intOrStrP(2), nil, nil),
  183. oldDeployment: newDeployment("foo", 10, nil, intOrStrP(2), nil, nil),
  184. newRS: rs("foo-v2", 6, nil, newTimestamp),
  185. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
  186. expectedNew: rs("foo-v2", 11, nil, newTimestamp),
  187. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 11, nil, oldTimestamp)},
  188. },
  189. {
  190. name: "change both surge and size",
  191. deployment: newDeployment("foo", 50, nil, intOrStrP(6), nil, nil),
  192. oldDeployment: newDeployment("foo", 10, nil, intOrStrP(3), nil, nil),
  193. newRS: rs("foo-v2", 5, nil, newTimestamp),
  194. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 8, nil, oldTimestamp)},
  195. expectedNew: rs("foo-v2", 22, nil, newTimestamp),
  196. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 34, nil, oldTimestamp)},
  197. },
  198. {
  199. name: "change both size and template",
  200. deployment: updatedTemplate(14),
  201. oldDeployment: newDeployment("foo", 10, nil, nil, nil, map[string]string{"foo": "bar"}),
  202. newRS: nil,
  203. oldRSs: []*apps.ReplicaSet{rs("foo-v2", 7, nil, newTimestamp), rs("foo-v1", 3, nil, oldTimestamp)},
  204. expectedNew: nil,
  205. expectedOld: []*apps.ReplicaSet{rs("foo-v2", 10, nil, newTimestamp), rs("foo-v1", 4, nil, oldTimestamp)},
  206. },
  207. {
  208. name: "saturated but broken new replica set does not affect old pods",
  209. deployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
  210. oldDeployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
  211. newRS: func() *apps.ReplicaSet {
  212. rs := rs("foo-v2", 2, nil, newTimestamp)
  213. rs.Status.AvailableReplicas = 0
  214. return rs
  215. }(),
  216. oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
  217. expectedNew: rs("foo-v2", 2, nil, newTimestamp),
  218. expectedOld: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
  219. },
  220. }
  221. for _, test := range tests {
  222. t.Run(test.name, func(t *testing.T) {
  223. _ = olderTimestamp
  224. t.Log(test.name)
  225. fake := fake.Clientset{}
  226. dc := &DeploymentController{
  227. client: &fake,
  228. eventRecorder: &record.FakeRecorder{},
  229. }
  230. if test.newRS != nil {
  231. desiredReplicas := *(test.oldDeployment.Spec.Replicas)
  232. if desired, ok := test.desiredReplicasAnnotations[test.newRS.Name]; ok {
  233. desiredReplicas = desired
  234. }
  235. deploymentutil.SetReplicasAnnotations(test.newRS, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
  236. }
  237. for i := range test.oldRSs {
  238. rs := test.oldRSs[i]
  239. if rs == nil {
  240. continue
  241. }
  242. desiredReplicas := *(test.oldDeployment.Spec.Replicas)
  243. if desired, ok := test.desiredReplicasAnnotations[rs.Name]; ok {
  244. desiredReplicas = desired
  245. }
  246. deploymentutil.SetReplicasAnnotations(rs, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
  247. }
  248. if err := dc.scale(test.deployment, test.newRS, test.oldRSs); err != nil {
  249. t.Errorf("%s: unexpected error: %v", test.name, err)
  250. return
  251. }
  252. // Construct the nameToSize map that will hold all the sizes we got our of tests
  253. // Skip updating the map if the replica set wasn't updated since there will be
  254. // no update action for it.
  255. nameToSize := make(map[string]int32)
  256. if test.newRS != nil {
  257. nameToSize[test.newRS.Name] = *(test.newRS.Spec.Replicas)
  258. }
  259. for i := range test.oldRSs {
  260. rs := test.oldRSs[i]
  261. nameToSize[rs.Name] = *(rs.Spec.Replicas)
  262. }
  263. // Get all the UPDATE actions and update nameToSize with all the updated sizes.
  264. for _, action := range fake.Actions() {
  265. rs := action.(testclient.UpdateAction).GetObject().(*apps.ReplicaSet)
  266. if !test.wasntUpdated[rs.Name] {
  267. nameToSize[rs.Name] = *(rs.Spec.Replicas)
  268. }
  269. }
  270. if test.expectedNew != nil && test.newRS != nil && *(test.expectedNew.Spec.Replicas) != nameToSize[test.newRS.Name] {
  271. t.Errorf("%s: expected new replicas: %d, got: %d", test.name, *(test.expectedNew.Spec.Replicas), nameToSize[test.newRS.Name])
  272. return
  273. }
  274. if len(test.expectedOld) != len(test.oldRSs) {
  275. t.Errorf("%s: expected %d old replica sets, got %d", test.name, len(test.expectedOld), len(test.oldRSs))
  276. return
  277. }
  278. for n := range test.oldRSs {
  279. rs := test.oldRSs[n]
  280. expected := test.expectedOld[n]
  281. if *(expected.Spec.Replicas) != nameToSize[rs.Name] {
  282. t.Errorf("%s: expected old (%s) replicas: %d, got: %d", test.name, rs.Name, *(expected.Spec.Replicas), nameToSize[rs.Name])
  283. }
  284. }
  285. })
  286. }
  287. }
  288. func TestDeploymentController_cleanupDeployment(t *testing.T) {
  289. selector := map[string]string{"foo": "bar"}
  290. alreadyDeleted := newRSWithStatus("foo-1", 0, 0, selector)
  291. now := metav1.Now()
  292. alreadyDeleted.DeletionTimestamp = &now
  293. tests := []struct {
  294. oldRSs []*apps.ReplicaSet
  295. revisionHistoryLimit int32
  296. expectedDeletions int
  297. }{
  298. {
  299. oldRSs: []*apps.ReplicaSet{
  300. newRSWithStatus("foo-1", 0, 0, selector),
  301. newRSWithStatus("foo-2", 0, 0, selector),
  302. newRSWithStatus("foo-3", 0, 0, selector),
  303. },
  304. revisionHistoryLimit: 1,
  305. expectedDeletions: 2,
  306. },
  307. {
  308. // Only delete the replica set with Spec.Replicas = Status.Replicas = 0.
  309. oldRSs: []*apps.ReplicaSet{
  310. newRSWithStatus("foo-1", 0, 0, selector),
  311. newRSWithStatus("foo-2", 0, 1, selector),
  312. newRSWithStatus("foo-3", 1, 0, selector),
  313. newRSWithStatus("foo-4", 1, 1, selector),
  314. },
  315. revisionHistoryLimit: 0,
  316. expectedDeletions: 1,
  317. },
  318. {
  319. oldRSs: []*apps.ReplicaSet{
  320. newRSWithStatus("foo-1", 0, 0, selector),
  321. newRSWithStatus("foo-2", 0, 0, selector),
  322. },
  323. revisionHistoryLimit: 0,
  324. expectedDeletions: 2,
  325. },
  326. {
  327. oldRSs: []*apps.ReplicaSet{
  328. newRSWithStatus("foo-1", 1, 1, selector),
  329. newRSWithStatus("foo-2", 1, 1, selector),
  330. },
  331. revisionHistoryLimit: 0,
  332. expectedDeletions: 0,
  333. },
  334. {
  335. oldRSs: []*apps.ReplicaSet{
  336. alreadyDeleted,
  337. },
  338. revisionHistoryLimit: 0,
  339. expectedDeletions: 0,
  340. },
  341. {
  342. // with unlimited revisionHistoryLimit
  343. oldRSs: []*apps.ReplicaSet{
  344. newRSWithStatus("foo-1", 0, 0, selector),
  345. newRSWithStatus("foo-2", 0, 0, selector),
  346. newRSWithStatus("foo-3", 0, 0, selector),
  347. },
  348. revisionHistoryLimit: math.MaxInt32,
  349. expectedDeletions: 0,
  350. },
  351. }
  352. for i := range tests {
  353. test := tests[i]
  354. t.Logf("scenario %d", i)
  355. fake := &fake.Clientset{}
  356. informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc())
  357. controller, err := NewDeploymentController(informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), fake)
  358. if err != nil {
  359. t.Fatalf("error creating Deployment controller: %v", err)
  360. }
  361. controller.eventRecorder = &record.FakeRecorder{}
  362. controller.dListerSynced = alwaysReady
  363. controller.rsListerSynced = alwaysReady
  364. controller.podListerSynced = alwaysReady
  365. for _, rs := range test.oldRSs {
  366. informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
  367. }
  368. stopCh := make(chan struct{})
  369. defer close(stopCh)
  370. informers.Start(stopCh)
  371. t.Logf(" &test.revisionHistoryLimit: %d", test.revisionHistoryLimit)
  372. d := newDeployment("foo", 1, &test.revisionHistoryLimit, nil, nil, map[string]string{"foo": "bar"})
  373. controller.cleanupDeployment(test.oldRSs, d)
  374. gotDeletions := 0
  375. for _, action := range fake.Actions() {
  376. if action.GetVerb() == "delete" {
  377. gotDeletions++
  378. }
  379. }
  380. if gotDeletions != test.expectedDeletions {
  381. t.Errorf("expect %v old replica sets been deleted, but got %v", test.expectedDeletions, gotDeletions)
  382. continue
  383. }
  384. }
  385. }