rolling_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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. "fmt"
  16. "testing"
  17. "k8s.io/kubernetes/pkg/api"
  18. exp "k8s.io/kubernetes/pkg/apis/extensions"
  19. "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
  20. "k8s.io/kubernetes/pkg/client/record"
  21. "k8s.io/kubernetes/pkg/client/testing/core"
  22. "k8s.io/kubernetes/pkg/runtime"
  23. "k8s.io/kubernetes/pkg/util/intstr"
  24. )
  25. func TestDeploymentController_reconcileNewReplicaSet(t *testing.T) {
  26. tests := []struct {
  27. deploymentReplicas int
  28. maxSurge intstr.IntOrString
  29. oldReplicas int
  30. newReplicas int
  31. scaleExpected bool
  32. expectedNewReplicas int
  33. }{
  34. {
  35. // Should not scale up.
  36. deploymentReplicas: 10,
  37. maxSurge: intstr.FromInt(0),
  38. oldReplicas: 10,
  39. newReplicas: 0,
  40. scaleExpected: false,
  41. },
  42. {
  43. deploymentReplicas: 10,
  44. maxSurge: intstr.FromInt(2),
  45. oldReplicas: 10,
  46. newReplicas: 0,
  47. scaleExpected: true,
  48. expectedNewReplicas: 2,
  49. },
  50. {
  51. deploymentReplicas: 10,
  52. maxSurge: intstr.FromInt(2),
  53. oldReplicas: 5,
  54. newReplicas: 0,
  55. scaleExpected: true,
  56. expectedNewReplicas: 7,
  57. },
  58. {
  59. deploymentReplicas: 10,
  60. maxSurge: intstr.FromInt(2),
  61. oldReplicas: 10,
  62. newReplicas: 2,
  63. scaleExpected: false,
  64. },
  65. {
  66. // Should scale down.
  67. deploymentReplicas: 10,
  68. maxSurge: intstr.FromInt(2),
  69. oldReplicas: 2,
  70. newReplicas: 11,
  71. scaleExpected: true,
  72. expectedNewReplicas: 10,
  73. },
  74. }
  75. for i, test := range tests {
  76. t.Logf("executing scenario %d", i)
  77. newRS := rs("foo-v2", test.newReplicas, nil, noTimestamp)
  78. oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
  79. allRSs := []*exp.ReplicaSet{newRS, oldRS}
  80. deployment := deployment("foo", test.deploymentReplicas, test.maxSurge, intstr.FromInt(0), nil)
  81. fake := fake.Clientset{}
  82. controller := &DeploymentController{
  83. client: &fake,
  84. eventRecorder: &record.FakeRecorder{},
  85. }
  86. scaled, err := controller.reconcileNewReplicaSet(allRSs, newRS, &deployment)
  87. if err != nil {
  88. t.Errorf("unexpected error: %v", err)
  89. continue
  90. }
  91. if !test.scaleExpected {
  92. if scaled || len(fake.Actions()) > 0 {
  93. t.Errorf("unexpected scaling: %v", fake.Actions())
  94. }
  95. continue
  96. }
  97. if test.scaleExpected && !scaled {
  98. t.Errorf("expected scaling to occur")
  99. continue
  100. }
  101. if len(fake.Actions()) != 1 {
  102. t.Errorf("expected 1 action during scale, got: %v", fake.Actions())
  103. continue
  104. }
  105. updated := fake.Actions()[0].(core.UpdateAction).GetObject().(*exp.ReplicaSet)
  106. if e, a := test.expectedNewReplicas, int(updated.Spec.Replicas); e != a {
  107. t.Errorf("expected update to %d replicas, got %d", e, a)
  108. }
  109. }
  110. }
  111. func TestDeploymentController_reconcileOldReplicaSets(t *testing.T) {
  112. tests := []struct {
  113. deploymentReplicas int
  114. maxUnavailable intstr.IntOrString
  115. oldReplicas int
  116. newReplicas int
  117. readyPodsFromOldRS int
  118. readyPodsFromNewRS int
  119. scaleExpected bool
  120. expectedOldReplicas int
  121. }{
  122. {
  123. deploymentReplicas: 10,
  124. maxUnavailable: intstr.FromInt(0),
  125. oldReplicas: 10,
  126. newReplicas: 0,
  127. readyPodsFromOldRS: 10,
  128. readyPodsFromNewRS: 0,
  129. scaleExpected: true,
  130. expectedOldReplicas: 9,
  131. },
  132. {
  133. deploymentReplicas: 10,
  134. maxUnavailable: intstr.FromInt(2),
  135. oldReplicas: 10,
  136. newReplicas: 0,
  137. readyPodsFromOldRS: 10,
  138. readyPodsFromNewRS: 0,
  139. scaleExpected: true,
  140. expectedOldReplicas: 8,
  141. },
  142. { // expect unhealthy replicas from old replica sets been cleaned up
  143. deploymentReplicas: 10,
  144. maxUnavailable: intstr.FromInt(2),
  145. oldReplicas: 10,
  146. newReplicas: 0,
  147. readyPodsFromOldRS: 8,
  148. readyPodsFromNewRS: 0,
  149. scaleExpected: true,
  150. expectedOldReplicas: 8,
  151. },
  152. { // expect 1 unhealthy replica from old replica sets been cleaned up, and 1 ready pod been scaled down
  153. deploymentReplicas: 10,
  154. maxUnavailable: intstr.FromInt(2),
  155. oldReplicas: 10,
  156. newReplicas: 0,
  157. readyPodsFromOldRS: 9,
  158. readyPodsFromNewRS: 0,
  159. scaleExpected: true,
  160. expectedOldReplicas: 8,
  161. },
  162. { // the unavailable pods from the newRS would not make us scale down old RSs in a further step
  163. deploymentReplicas: 10,
  164. maxUnavailable: intstr.FromInt(2),
  165. oldReplicas: 8,
  166. newReplicas: 2,
  167. readyPodsFromOldRS: 8,
  168. readyPodsFromNewRS: 0,
  169. scaleExpected: false,
  170. },
  171. }
  172. for i, test := range tests {
  173. t.Logf("executing scenario %d", i)
  174. newSelector := map[string]string{"foo": "new"}
  175. oldSelector := map[string]string{"foo": "old"}
  176. newRS := rs("foo-new", test.newReplicas, newSelector, noTimestamp)
  177. oldRS := rs("foo-old", test.oldReplicas, oldSelector, noTimestamp)
  178. oldRSs := []*exp.ReplicaSet{oldRS}
  179. allRSs := []*exp.ReplicaSet{oldRS, newRS}
  180. deployment := deployment("foo", test.deploymentReplicas, intstr.FromInt(0), test.maxUnavailable, newSelector)
  181. fakeClientset := fake.Clientset{}
  182. fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  183. switch action.(type) {
  184. case core.ListAction:
  185. podList := &api.PodList{}
  186. for podIndex := 0; podIndex < test.readyPodsFromOldRS; podIndex++ {
  187. podList.Items = append(podList.Items, api.Pod{
  188. ObjectMeta: api.ObjectMeta{
  189. Name: fmt.Sprintf("%s-oldReadyPod-%d", oldRS.Name, podIndex),
  190. Labels: oldSelector,
  191. },
  192. Status: api.PodStatus{
  193. Conditions: []api.PodCondition{
  194. {
  195. Type: api.PodReady,
  196. Status: api.ConditionTrue,
  197. },
  198. },
  199. },
  200. })
  201. }
  202. for podIndex := 0; podIndex < test.oldReplicas-test.readyPodsFromOldRS; podIndex++ {
  203. podList.Items = append(podList.Items, api.Pod{
  204. ObjectMeta: api.ObjectMeta{
  205. Name: fmt.Sprintf("%s-oldUnhealthyPod-%d", oldRS.Name, podIndex),
  206. Labels: oldSelector,
  207. },
  208. Status: api.PodStatus{
  209. Conditions: []api.PodCondition{
  210. {
  211. Type: api.PodReady,
  212. Status: api.ConditionFalse,
  213. },
  214. },
  215. },
  216. })
  217. }
  218. for podIndex := 0; podIndex < test.readyPodsFromNewRS; podIndex++ {
  219. podList.Items = append(podList.Items, api.Pod{
  220. ObjectMeta: api.ObjectMeta{
  221. Name: fmt.Sprintf("%s-newReadyPod-%d", oldRS.Name, podIndex),
  222. Labels: newSelector,
  223. },
  224. Status: api.PodStatus{
  225. Conditions: []api.PodCondition{
  226. {
  227. Type: api.PodReady,
  228. Status: api.ConditionTrue,
  229. },
  230. },
  231. },
  232. })
  233. }
  234. for podIndex := 0; podIndex < test.oldReplicas-test.readyPodsFromOldRS; podIndex++ {
  235. podList.Items = append(podList.Items, api.Pod{
  236. ObjectMeta: api.ObjectMeta{
  237. Name: fmt.Sprintf("%s-newUnhealthyPod-%d", oldRS.Name, podIndex),
  238. Labels: newSelector,
  239. },
  240. Status: api.PodStatus{
  241. Conditions: []api.PodCondition{
  242. {
  243. Type: api.PodReady,
  244. Status: api.ConditionFalse,
  245. },
  246. },
  247. },
  248. })
  249. }
  250. return true, podList, nil
  251. }
  252. return false, nil, nil
  253. })
  254. controller := &DeploymentController{
  255. client: &fakeClientset,
  256. eventRecorder: &record.FakeRecorder{},
  257. }
  258. scaled, err := controller.reconcileOldReplicaSets(allRSs, oldRSs, newRS, &deployment)
  259. if err != nil {
  260. t.Errorf("unexpected error: %v", err)
  261. continue
  262. }
  263. if !test.scaleExpected && scaled {
  264. t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
  265. }
  266. if test.scaleExpected && !scaled {
  267. t.Errorf("expected scaling to occur")
  268. continue
  269. }
  270. continue
  271. }
  272. }
  273. func TestDeploymentController_cleanupUnhealthyReplicas(t *testing.T) {
  274. tests := []struct {
  275. oldReplicas int
  276. readyPods int
  277. unHealthyPods int
  278. maxCleanupCount int
  279. cleanupCountExpected int
  280. }{
  281. {
  282. oldReplicas: 10,
  283. readyPods: 8,
  284. unHealthyPods: 2,
  285. maxCleanupCount: 1,
  286. cleanupCountExpected: 1,
  287. },
  288. {
  289. oldReplicas: 10,
  290. readyPods: 8,
  291. unHealthyPods: 2,
  292. maxCleanupCount: 3,
  293. cleanupCountExpected: 2,
  294. },
  295. {
  296. oldReplicas: 10,
  297. readyPods: 8,
  298. unHealthyPods: 2,
  299. maxCleanupCount: 0,
  300. cleanupCountExpected: 0,
  301. },
  302. {
  303. oldReplicas: 10,
  304. readyPods: 10,
  305. unHealthyPods: 0,
  306. maxCleanupCount: 3,
  307. cleanupCountExpected: 0,
  308. },
  309. }
  310. for i, test := range tests {
  311. t.Logf("executing scenario %d", i)
  312. oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
  313. oldRSs := []*exp.ReplicaSet{oldRS}
  314. deployment := deployment("foo", 10, intstr.FromInt(2), intstr.FromInt(2), nil)
  315. fakeClientset := fake.Clientset{}
  316. fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  317. switch action.(type) {
  318. case core.ListAction:
  319. podList := &api.PodList{}
  320. for podIndex := 0; podIndex < test.readyPods; podIndex++ {
  321. podList.Items = append(podList.Items, api.Pod{
  322. ObjectMeta: api.ObjectMeta{
  323. Name: fmt.Sprintf("%s-readyPod-%d", oldRS.Name, podIndex),
  324. },
  325. Status: api.PodStatus{
  326. Conditions: []api.PodCondition{
  327. {
  328. Type: api.PodReady,
  329. Status: api.ConditionTrue,
  330. },
  331. },
  332. },
  333. })
  334. }
  335. for podIndex := 0; podIndex < test.unHealthyPods; podIndex++ {
  336. podList.Items = append(podList.Items, api.Pod{
  337. ObjectMeta: api.ObjectMeta{
  338. Name: fmt.Sprintf("%s-unHealthyPod-%d", oldRS.Name, podIndex),
  339. },
  340. Status: api.PodStatus{
  341. Conditions: []api.PodCondition{
  342. {
  343. Type: api.PodReady,
  344. Status: api.ConditionFalse,
  345. },
  346. },
  347. },
  348. })
  349. }
  350. return true, podList, nil
  351. }
  352. return false, nil, nil
  353. })
  354. controller := &DeploymentController{
  355. client: &fakeClientset,
  356. eventRecorder: &record.FakeRecorder{},
  357. }
  358. _, cleanupCount, err := controller.cleanupUnhealthyReplicas(oldRSs, &deployment, 0, int32(test.maxCleanupCount))
  359. if err != nil {
  360. t.Errorf("unexpected error: %v", err)
  361. continue
  362. }
  363. if int(cleanupCount) != test.cleanupCountExpected {
  364. t.Errorf("expected %v unhealthy replicas been cleaned up, got %v", test.cleanupCountExpected, cleanupCount)
  365. continue
  366. }
  367. }
  368. }
  369. func TestDeploymentController_scaleDownOldReplicaSetsForRollingUpdate(t *testing.T) {
  370. tests := []struct {
  371. deploymentReplicas int
  372. maxUnavailable intstr.IntOrString
  373. readyPods int
  374. oldReplicas int
  375. scaleExpected bool
  376. expectedOldReplicas int
  377. errorExpected bool
  378. }{
  379. {
  380. deploymentReplicas: 10,
  381. maxUnavailable: intstr.FromInt(0),
  382. readyPods: 10,
  383. oldReplicas: 10,
  384. scaleExpected: true,
  385. expectedOldReplicas: 9,
  386. errorExpected: false,
  387. },
  388. {
  389. deploymentReplicas: 10,
  390. maxUnavailable: intstr.FromInt(2),
  391. readyPods: 10,
  392. oldReplicas: 10,
  393. scaleExpected: true,
  394. expectedOldReplicas: 8,
  395. errorExpected: false,
  396. },
  397. {
  398. deploymentReplicas: 10,
  399. maxUnavailable: intstr.FromInt(2),
  400. readyPods: 8,
  401. oldReplicas: 10,
  402. scaleExpected: false,
  403. errorExpected: false,
  404. },
  405. {
  406. deploymentReplicas: 10,
  407. maxUnavailable: intstr.FromInt(2),
  408. readyPods: 10,
  409. oldReplicas: 0,
  410. scaleExpected: false,
  411. errorExpected: true,
  412. },
  413. {
  414. deploymentReplicas: 10,
  415. maxUnavailable: intstr.FromInt(2),
  416. readyPods: 1,
  417. oldReplicas: 10,
  418. scaleExpected: false,
  419. errorExpected: false,
  420. },
  421. }
  422. for i, test := range tests {
  423. t.Logf("executing scenario %d", i)
  424. oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
  425. allRSs := []*exp.ReplicaSet{oldRS}
  426. oldRSs := []*exp.ReplicaSet{oldRS}
  427. deployment := deployment("foo", test.deploymentReplicas, intstr.FromInt(0), test.maxUnavailable, map[string]string{"foo": "bar"})
  428. fakeClientset := fake.Clientset{}
  429. fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  430. switch action.(type) {
  431. case core.ListAction:
  432. podList := &api.PodList{}
  433. for podIndex := 0; podIndex < test.readyPods; podIndex++ {
  434. podList.Items = append(podList.Items, api.Pod{
  435. ObjectMeta: api.ObjectMeta{
  436. Name: fmt.Sprintf("%s-pod-%d", oldRS.Name, podIndex),
  437. Labels: map[string]string{"foo": "bar"},
  438. },
  439. Status: api.PodStatus{
  440. Conditions: []api.PodCondition{
  441. {
  442. Type: api.PodReady,
  443. Status: api.ConditionTrue,
  444. },
  445. },
  446. },
  447. })
  448. }
  449. return true, podList, nil
  450. }
  451. return false, nil, nil
  452. })
  453. controller := &DeploymentController{
  454. client: &fakeClientset,
  455. eventRecorder: &record.FakeRecorder{},
  456. }
  457. scaled, err := controller.scaleDownOldReplicaSetsForRollingUpdate(allRSs, oldRSs, &deployment)
  458. if !test.errorExpected && err != nil {
  459. t.Errorf("unexpected error: %v", err)
  460. continue
  461. }
  462. if !test.scaleExpected {
  463. if scaled != 0 {
  464. t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
  465. }
  466. continue
  467. }
  468. if test.scaleExpected && scaled == 0 {
  469. t.Errorf("expected scaling to occur; actions: %v", fakeClientset.Actions())
  470. continue
  471. }
  472. // There are both list and update actions logged, so extract the update
  473. // action for verification.
  474. var updateAction core.UpdateAction
  475. for _, action := range fakeClientset.Actions() {
  476. switch a := action.(type) {
  477. case core.UpdateAction:
  478. if updateAction != nil {
  479. t.Errorf("expected only 1 update action; had %v and found %v", updateAction, a)
  480. } else {
  481. updateAction = a
  482. }
  483. }
  484. }
  485. if updateAction == nil {
  486. t.Errorf("expected an update action")
  487. continue
  488. }
  489. updated := updateAction.GetObject().(*exp.ReplicaSet)
  490. if e, a := test.expectedOldReplicas, int(updated.Spec.Replicas); e != a {
  491. t.Errorf("expected update to %d replicas, got %d", e, a)
  492. }
  493. }
  494. }