rolling_updater_test.go 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749
  1. /*
  2. Copyright 2014 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 kubectl
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "net/http"
  20. "reflect"
  21. "testing"
  22. "time"
  23. "k8s.io/kubernetes/pkg/api"
  24. "k8s.io/kubernetes/pkg/api/errors"
  25. "k8s.io/kubernetes/pkg/api/testapi"
  26. apitesting "k8s.io/kubernetes/pkg/api/testing"
  27. "k8s.io/kubernetes/pkg/api/unversioned"
  28. "k8s.io/kubernetes/pkg/client/restclient"
  29. client "k8s.io/kubernetes/pkg/client/unversioned"
  30. "k8s.io/kubernetes/pkg/client/unversioned/fake"
  31. "k8s.io/kubernetes/pkg/client/unversioned/testclient"
  32. "k8s.io/kubernetes/pkg/runtime"
  33. "k8s.io/kubernetes/pkg/util/intstr"
  34. "k8s.io/kubernetes/pkg/util/sets"
  35. )
  36. func oldRc(replicas int, original int) *api.ReplicationController {
  37. return &api.ReplicationController{
  38. ObjectMeta: api.ObjectMeta{
  39. Name: "foo-v1",
  40. UID: "7764ae47-9092-11e4-8393-42010af018ff",
  41. Annotations: map[string]string{
  42. originalReplicasAnnotation: fmt.Sprintf("%d", original),
  43. },
  44. },
  45. Spec: api.ReplicationControllerSpec{
  46. Replicas: int32(replicas),
  47. Selector: map[string]string{"version": "v1"},
  48. Template: &api.PodTemplateSpec{
  49. ObjectMeta: api.ObjectMeta{
  50. Name: "foo-v1",
  51. Labels: map[string]string{"version": "v1"},
  52. },
  53. },
  54. },
  55. Status: api.ReplicationControllerStatus{
  56. Replicas: int32(replicas),
  57. },
  58. }
  59. }
  60. func newRc(replicas int, desired int) *api.ReplicationController {
  61. rc := oldRc(replicas, replicas)
  62. rc.Spec.Template = &api.PodTemplateSpec{
  63. ObjectMeta: api.ObjectMeta{
  64. Name: "foo-v2",
  65. Labels: map[string]string{"version": "v2"},
  66. },
  67. }
  68. rc.Spec.Selector = map[string]string{"version": "v2"}
  69. rc.ObjectMeta = api.ObjectMeta{
  70. Name: "foo-v2",
  71. Annotations: map[string]string{
  72. desiredReplicasAnnotation: fmt.Sprintf("%d", desired),
  73. sourceIdAnnotation: "foo-v1:7764ae47-9092-11e4-8393-42010af018ff",
  74. },
  75. }
  76. return rc
  77. }
  78. // TestUpdate performs complex scenario testing for rolling updates. It
  79. // provides fine grained control over the states for each update interval to
  80. // allow the expression of as many edge cases as possible.
  81. func TestUpdate(t *testing.T) {
  82. // up represents a simulated scale up event and expectation
  83. type up struct {
  84. // to is the expected replica count for a scale-up
  85. to int
  86. }
  87. // down represents a simulated scale down event and expectation
  88. type down struct {
  89. // oldReady is the number of oldRc replicas which will be seen
  90. // as ready during the scale down attempt
  91. oldReady int
  92. // newReady is the number of newRc replicas which will be seen
  93. // as ready during the scale up attempt
  94. newReady int
  95. // to is the expected replica count for the scale down
  96. to int
  97. // noop and to are mutually exclusive; if noop is true, that means for
  98. // this down event, no scaling attempt should be made (for example, if
  99. // by scaling down, the readiness minimum would be crossed.)
  100. noop bool
  101. }
  102. tests := []struct {
  103. name string
  104. // oldRc is the "from" deployment
  105. oldRc *api.ReplicationController
  106. // newRc is the "to" deployment
  107. newRc *api.ReplicationController
  108. // whether newRc existed (false means it was created)
  109. newRcExists bool
  110. maxUnavail intstr.IntOrString
  111. maxSurge intstr.IntOrString
  112. // expected is the sequence of up/down events that will be simulated and
  113. // verified
  114. expected []interface{}
  115. // output is the expected textual output written
  116. output string
  117. }{
  118. {
  119. name: "10->10 30/0 fast readiness",
  120. oldRc: oldRc(10, 10),
  121. newRc: newRc(0, 10),
  122. newRcExists: false,
  123. maxUnavail: intstr.FromString("30%"),
  124. maxSurge: intstr.FromString("0%"),
  125. expected: []interface{}{
  126. down{oldReady: 10, newReady: 0, to: 7},
  127. up{3},
  128. down{oldReady: 7, newReady: 3, to: 4},
  129. up{6},
  130. down{oldReady: 4, newReady: 6, to: 1},
  131. up{9},
  132. down{oldReady: 1, newReady: 9, to: 0},
  133. up{10},
  134. },
  135. output: `Created foo-v2
  136. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods)
  137. Scaling foo-v1 down to 7
  138. Scaling foo-v2 up to 3
  139. Scaling foo-v1 down to 4
  140. Scaling foo-v2 up to 6
  141. Scaling foo-v1 down to 1
  142. Scaling foo-v2 up to 9
  143. Scaling foo-v1 down to 0
  144. Scaling foo-v2 up to 10
  145. `,
  146. },
  147. {
  148. name: "10->10 30/0 delayed readiness",
  149. oldRc: oldRc(10, 10),
  150. newRc: newRc(0, 10),
  151. newRcExists: false,
  152. maxUnavail: intstr.FromString("30%"),
  153. maxSurge: intstr.FromString("0%"),
  154. expected: []interface{}{
  155. down{oldReady: 10, newReady: 0, to: 7},
  156. up{3},
  157. down{oldReady: 7, newReady: 0, noop: true},
  158. down{oldReady: 7, newReady: 1, to: 6},
  159. up{4},
  160. down{oldReady: 6, newReady: 4, to: 3},
  161. up{7},
  162. down{oldReady: 3, newReady: 7, to: 0},
  163. up{10},
  164. },
  165. output: `Created foo-v2
  166. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 7 pods available, don't exceed 10 pods)
  167. Scaling foo-v1 down to 7
  168. Scaling foo-v2 up to 3
  169. Scaling foo-v1 down to 6
  170. Scaling foo-v2 up to 4
  171. Scaling foo-v1 down to 3
  172. Scaling foo-v2 up to 7
  173. Scaling foo-v1 down to 0
  174. Scaling foo-v2 up to 10
  175. `,
  176. }, {
  177. name: "10->10 30/0 fast readiness, continuation",
  178. oldRc: oldRc(7, 10),
  179. newRc: newRc(3, 10),
  180. newRcExists: false,
  181. maxUnavail: intstr.FromString("30%"),
  182. maxSurge: intstr.FromString("0%"),
  183. expected: []interface{}{
  184. down{oldReady: 7, newReady: 3, to: 4},
  185. up{6},
  186. down{oldReady: 4, newReady: 6, to: 1},
  187. up{9},
  188. down{oldReady: 1, newReady: 9, to: 0},
  189. up{10},
  190. },
  191. output: `Created foo-v2
  192. Scaling up foo-v2 from 3 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods)
  193. Scaling foo-v1 down to 4
  194. Scaling foo-v2 up to 6
  195. Scaling foo-v1 down to 1
  196. Scaling foo-v2 up to 9
  197. Scaling foo-v1 down to 0
  198. Scaling foo-v2 up to 10
  199. `,
  200. }, {
  201. name: "10->10 30/0 fast readiness, continued after restart which prevented first scale-up",
  202. oldRc: oldRc(7, 10),
  203. newRc: newRc(0, 10),
  204. newRcExists: false,
  205. maxUnavail: intstr.FromString("30%"),
  206. maxSurge: intstr.FromString("0%"),
  207. expected: []interface{}{
  208. down{oldReady: 7, newReady: 0, noop: true},
  209. up{3},
  210. down{oldReady: 7, newReady: 3, to: 4},
  211. up{6},
  212. down{oldReady: 4, newReady: 6, to: 1},
  213. up{9},
  214. down{oldReady: 1, newReady: 9, to: 0},
  215. up{10},
  216. },
  217. output: `Created foo-v2
  218. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 7 to 0 (keep 7 pods available, don't exceed 10 pods)
  219. Scaling foo-v2 up to 3
  220. Scaling foo-v1 down to 4
  221. Scaling foo-v2 up to 6
  222. Scaling foo-v1 down to 1
  223. Scaling foo-v2 up to 9
  224. Scaling foo-v1 down to 0
  225. Scaling foo-v2 up to 10
  226. `,
  227. }, {
  228. name: "10->10 0/30 fast readiness",
  229. oldRc: oldRc(10, 10),
  230. newRc: newRc(0, 10),
  231. newRcExists: false,
  232. maxUnavail: intstr.FromString("0%"),
  233. maxSurge: intstr.FromString("30%"),
  234. expected: []interface{}{
  235. up{3},
  236. down{oldReady: 10, newReady: 3, to: 7},
  237. up{6},
  238. down{oldReady: 7, newReady: 6, to: 4},
  239. up{9},
  240. down{oldReady: 4, newReady: 9, to: 1},
  241. up{10},
  242. down{oldReady: 1, newReady: 10, to: 0},
  243. },
  244. output: `Created foo-v2
  245. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods)
  246. Scaling foo-v2 up to 3
  247. Scaling foo-v1 down to 7
  248. Scaling foo-v2 up to 6
  249. Scaling foo-v1 down to 4
  250. Scaling foo-v2 up to 9
  251. Scaling foo-v1 down to 1
  252. Scaling foo-v2 up to 10
  253. Scaling foo-v1 down to 0
  254. `,
  255. }, {
  256. name: "10->10 0/30 delayed readiness",
  257. oldRc: oldRc(10, 10),
  258. newRc: newRc(0, 10),
  259. newRcExists: false,
  260. maxUnavail: intstr.FromString("0%"),
  261. maxSurge: intstr.FromString("30%"),
  262. expected: []interface{}{
  263. up{3},
  264. down{oldReady: 10, newReady: 0, noop: true},
  265. down{oldReady: 10, newReady: 1, to: 9},
  266. up{4},
  267. down{oldReady: 9, newReady: 3, to: 7},
  268. up{6},
  269. down{oldReady: 7, newReady: 6, to: 4},
  270. up{9},
  271. down{oldReady: 4, newReady: 9, to: 1},
  272. up{10},
  273. down{oldReady: 1, newReady: 9, noop: true},
  274. down{oldReady: 1, newReady: 10, to: 0},
  275. },
  276. output: `Created foo-v2
  277. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 13 pods)
  278. Scaling foo-v2 up to 3
  279. Scaling foo-v1 down to 9
  280. Scaling foo-v2 up to 4
  281. Scaling foo-v1 down to 7
  282. Scaling foo-v2 up to 6
  283. Scaling foo-v1 down to 4
  284. Scaling foo-v2 up to 9
  285. Scaling foo-v1 down to 1
  286. Scaling foo-v2 up to 10
  287. Scaling foo-v1 down to 0
  288. `,
  289. }, {
  290. name: "10->10 10/20 fast readiness",
  291. oldRc: oldRc(10, 10),
  292. newRc: newRc(0, 10),
  293. newRcExists: false,
  294. maxUnavail: intstr.FromString("10%"),
  295. maxSurge: intstr.FromString("20%"),
  296. expected: []interface{}{
  297. up{2},
  298. down{oldReady: 10, newReady: 2, to: 7},
  299. up{5},
  300. down{oldReady: 7, newReady: 5, to: 4},
  301. up{8},
  302. down{oldReady: 4, newReady: 8, to: 1},
  303. up{10},
  304. down{oldReady: 1, newReady: 10, to: 0},
  305. },
  306. output: `Created foo-v2
  307. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
  308. Scaling foo-v2 up to 2
  309. Scaling foo-v1 down to 7
  310. Scaling foo-v2 up to 5
  311. Scaling foo-v1 down to 4
  312. Scaling foo-v2 up to 8
  313. Scaling foo-v1 down to 1
  314. Scaling foo-v2 up to 10
  315. Scaling foo-v1 down to 0
  316. `,
  317. }, {
  318. name: "10->10 10/20 delayed readiness",
  319. oldRc: oldRc(10, 10),
  320. newRc: newRc(0, 10),
  321. newRcExists: false,
  322. maxUnavail: intstr.FromString("10%"),
  323. maxSurge: intstr.FromString("20%"),
  324. expected: []interface{}{
  325. up{2},
  326. down{oldReady: 10, newReady: 2, to: 7},
  327. up{5},
  328. down{oldReady: 7, newReady: 4, to: 5},
  329. up{7},
  330. down{oldReady: 5, newReady: 4, noop: true},
  331. down{oldReady: 5, newReady: 7, to: 2},
  332. up{10},
  333. down{oldReady: 2, newReady: 9, to: 0},
  334. },
  335. output: `Created foo-v2
  336. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
  337. Scaling foo-v2 up to 2
  338. Scaling foo-v1 down to 7
  339. Scaling foo-v2 up to 5
  340. Scaling foo-v1 down to 5
  341. Scaling foo-v2 up to 7
  342. Scaling foo-v1 down to 2
  343. Scaling foo-v2 up to 10
  344. Scaling foo-v1 down to 0
  345. `,
  346. }, {
  347. name: "10->10 10/20 fast readiness continued after restart which prevented first scale-down",
  348. oldRc: oldRc(10, 10),
  349. newRc: newRc(2, 10),
  350. newRcExists: false,
  351. maxUnavail: intstr.FromString("10%"),
  352. maxSurge: intstr.FromString("20%"),
  353. expected: []interface{}{
  354. down{oldReady: 10, newReady: 2, to: 7},
  355. up{5},
  356. down{oldReady: 7, newReady: 5, to: 4},
  357. up{8},
  358. down{oldReady: 4, newReady: 8, to: 1},
  359. up{10},
  360. down{oldReady: 1, newReady: 10, to: 0},
  361. },
  362. output: `Created foo-v2
  363. Scaling up foo-v2 from 2 to 10, scaling down foo-v1 from 10 to 0 (keep 9 pods available, don't exceed 12 pods)
  364. Scaling foo-v1 down to 7
  365. Scaling foo-v2 up to 5
  366. Scaling foo-v1 down to 4
  367. Scaling foo-v2 up to 8
  368. Scaling foo-v1 down to 1
  369. Scaling foo-v2 up to 10
  370. Scaling foo-v1 down to 0
  371. `,
  372. }, {
  373. name: "10->10 0/100 fast readiness",
  374. oldRc: oldRc(10, 10),
  375. newRc: newRc(0, 10),
  376. newRcExists: false,
  377. maxUnavail: intstr.FromString("0%"),
  378. maxSurge: intstr.FromString("100%"),
  379. expected: []interface{}{
  380. up{10},
  381. down{oldReady: 10, newReady: 10, to: 0},
  382. },
  383. output: `Created foo-v2
  384. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods)
  385. Scaling foo-v2 up to 10
  386. Scaling foo-v1 down to 0
  387. `,
  388. }, {
  389. name: "10->10 0/100 delayed readiness",
  390. oldRc: oldRc(10, 10),
  391. newRc: newRc(0, 10),
  392. newRcExists: false,
  393. maxUnavail: intstr.FromString("0%"),
  394. maxSurge: intstr.FromString("100%"),
  395. expected: []interface{}{
  396. up{10},
  397. down{oldReady: 10, newReady: 0, noop: true},
  398. down{oldReady: 10, newReady: 2, to: 8},
  399. down{oldReady: 8, newReady: 7, to: 3},
  400. down{oldReady: 3, newReady: 10, to: 0},
  401. },
  402. output: `Created foo-v2
  403. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 10 pods available, don't exceed 20 pods)
  404. Scaling foo-v2 up to 10
  405. Scaling foo-v1 down to 8
  406. Scaling foo-v1 down to 3
  407. Scaling foo-v1 down to 0
  408. `,
  409. }, {
  410. name: "10->10 100/0 fast readiness",
  411. oldRc: oldRc(10, 10),
  412. newRc: newRc(0, 10),
  413. newRcExists: false,
  414. maxUnavail: intstr.FromString("100%"),
  415. maxSurge: intstr.FromString("0%"),
  416. expected: []interface{}{
  417. down{oldReady: 10, newReady: 0, to: 0},
  418. up{10},
  419. },
  420. output: `Created foo-v2
  421. Scaling up foo-v2 from 0 to 10, scaling down foo-v1 from 10 to 0 (keep 0 pods available, don't exceed 10 pods)
  422. Scaling foo-v1 down to 0
  423. Scaling foo-v2 up to 10
  424. `,
  425. }, {
  426. name: "1->1 25/25 maintain minimum availability",
  427. oldRc: oldRc(1, 1),
  428. newRc: newRc(0, 1),
  429. newRcExists: false,
  430. maxUnavail: intstr.FromString("25%"),
  431. maxSurge: intstr.FromString("25%"),
  432. expected: []interface{}{
  433. up{1},
  434. down{oldReady: 1, newReady: 0, noop: true},
  435. down{oldReady: 1, newReady: 1, to: 0},
  436. },
  437. output: `Created foo-v2
  438. Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
  439. Scaling foo-v2 up to 1
  440. Scaling foo-v1 down to 0
  441. `,
  442. }, {
  443. name: "1->1 0/10 delayed readiness",
  444. oldRc: oldRc(1, 1),
  445. newRc: newRc(0, 1),
  446. newRcExists: false,
  447. maxUnavail: intstr.FromString("0%"),
  448. maxSurge: intstr.FromString("10%"),
  449. expected: []interface{}{
  450. up{1},
  451. down{oldReady: 1, newReady: 0, noop: true},
  452. down{oldReady: 1, newReady: 1, to: 0},
  453. },
  454. output: `Created foo-v2
  455. Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
  456. Scaling foo-v2 up to 1
  457. Scaling foo-v1 down to 0
  458. `,
  459. }, {
  460. name: "1->1 10/10 delayed readiness",
  461. oldRc: oldRc(1, 1),
  462. newRc: newRc(0, 1),
  463. newRcExists: false,
  464. maxUnavail: intstr.FromString("10%"),
  465. maxSurge: intstr.FromString("10%"),
  466. expected: []interface{}{
  467. up{1},
  468. down{oldReady: 1, newReady: 0, noop: true},
  469. down{oldReady: 1, newReady: 1, to: 0},
  470. },
  471. output: `Created foo-v2
  472. Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
  473. Scaling foo-v2 up to 1
  474. Scaling foo-v1 down to 0
  475. `,
  476. }, {
  477. name: "3->3 1/1 fast readiness (absolute values)",
  478. oldRc: oldRc(3, 3),
  479. newRc: newRc(0, 3),
  480. newRcExists: false,
  481. maxUnavail: intstr.FromInt(0),
  482. maxSurge: intstr.FromInt(1),
  483. expected: []interface{}{
  484. up{1},
  485. down{oldReady: 3, newReady: 1, to: 2},
  486. up{2},
  487. down{oldReady: 2, newReady: 2, to: 1},
  488. up{3},
  489. down{oldReady: 1, newReady: 3, to: 0},
  490. },
  491. output: `Created foo-v2
  492. Scaling up foo-v2 from 0 to 3, scaling down foo-v1 from 3 to 0 (keep 3 pods available, don't exceed 4 pods)
  493. Scaling foo-v2 up to 1
  494. Scaling foo-v1 down to 2
  495. Scaling foo-v2 up to 2
  496. Scaling foo-v1 down to 1
  497. Scaling foo-v2 up to 3
  498. Scaling foo-v1 down to 0
  499. `,
  500. }, {
  501. name: "10->10 0/20 fast readiness, continued after restart which resulted in partial first scale-up",
  502. oldRc: oldRc(6, 10),
  503. newRc: newRc(5, 10),
  504. newRcExists: false,
  505. maxUnavail: intstr.FromString("0%"),
  506. maxSurge: intstr.FromString("20%"),
  507. expected: []interface{}{
  508. up{6},
  509. down{oldReady: 6, newReady: 6, to: 4},
  510. up{8},
  511. down{oldReady: 4, newReady: 8, to: 2},
  512. up{10},
  513. down{oldReady: 1, newReady: 10, to: 0},
  514. },
  515. output: `Created foo-v2
  516. Scaling up foo-v2 from 5 to 10, scaling down foo-v1 from 6 to 0 (keep 10 pods available, don't exceed 12 pods)
  517. Scaling foo-v2 up to 6
  518. Scaling foo-v1 down to 4
  519. Scaling foo-v2 up to 8
  520. Scaling foo-v1 down to 2
  521. Scaling foo-v2 up to 10
  522. Scaling foo-v1 down to 0
  523. `,
  524. }, {
  525. name: "10->20 0/300 fast readiness",
  526. oldRc: oldRc(10, 10),
  527. newRc: newRc(0, 20),
  528. newRcExists: false,
  529. maxUnavail: intstr.FromString("0%"),
  530. maxSurge: intstr.FromString("300%"),
  531. expected: []interface{}{
  532. up{20},
  533. down{oldReady: 10, newReady: 20, to: 0},
  534. },
  535. output: `Created foo-v2
  536. Scaling up foo-v2 from 0 to 20, scaling down foo-v1 from 10 to 0 (keep 20 pods available, don't exceed 80 pods)
  537. Scaling foo-v2 up to 20
  538. Scaling foo-v1 down to 0
  539. `,
  540. }, {
  541. name: "1->1 0/1 scale down unavailable rc to a ready rc (rollback)",
  542. oldRc: oldRc(1, 1),
  543. newRc: newRc(1, 1),
  544. newRcExists: true,
  545. maxUnavail: intstr.FromInt(0),
  546. maxSurge: intstr.FromInt(1),
  547. expected: []interface{}{
  548. up{1},
  549. down{oldReady: 0, newReady: 1, to: 0},
  550. },
  551. output: `Continuing update with existing controller foo-v2.
  552. Scaling up foo-v2 from 1 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods)
  553. Scaling foo-v1 down to 0
  554. `,
  555. },
  556. {
  557. name: "3->0 1/1 desired 0 (absolute values)",
  558. oldRc: oldRc(3, 3),
  559. newRc: newRc(0, 0),
  560. newRcExists: true,
  561. maxUnavail: intstr.FromInt(1),
  562. maxSurge: intstr.FromInt(1),
  563. expected: []interface{}{
  564. down{oldReady: 3, newReady: 0, to: 0},
  565. },
  566. output: `Continuing update with existing controller foo-v2.
  567. Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 1 pods)
  568. Scaling foo-v1 down to 0
  569. `,
  570. },
  571. {
  572. name: "3->0 10/10 desired 0 (percentages)",
  573. oldRc: oldRc(3, 3),
  574. newRc: newRc(0, 0),
  575. newRcExists: true,
  576. maxUnavail: intstr.FromString("10%"),
  577. maxSurge: intstr.FromString("10%"),
  578. expected: []interface{}{
  579. down{oldReady: 3, newReady: 0, to: 0},
  580. },
  581. output: `Continuing update with existing controller foo-v2.
  582. Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods)
  583. Scaling foo-v1 down to 0
  584. `,
  585. },
  586. {
  587. name: "3->0 10/10 desired 0 (create new RC)",
  588. oldRc: oldRc(3, 3),
  589. newRc: newRc(0, 0),
  590. newRcExists: false,
  591. maxUnavail: intstr.FromString("10%"),
  592. maxSurge: intstr.FromString("10%"),
  593. expected: []interface{}{
  594. down{oldReady: 3, newReady: 0, to: 0},
  595. },
  596. output: `Created foo-v2
  597. Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods)
  598. Scaling foo-v1 down to 0
  599. `,
  600. },
  601. {
  602. name: "0->0 1/1 desired 0 (absolute values)",
  603. oldRc: oldRc(0, 0),
  604. newRc: newRc(0, 0),
  605. newRcExists: true,
  606. maxUnavail: intstr.FromInt(1),
  607. maxSurge: intstr.FromInt(1),
  608. expected: []interface{}{
  609. down{oldReady: 0, newReady: 0, to: 0},
  610. },
  611. output: `Continuing update with existing controller foo-v2.
  612. Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 0 to 0 (keep 0 pods available, don't exceed 1 pods)
  613. `,
  614. }, {
  615. name: "30->2 50%/0",
  616. oldRc: oldRc(30, 30),
  617. newRc: newRc(0, 2),
  618. newRcExists: false,
  619. maxUnavail: intstr.FromString("50%"),
  620. maxSurge: intstr.FromInt(0),
  621. expected: []interface{}{
  622. down{oldReady: 30, newReady: 0, to: 1},
  623. up{1},
  624. down{oldReady: 1, newReady: 2, to: 0},
  625. up{2},
  626. },
  627. output: `Created foo-v2
  628. Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 30 to 0 (keep 1 pods available, don't exceed 2 pods)
  629. Scaling foo-v1 down to 1
  630. Scaling foo-v2 up to 1
  631. Scaling foo-v1 down to 0
  632. Scaling foo-v2 up to 2
  633. `,
  634. },
  635. {
  636. name: "2->2 1/0 blocked oldRc",
  637. oldRc: oldRc(2, 2),
  638. newRc: newRc(0, 2),
  639. newRcExists: false,
  640. maxUnavail: intstr.FromInt(1),
  641. maxSurge: intstr.FromInt(0),
  642. expected: []interface{}{
  643. down{oldReady: 1, newReady: 0, to: 1},
  644. up{1},
  645. down{oldReady: 1, newReady: 1, to: 0},
  646. up{2},
  647. },
  648. output: `Created foo-v2
  649. Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods)
  650. Scaling foo-v1 down to 1
  651. Scaling foo-v2 up to 1
  652. Scaling foo-v1 down to 0
  653. Scaling foo-v2 up to 2
  654. `,
  655. },
  656. {
  657. name: "1->1 1/0 allow maxUnavailability",
  658. oldRc: oldRc(1, 1),
  659. newRc: newRc(0, 1),
  660. newRcExists: false,
  661. maxUnavail: intstr.FromString("1%"),
  662. maxSurge: intstr.FromInt(0),
  663. expected: []interface{}{
  664. down{oldReady: 1, newReady: 0, to: 0},
  665. up{1},
  666. },
  667. output: `Created foo-v2
  668. Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 1 pods)
  669. Scaling foo-v1 down to 0
  670. Scaling foo-v2 up to 1
  671. `,
  672. },
  673. {
  674. name: "1->2 25/25 complex asymmetric deployment",
  675. oldRc: oldRc(1, 1),
  676. newRc: newRc(0, 2),
  677. newRcExists: false,
  678. maxUnavail: intstr.FromString("25%"),
  679. maxSurge: intstr.FromString("25%"),
  680. expected: []interface{}{
  681. up{2},
  682. down{oldReady: 1, newReady: 2, to: 0},
  683. },
  684. output: `Created foo-v2
  685. Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 1 to 0 (keep 2 pods available, don't exceed 3 pods)
  686. Scaling foo-v2 up to 2
  687. Scaling foo-v1 down to 0
  688. `,
  689. },
  690. {
  691. name: "2->2 25/1 maxSurge trumps maxUnavailable",
  692. oldRc: oldRc(2, 2),
  693. newRc: newRc(0, 2),
  694. newRcExists: false,
  695. maxUnavail: intstr.FromString("25%"),
  696. maxSurge: intstr.FromString("1%"),
  697. expected: []interface{}{
  698. up{1},
  699. down{oldReady: 2, newReady: 1, to: 1},
  700. up{2},
  701. down{oldReady: 1, newReady: 2, to: 0},
  702. },
  703. output: `Created foo-v2
  704. Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 2 pods available, don't exceed 3 pods)
  705. Scaling foo-v2 up to 1
  706. Scaling foo-v1 down to 1
  707. Scaling foo-v2 up to 2
  708. Scaling foo-v1 down to 0
  709. `,
  710. },
  711. {
  712. name: "2->2 25/0 maxUnavailable resolves to zero, then one",
  713. oldRc: oldRc(2, 2),
  714. newRc: newRc(0, 2),
  715. newRcExists: false,
  716. maxUnavail: intstr.FromString("25%"),
  717. maxSurge: intstr.FromString("0%"),
  718. expected: []interface{}{
  719. down{oldReady: 2, newReady: 0, to: 1},
  720. up{1},
  721. down{oldReady: 1, newReady: 1, to: 0},
  722. up{2},
  723. },
  724. output: `Created foo-v2
  725. Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods)
  726. Scaling foo-v1 down to 1
  727. Scaling foo-v2 up to 1
  728. Scaling foo-v1 down to 0
  729. Scaling foo-v2 up to 2
  730. `,
  731. },
  732. }
  733. for i, test := range tests {
  734. // Extract expectations into some makeshift FIFOs so they can be returned
  735. // in the correct order from the right places. This lets scale downs be
  736. // expressed a single event even though the data is used from multiple
  737. // interface calls.
  738. oldReady := []int{}
  739. newReady := []int{}
  740. upTo := []int{}
  741. downTo := []int{}
  742. for _, event := range test.expected {
  743. switch e := event.(type) {
  744. case down:
  745. oldReady = append(oldReady, e.oldReady)
  746. newReady = append(newReady, e.newReady)
  747. if !e.noop {
  748. downTo = append(downTo, e.to)
  749. }
  750. case up:
  751. upTo = append(upTo, e.to)
  752. }
  753. }
  754. // Make a way to get the next item from our FIFOs. Returns -1 if the array
  755. // is empty.
  756. next := func(s *[]int) int {
  757. slice := *s
  758. v := -1
  759. if len(slice) > 0 {
  760. v = slice[0]
  761. if len(slice) > 1 {
  762. *s = slice[1:]
  763. } else {
  764. *s = []int{}
  765. }
  766. }
  767. return v
  768. }
  769. t.Logf("running test %d (%s) (up: %v, down: %v, oldReady: %v, newReady: %v)", i, test.name, upTo, downTo, oldReady, newReady)
  770. updater := &RollingUpdater{
  771. ns: "default",
  772. scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
  773. // Return a scale up or scale down expectation depending on the rc,
  774. // and throw errors if there is no expectation expressed for this
  775. // call.
  776. expected := -1
  777. switch {
  778. case rc == test.newRc:
  779. t.Logf("scaling up %s to %d", rc.Name, rc.Spec.Replicas)
  780. expected = next(&upTo)
  781. case rc == test.oldRc:
  782. t.Logf("scaling down %s to %d", rc.Name, rc.Spec.Replicas)
  783. expected = next(&downTo)
  784. }
  785. if expected == -1 {
  786. t.Fatalf("unexpected scale of %s to %d", rc.Name, rc.Spec.Replicas)
  787. } else if e, a := expected, int(rc.Spec.Replicas); e != a {
  788. t.Fatalf("expected scale of %s to %d, got %d", rc.Name, e, a)
  789. }
  790. // Simulate the scale.
  791. rc.Status.Replicas = rc.Spec.Replicas
  792. return rc, nil
  793. },
  794. getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
  795. // Simulate a create vs. update of an existing controller.
  796. return test.newRc, test.newRcExists, nil
  797. },
  798. cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
  799. return nil
  800. },
  801. }
  802. // Set up a mock readiness check which handles the test assertions.
  803. updater.getReadyPods = func(oldRc, newRc *api.ReplicationController, minReadySecondsDeadline int32) (int32, int32, error) {
  804. // Return simulated readiness, and throw an error if this call has no
  805. // expectations defined.
  806. oldReady := next(&oldReady)
  807. newReady := next(&newReady)
  808. if oldReady == -1 || newReady == -1 {
  809. t.Fatalf("unexpected getReadyPods call for:\noldRc: %#v\nnewRc: %#v", oldRc, newRc)
  810. }
  811. return int32(oldReady), int32(newReady), nil
  812. }
  813. var buffer bytes.Buffer
  814. config := &RollingUpdaterConfig{
  815. Out: &buffer,
  816. OldRc: test.oldRc,
  817. NewRc: test.newRc,
  818. UpdatePeriod: 0,
  819. Interval: time.Millisecond,
  820. Timeout: time.Millisecond,
  821. CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
  822. MaxUnavailable: test.maxUnavail,
  823. MaxSurge: test.maxSurge,
  824. }
  825. err := updater.Update(config)
  826. if err != nil {
  827. t.Errorf("unexpected error: %v", err)
  828. }
  829. if buffer.String() != test.output {
  830. t.Errorf("Bad output. expected:\n%s\ngot:\n%s", test.output, buffer.String())
  831. }
  832. }
  833. }
  834. // TestUpdate_progressTimeout ensures that an update which isn't making any
  835. // progress will eventually time out with a specified error.
  836. func TestUpdate_progressTimeout(t *testing.T) {
  837. oldRc := oldRc(2, 2)
  838. newRc := newRc(0, 2)
  839. updater := &RollingUpdater{
  840. ns: "default",
  841. scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
  842. // Do nothing.
  843. return rc, nil
  844. },
  845. getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
  846. return newRc, false, nil
  847. },
  848. cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
  849. return nil
  850. },
  851. }
  852. updater.getReadyPods = func(oldRc, newRc *api.ReplicationController, minReadySeconds int32) (int32, int32, error) {
  853. // Coerce a timeout by pods never becoming ready.
  854. return 0, 0, nil
  855. }
  856. var buffer bytes.Buffer
  857. config := &RollingUpdaterConfig{
  858. Out: &buffer,
  859. OldRc: oldRc,
  860. NewRc: newRc,
  861. UpdatePeriod: 0,
  862. Interval: time.Millisecond,
  863. Timeout: time.Millisecond,
  864. CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
  865. MaxUnavailable: intstr.FromInt(0),
  866. MaxSurge: intstr.FromInt(1),
  867. }
  868. err := updater.Update(config)
  869. if err == nil {
  870. t.Fatalf("expected an error")
  871. }
  872. if e, a := "timed out waiting for any update progress to be made", err.Error(); e != a {
  873. t.Fatalf("expected error message: %s, got: %s", e, a)
  874. }
  875. }
  876. func TestUpdate_assignOriginalAnnotation(t *testing.T) {
  877. oldRc := oldRc(1, 1)
  878. delete(oldRc.Annotations, originalReplicasAnnotation)
  879. newRc := newRc(1, 1)
  880. var updatedOldRc *api.ReplicationController
  881. fake := &testclient.Fake{}
  882. fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
  883. switch a := action.(type) {
  884. case testclient.GetAction:
  885. return true, oldRc, nil
  886. case testclient.UpdateAction:
  887. updatedOldRc = a.GetObject().(*api.ReplicationController)
  888. return true, updatedOldRc, nil
  889. }
  890. return false, nil, nil
  891. })
  892. updater := &RollingUpdater{
  893. c: fake,
  894. ns: "default",
  895. scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
  896. return rc, nil
  897. },
  898. getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
  899. return newRc, false, nil
  900. },
  901. cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
  902. return nil
  903. },
  904. getReadyPods: func(oldRc, newRc *api.ReplicationController, minReadySeconds int32) (int32, int32, error) {
  905. return 1, 1, nil
  906. },
  907. }
  908. var buffer bytes.Buffer
  909. config := &RollingUpdaterConfig{
  910. Out: &buffer,
  911. OldRc: oldRc,
  912. NewRc: newRc,
  913. UpdatePeriod: 0,
  914. Interval: time.Millisecond,
  915. Timeout: time.Millisecond,
  916. CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
  917. MaxUnavailable: intstr.FromString("100%"),
  918. }
  919. err := updater.Update(config)
  920. if err != nil {
  921. t.Fatalf("unexpected error: %v", err)
  922. }
  923. if updatedOldRc == nil {
  924. t.Fatalf("expected rc to be updated")
  925. }
  926. if e, a := "1", updatedOldRc.Annotations[originalReplicasAnnotation]; e != a {
  927. t.Fatalf("expected annotation value %s, got %s", e, a)
  928. }
  929. }
  930. func TestRollingUpdater_multipleContainersInPod(t *testing.T) {
  931. tests := []struct {
  932. oldRc *api.ReplicationController
  933. newRc *api.ReplicationController
  934. container string
  935. image string
  936. deploymentKey string
  937. }{
  938. {
  939. oldRc: &api.ReplicationController{
  940. ObjectMeta: api.ObjectMeta{
  941. Name: "foo",
  942. },
  943. Spec: api.ReplicationControllerSpec{
  944. Selector: map[string]string{
  945. "dk": "old",
  946. },
  947. Template: &api.PodTemplateSpec{
  948. ObjectMeta: api.ObjectMeta{
  949. Labels: map[string]string{
  950. "dk": "old",
  951. },
  952. },
  953. Spec: api.PodSpec{
  954. Containers: []api.Container{
  955. {
  956. Name: "container1",
  957. Image: "image1",
  958. },
  959. {
  960. Name: "container2",
  961. Image: "image2",
  962. },
  963. },
  964. },
  965. },
  966. },
  967. },
  968. newRc: &api.ReplicationController{
  969. ObjectMeta: api.ObjectMeta{
  970. Name: "foo",
  971. },
  972. Spec: api.ReplicationControllerSpec{
  973. Selector: map[string]string{
  974. "dk": "old",
  975. },
  976. Template: &api.PodTemplateSpec{
  977. ObjectMeta: api.ObjectMeta{
  978. Labels: map[string]string{
  979. "dk": "old",
  980. },
  981. },
  982. Spec: api.PodSpec{
  983. Containers: []api.Container{
  984. {
  985. Name: "container1",
  986. Image: "newimage",
  987. },
  988. {
  989. Name: "container2",
  990. Image: "image2",
  991. },
  992. },
  993. },
  994. },
  995. },
  996. },
  997. container: "container1",
  998. image: "newimage",
  999. deploymentKey: "dk",
  1000. },
  1001. {
  1002. oldRc: &api.ReplicationController{
  1003. ObjectMeta: api.ObjectMeta{
  1004. Name: "bar",
  1005. },
  1006. Spec: api.ReplicationControllerSpec{
  1007. Selector: map[string]string{
  1008. "dk": "old",
  1009. },
  1010. Template: &api.PodTemplateSpec{
  1011. ObjectMeta: api.ObjectMeta{
  1012. Labels: map[string]string{
  1013. "dk": "old",
  1014. },
  1015. },
  1016. Spec: api.PodSpec{
  1017. Containers: []api.Container{
  1018. {
  1019. Name: "container1",
  1020. Image: "image1",
  1021. },
  1022. },
  1023. },
  1024. },
  1025. },
  1026. },
  1027. newRc: &api.ReplicationController{
  1028. ObjectMeta: api.ObjectMeta{
  1029. Name: "bar",
  1030. },
  1031. Spec: api.ReplicationControllerSpec{
  1032. Selector: map[string]string{
  1033. "dk": "old",
  1034. },
  1035. Template: &api.PodTemplateSpec{
  1036. ObjectMeta: api.ObjectMeta{
  1037. Labels: map[string]string{
  1038. "dk": "old",
  1039. },
  1040. },
  1041. Spec: api.PodSpec{
  1042. Containers: []api.Container{
  1043. {
  1044. Name: "container1",
  1045. Image: "newimage",
  1046. },
  1047. },
  1048. },
  1049. },
  1050. },
  1051. },
  1052. container: "container1",
  1053. image: "newimage",
  1054. deploymentKey: "dk",
  1055. },
  1056. }
  1057. for _, test := range tests {
  1058. fake := &testclient.Fake{}
  1059. fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
  1060. switch action.(type) {
  1061. case testclient.GetAction:
  1062. return true, test.oldRc, nil
  1063. }
  1064. return false, nil, nil
  1065. })
  1066. codec := testapi.Default.Codec()
  1067. deploymentHash, err := api.HashObject(test.newRc, codec)
  1068. if err != nil {
  1069. t.Errorf("unexpected error: %v", err)
  1070. }
  1071. test.newRc.Spec.Selector[test.deploymentKey] = deploymentHash
  1072. test.newRc.Spec.Template.Labels[test.deploymentKey] = deploymentHash
  1073. test.newRc.Name = fmt.Sprintf("%s-%s", test.newRc.Name, deploymentHash)
  1074. config := &NewControllerConfig{
  1075. OldName: test.oldRc.ObjectMeta.Name,
  1076. NewName: test.newRc.ObjectMeta.Name,
  1077. Image: test.image,
  1078. Container: test.container,
  1079. DeploymentKey: test.deploymentKey,
  1080. }
  1081. updatedRc, err := CreateNewControllerFromCurrentController(fake, codec, config)
  1082. if err != nil {
  1083. t.Errorf("unexpected error: %v", err)
  1084. }
  1085. if !reflect.DeepEqual(updatedRc, test.newRc) {
  1086. t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.newRc, updatedRc)
  1087. }
  1088. }
  1089. }
  1090. // TestRollingUpdater_cleanupWithClients ensures that the cleanup policy is
  1091. // correctly implemented.
  1092. func TestRollingUpdater_cleanupWithClients(t *testing.T) {
  1093. rc := oldRc(2, 2)
  1094. rcExisting := newRc(1, 3)
  1095. tests := []struct {
  1096. name string
  1097. policy RollingUpdaterCleanupPolicy
  1098. responses []runtime.Object
  1099. expected []string
  1100. }{
  1101. {
  1102. name: "preserve",
  1103. policy: PreserveRollingUpdateCleanupPolicy,
  1104. responses: []runtime.Object{rcExisting},
  1105. expected: []string{
  1106. "get",
  1107. "update",
  1108. "get",
  1109. "get",
  1110. },
  1111. },
  1112. {
  1113. name: "delete",
  1114. policy: DeleteRollingUpdateCleanupPolicy,
  1115. responses: []runtime.Object{rcExisting},
  1116. expected: []string{
  1117. "get",
  1118. "update",
  1119. "get",
  1120. "get",
  1121. "delete",
  1122. },
  1123. },
  1124. //{
  1125. // This cases is separated to a standalone
  1126. // TestRollingUpdater_cleanupWithClients_Rename. We have to do this
  1127. // because the unversioned fake client is unable to delete objects.
  1128. // TODO: uncomment this case when the unversioned fake client uses
  1129. // pkg/client/testing/core.
  1130. // {
  1131. // name: "rename",
  1132. // policy: RenameRollingUpdateCleanupPolicy,
  1133. // responses: []runtime.Object{rcExisting},
  1134. // expected: []string{
  1135. // "get",
  1136. // "update",
  1137. // "get",
  1138. // "get",
  1139. // "delete",
  1140. // "create",
  1141. // "delete",
  1142. // },
  1143. // },
  1144. //},
  1145. }
  1146. for _, test := range tests {
  1147. fake := testclient.NewSimpleFake(test.responses...)
  1148. updater := &RollingUpdater{
  1149. ns: "default",
  1150. c: fake,
  1151. }
  1152. config := &RollingUpdaterConfig{
  1153. Out: ioutil.Discard,
  1154. OldRc: rc,
  1155. NewRc: rcExisting,
  1156. UpdatePeriod: 0,
  1157. Interval: time.Millisecond,
  1158. Timeout: time.Millisecond,
  1159. CleanupPolicy: test.policy,
  1160. }
  1161. err := updater.cleanupWithClients(rc, rcExisting, config)
  1162. if err != nil {
  1163. t.Errorf("unexpected error: %v", err)
  1164. }
  1165. if len(fake.Actions()) != len(test.expected) {
  1166. t.Fatalf("%s: unexpected actions: %v, expected %v", test.name, fake.Actions(), test.expected)
  1167. }
  1168. for j, action := range fake.Actions() {
  1169. if e, a := test.expected[j], action.GetVerb(); e != a {
  1170. t.Errorf("%s: unexpected action: expected %s, got %s", test.name, e, a)
  1171. }
  1172. }
  1173. }
  1174. }
  1175. // TestRollingUpdater_cleanupWithClients_Rename tests the rename cleanup policy. It's separated to
  1176. // a standalone test because the unversioned fake client is unable to delete
  1177. // objects.
  1178. // TODO: move this test back to TestRollingUpdater_cleanupWithClients
  1179. // when the fake client uses pkg/client/testing/core in the future.
  1180. func TestRollingUpdater_cleanupWithClients_Rename(t *testing.T) {
  1181. rc := oldRc(2, 2)
  1182. rcExisting := newRc(1, 3)
  1183. expectedActions := []string{"delete", "get", "create"}
  1184. fake := &testclient.Fake{}
  1185. fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
  1186. switch action.(type) {
  1187. case testclient.CreateAction:
  1188. return true, nil, nil
  1189. case testclient.GetAction:
  1190. return true, nil, errors.NewNotFound(unversioned.GroupResource{}, "")
  1191. case testclient.DeleteAction:
  1192. return true, nil, nil
  1193. }
  1194. return false, nil, nil
  1195. })
  1196. err := Rename(fake, rcExisting, rc.Name)
  1197. if err != nil {
  1198. t.Fatal(err)
  1199. }
  1200. for j, action := range fake.Actions() {
  1201. if e, a := expectedActions[j], action.GetVerb(); e != a {
  1202. t.Errorf("unexpected action: expected %s, got %s", e, a)
  1203. }
  1204. }
  1205. }
  1206. func TestFindSourceController(t *testing.T) {
  1207. ctrl1 := api.ReplicationController{
  1208. ObjectMeta: api.ObjectMeta{
  1209. Name: "foo",
  1210. Annotations: map[string]string{
  1211. sourceIdAnnotation: "bar:1234",
  1212. },
  1213. },
  1214. }
  1215. ctrl2 := api.ReplicationController{
  1216. ObjectMeta: api.ObjectMeta{
  1217. Name: "bar",
  1218. Annotations: map[string]string{
  1219. sourceIdAnnotation: "foo:12345",
  1220. },
  1221. },
  1222. }
  1223. ctrl3 := api.ReplicationController{
  1224. ObjectMeta: api.ObjectMeta{
  1225. Annotations: map[string]string{
  1226. sourceIdAnnotation: "baz:45667",
  1227. },
  1228. },
  1229. }
  1230. tests := []struct {
  1231. list *api.ReplicationControllerList
  1232. expectedController *api.ReplicationController
  1233. err error
  1234. name string
  1235. expectError bool
  1236. }{
  1237. {
  1238. list: &api.ReplicationControllerList{},
  1239. expectError: true,
  1240. },
  1241. {
  1242. list: &api.ReplicationControllerList{
  1243. Items: []api.ReplicationController{ctrl1},
  1244. },
  1245. name: "foo",
  1246. expectError: true,
  1247. },
  1248. {
  1249. list: &api.ReplicationControllerList{
  1250. Items: []api.ReplicationController{ctrl1},
  1251. },
  1252. name: "bar",
  1253. expectedController: &ctrl1,
  1254. },
  1255. {
  1256. list: &api.ReplicationControllerList{
  1257. Items: []api.ReplicationController{ctrl1, ctrl2},
  1258. },
  1259. name: "bar",
  1260. expectedController: &ctrl1,
  1261. },
  1262. {
  1263. list: &api.ReplicationControllerList{
  1264. Items: []api.ReplicationController{ctrl1, ctrl2},
  1265. },
  1266. name: "foo",
  1267. expectedController: &ctrl2,
  1268. },
  1269. {
  1270. list: &api.ReplicationControllerList{
  1271. Items: []api.ReplicationController{ctrl1, ctrl2, ctrl3},
  1272. },
  1273. name: "baz",
  1274. expectedController: &ctrl3,
  1275. },
  1276. }
  1277. for _, test := range tests {
  1278. fakeClient := testclient.NewSimpleFake(test.list)
  1279. ctrl, err := FindSourceController(fakeClient, "default", test.name)
  1280. if test.expectError && err == nil {
  1281. t.Errorf("unexpected non-error")
  1282. }
  1283. if !test.expectError && err != nil {
  1284. t.Errorf("unexpected error")
  1285. }
  1286. if !reflect.DeepEqual(ctrl, test.expectedController) {
  1287. t.Errorf("expected:\n%v\ngot:\n%v\n", test.expectedController, ctrl)
  1288. }
  1289. }
  1290. }
  1291. func TestUpdateExistingReplicationController(t *testing.T) {
  1292. tests := []struct {
  1293. rc *api.ReplicationController
  1294. name string
  1295. deploymentKey string
  1296. deploymentValue string
  1297. expectedRc *api.ReplicationController
  1298. expectErr bool
  1299. }{
  1300. {
  1301. rc: &api.ReplicationController{
  1302. Spec: api.ReplicationControllerSpec{
  1303. Template: &api.PodTemplateSpec{},
  1304. },
  1305. },
  1306. name: "foo",
  1307. deploymentKey: "dk",
  1308. deploymentValue: "some-hash",
  1309. expectedRc: &api.ReplicationController{
  1310. ObjectMeta: api.ObjectMeta{
  1311. Annotations: map[string]string{
  1312. "kubectl.kubernetes.io/next-controller-id": "foo",
  1313. },
  1314. },
  1315. Spec: api.ReplicationControllerSpec{
  1316. Selector: map[string]string{
  1317. "dk": "some-hash",
  1318. },
  1319. Template: &api.PodTemplateSpec{
  1320. ObjectMeta: api.ObjectMeta{
  1321. Labels: map[string]string{
  1322. "dk": "some-hash",
  1323. },
  1324. },
  1325. },
  1326. },
  1327. },
  1328. },
  1329. {
  1330. rc: &api.ReplicationController{
  1331. Spec: api.ReplicationControllerSpec{
  1332. Template: &api.PodTemplateSpec{
  1333. ObjectMeta: api.ObjectMeta{
  1334. Labels: map[string]string{
  1335. "dk": "some-other-hash",
  1336. },
  1337. },
  1338. },
  1339. Selector: map[string]string{
  1340. "dk": "some-other-hash",
  1341. },
  1342. },
  1343. },
  1344. name: "foo",
  1345. deploymentKey: "dk",
  1346. deploymentValue: "some-hash",
  1347. expectedRc: &api.ReplicationController{
  1348. ObjectMeta: api.ObjectMeta{
  1349. Annotations: map[string]string{
  1350. "kubectl.kubernetes.io/next-controller-id": "foo",
  1351. },
  1352. },
  1353. Spec: api.ReplicationControllerSpec{
  1354. Selector: map[string]string{
  1355. "dk": "some-other-hash",
  1356. },
  1357. Template: &api.PodTemplateSpec{
  1358. ObjectMeta: api.ObjectMeta{
  1359. Labels: map[string]string{
  1360. "dk": "some-other-hash",
  1361. },
  1362. },
  1363. },
  1364. },
  1365. },
  1366. },
  1367. }
  1368. for _, test := range tests {
  1369. buffer := &bytes.Buffer{}
  1370. fakeClient := testclient.NewSimpleFake(test.expectedRc)
  1371. rc, err := UpdateExistingReplicationController(fakeClient, test.rc, "default", test.name, test.deploymentKey, test.deploymentValue, buffer)
  1372. if !reflect.DeepEqual(rc, test.expectedRc) {
  1373. t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.expectedRc, rc)
  1374. }
  1375. if test.expectErr && err == nil {
  1376. t.Errorf("unexpected non-error")
  1377. }
  1378. if !test.expectErr && err != nil {
  1379. t.Errorf("unexpected error: %v", err)
  1380. }
  1381. }
  1382. }
  1383. func TestUpdateRcWithRetries(t *testing.T) {
  1384. codec := testapi.Default.Codec()
  1385. rc := &api.ReplicationController{
  1386. ObjectMeta: api.ObjectMeta{Name: "rc",
  1387. Labels: map[string]string{
  1388. "foo": "bar",
  1389. },
  1390. },
  1391. Spec: api.ReplicationControllerSpec{
  1392. Selector: map[string]string{
  1393. "foo": "bar",
  1394. },
  1395. Template: &api.PodTemplateSpec{
  1396. ObjectMeta: api.ObjectMeta{
  1397. Labels: map[string]string{
  1398. "foo": "bar",
  1399. },
  1400. },
  1401. Spec: apitesting.DeepEqualSafePodSpec(),
  1402. },
  1403. },
  1404. }
  1405. // Test end to end updating of the rc with retries. Essentially make sure the update handler
  1406. // sees the right updates, failures in update/get are handled properly, and that the updated
  1407. // rc with new resource version is returned to the caller. Without any of these rollingupdate
  1408. // will fail cryptically.
  1409. newRc := *rc
  1410. newRc.ResourceVersion = "2"
  1411. newRc.Spec.Selector["baz"] = "foobar"
  1412. header := http.Header{}
  1413. header.Set("Content-Type", runtime.ContentTypeJSON)
  1414. updates := []*http.Response{
  1415. {StatusCode: 409, Header: header, Body: objBody(codec, &api.ReplicationController{})}, // conflict
  1416. {StatusCode: 409, Header: header, Body: objBody(codec, &api.ReplicationController{})}, // conflict
  1417. {StatusCode: 200, Header: header, Body: objBody(codec, &newRc)},
  1418. }
  1419. gets := []*http.Response{
  1420. {StatusCode: 500, Header: header, Body: objBody(codec, &api.ReplicationController{})},
  1421. {StatusCode: 200, Header: header, Body: objBody(codec, rc)},
  1422. }
  1423. fakeClient := &fake.RESTClient{
  1424. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  1425. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  1426. switch p, m := req.URL.Path, req.Method; {
  1427. case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "PUT":
  1428. update := updates[0]
  1429. updates = updates[1:]
  1430. // We should always get an update with a valid rc even when the get fails. The rc should always
  1431. // contain the update.
  1432. if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !reflect.DeepEqual(rc, c) {
  1433. t.Errorf("Unexpected update body, got %+v expected %+v", c, rc)
  1434. } else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" {
  1435. t.Errorf("Expected selector label update, got %+v", c.Spec.Selector)
  1436. } else {
  1437. delete(c.Spec.Selector, "baz")
  1438. }
  1439. return update, nil
  1440. case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "GET":
  1441. get := gets[0]
  1442. gets = gets[1:]
  1443. return get, nil
  1444. default:
  1445. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  1446. return nil, nil
  1447. }
  1448. }),
  1449. }
  1450. clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}
  1451. client := client.NewOrDie(clientConfig)
  1452. client.Client = fakeClient.Client
  1453. if rc, err := updateRcWithRetries(
  1454. client, "default", rc, func(c *api.ReplicationController) {
  1455. c.Spec.Selector["baz"] = "foobar"
  1456. }); err != nil {
  1457. t.Errorf("unexpected error: %v", err)
  1458. } else if sel, ok := rc.Spec.Selector["baz"]; !ok || sel != "foobar" || rc.ResourceVersion != "2" {
  1459. t.Errorf("Expected updated rc, got %+v", rc)
  1460. }
  1461. if len(updates) != 0 || len(gets) != 0 {
  1462. t.Errorf("Remaining updates %#v gets %#v", updates, gets)
  1463. }
  1464. }
  1465. func readOrDie(t *testing.T, req *http.Request, codec runtime.Codec) runtime.Object {
  1466. data, err := ioutil.ReadAll(req.Body)
  1467. if err != nil {
  1468. t.Errorf("Error reading: %v", err)
  1469. t.FailNow()
  1470. }
  1471. obj, err := runtime.Decode(codec, data)
  1472. if err != nil {
  1473. t.Errorf("error decoding: %v", err)
  1474. t.FailNow()
  1475. }
  1476. return obj
  1477. }
  1478. func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
  1479. return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
  1480. }
  1481. func TestAddDeploymentHash(t *testing.T) {
  1482. buf := &bytes.Buffer{}
  1483. codec := testapi.Default.Codec()
  1484. rc := &api.ReplicationController{
  1485. ObjectMeta: api.ObjectMeta{Name: "rc"},
  1486. Spec: api.ReplicationControllerSpec{
  1487. Selector: map[string]string{
  1488. "foo": "bar",
  1489. },
  1490. Template: &api.PodTemplateSpec{
  1491. ObjectMeta: api.ObjectMeta{
  1492. Labels: map[string]string{
  1493. "foo": "bar",
  1494. },
  1495. },
  1496. },
  1497. },
  1498. }
  1499. podList := &api.PodList{
  1500. Items: []api.Pod{
  1501. {ObjectMeta: api.ObjectMeta{Name: "foo"}},
  1502. {ObjectMeta: api.ObjectMeta{Name: "bar"}},
  1503. {ObjectMeta: api.ObjectMeta{Name: "baz"}},
  1504. },
  1505. }
  1506. seen := sets.String{}
  1507. updatedRc := false
  1508. fakeClient := &fake.RESTClient{
  1509. NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
  1510. Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
  1511. header := http.Header{}
  1512. header.Set("Content-Type", runtime.ContentTypeJSON)
  1513. switch p, m := req.URL.Path, req.Method; {
  1514. case p == testapi.Default.ResourcePath("pods", "default", "") && m == "GET":
  1515. if req.URL.RawQuery != "labelSelector=foo%3Dbar" {
  1516. t.Errorf("Unexpected query string: %s", req.URL.RawQuery)
  1517. }
  1518. return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, podList)}, nil
  1519. case p == testapi.Default.ResourcePath("pods", "default", "foo") && m == "PUT":
  1520. seen.Insert("foo")
  1521. obj := readOrDie(t, req, codec)
  1522. podList.Items[0] = *(obj.(*api.Pod))
  1523. return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[0])}, nil
  1524. case p == testapi.Default.ResourcePath("pods", "default", "bar") && m == "PUT":
  1525. seen.Insert("bar")
  1526. obj := readOrDie(t, req, codec)
  1527. podList.Items[1] = *(obj.(*api.Pod))
  1528. return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[1])}, nil
  1529. case p == testapi.Default.ResourcePath("pods", "default", "baz") && m == "PUT":
  1530. seen.Insert("baz")
  1531. obj := readOrDie(t, req, codec)
  1532. podList.Items[2] = *(obj.(*api.Pod))
  1533. return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[2])}, nil
  1534. case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "PUT":
  1535. updatedRc = true
  1536. return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, rc)}, nil
  1537. default:
  1538. t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
  1539. return nil, nil
  1540. }
  1541. }),
  1542. }
  1543. clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}
  1544. client := client.NewOrDie(clientConfig)
  1545. client.Client = fakeClient.Client
  1546. if _, err := AddDeploymentKeyToReplicationController(rc, client, "dk", "hash", api.NamespaceDefault, buf); err != nil {
  1547. t.Errorf("unexpected error: %v", err)
  1548. }
  1549. for _, pod := range podList.Items {
  1550. if !seen.Has(pod.Name) {
  1551. t.Errorf("Missing update for pod: %s", pod.Name)
  1552. }
  1553. }
  1554. if !updatedRc {
  1555. t.Errorf("Failed to update replication controller with new labels")
  1556. }
  1557. }
  1558. func TestRollingUpdater_readyPods(t *testing.T) {
  1559. now := unversioned.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC)
  1560. mkpod := func(owner *api.ReplicationController, ready bool, readyTime unversioned.Time) *api.Pod {
  1561. labels := map[string]string{}
  1562. for k, v := range owner.Spec.Selector {
  1563. labels[k] = v
  1564. }
  1565. status := api.ConditionTrue
  1566. if !ready {
  1567. status = api.ConditionFalse
  1568. }
  1569. return &api.Pod{
  1570. ObjectMeta: api.ObjectMeta{
  1571. Name: "pod",
  1572. Labels: labels,
  1573. },
  1574. Status: api.PodStatus{
  1575. Conditions: []api.PodCondition{
  1576. {
  1577. Type: api.PodReady,
  1578. Status: status,
  1579. LastTransitionTime: readyTime,
  1580. },
  1581. },
  1582. },
  1583. }
  1584. }
  1585. tests := []struct {
  1586. oldRc *api.ReplicationController
  1587. newRc *api.ReplicationController
  1588. // expectated old/new ready counts
  1589. oldReady int32
  1590. newReady int32
  1591. // pods owned by the rcs; indicate whether they're ready
  1592. oldPods []bool
  1593. newPods []bool
  1594. // specify additional time to wait for deployment to wait on top of the
  1595. // pod ready time
  1596. minReadySeconds int32
  1597. podReadyTimeFn func() unversioned.Time
  1598. nowFn func() unversioned.Time
  1599. }{
  1600. {
  1601. oldRc: oldRc(4, 4),
  1602. newRc: newRc(4, 4),
  1603. oldReady: 4,
  1604. newReady: 2,
  1605. oldPods: []bool{
  1606. true,
  1607. true,
  1608. true,
  1609. true,
  1610. },
  1611. newPods: []bool{
  1612. true,
  1613. false,
  1614. true,
  1615. false,
  1616. },
  1617. },
  1618. {
  1619. oldRc: oldRc(4, 4),
  1620. newRc: newRc(4, 4),
  1621. oldReady: 0,
  1622. newReady: 1,
  1623. oldPods: []bool{
  1624. false,
  1625. },
  1626. newPods: []bool{
  1627. true,
  1628. },
  1629. },
  1630. {
  1631. oldRc: oldRc(4, 4),
  1632. newRc: newRc(4, 4),
  1633. oldReady: 1,
  1634. newReady: 0,
  1635. oldPods: []bool{
  1636. true,
  1637. },
  1638. newPods: []bool{
  1639. false,
  1640. },
  1641. },
  1642. {
  1643. oldRc: oldRc(4, 4),
  1644. newRc: newRc(4, 4),
  1645. oldReady: 0,
  1646. newReady: 0,
  1647. oldPods: []bool{
  1648. true,
  1649. },
  1650. newPods: []bool{
  1651. true,
  1652. },
  1653. minReadySeconds: 5,
  1654. nowFn: func() unversioned.Time { return now },
  1655. },
  1656. {
  1657. oldRc: oldRc(4, 4),
  1658. newRc: newRc(4, 4),
  1659. oldReady: 1,
  1660. newReady: 1,
  1661. oldPods: []bool{
  1662. true,
  1663. },
  1664. newPods: []bool{
  1665. true,
  1666. },
  1667. minReadySeconds: 5,
  1668. nowFn: func() unversioned.Time { return unversioned.Time{Time: now.Add(time.Duration(6 * time.Second))} },
  1669. podReadyTimeFn: func() unversioned.Time { return now },
  1670. },
  1671. }
  1672. for i, test := range tests {
  1673. t.Logf("evaluating test %d", i)
  1674. if test.nowFn == nil {
  1675. test.nowFn = func() unversioned.Time { return now }
  1676. }
  1677. if test.podReadyTimeFn == nil {
  1678. test.podReadyTimeFn = test.nowFn
  1679. }
  1680. // Populate the fake client with pods associated with their owners.
  1681. pods := []runtime.Object{}
  1682. for _, ready := range test.oldPods {
  1683. pods = append(pods, mkpod(test.oldRc, ready, test.podReadyTimeFn()))
  1684. }
  1685. for _, ready := range test.newPods {
  1686. pods = append(pods, mkpod(test.newRc, ready, test.podReadyTimeFn()))
  1687. }
  1688. client := testclient.NewSimpleFake(pods...)
  1689. updater := &RollingUpdater{
  1690. ns: "default",
  1691. c: client,
  1692. nowFn: test.nowFn,
  1693. }
  1694. oldReady, newReady, err := updater.readyPods(test.oldRc, test.newRc, test.minReadySeconds)
  1695. if err != nil {
  1696. t.Errorf("unexpected error: %v", err)
  1697. }
  1698. if e, a := test.oldReady, oldReady; e != a {
  1699. t.Errorf("expected old ready %d, got %d", e, a)
  1700. }
  1701. if e, a := test.newReady, newReady; e != a {
  1702. t.Errorf("expected new ready %d, got %d", e, a)
  1703. }
  1704. }
  1705. }