123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package kubectl
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "reflect"
- "testing"
- "time"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/errors"
- "k8s.io/kubernetes/pkg/api/testapi"
- apitesting "k8s.io/kubernetes/pkg/api/testing"
- "k8s.io/kubernetes/pkg/api/unversioned"
- "k8s.io/kubernetes/pkg/client/restclient"
- client "k8s.io/kubernetes/pkg/client/unversioned"
- "k8s.io/kubernetes/pkg/client/unversioned/fake"
- "k8s.io/kubernetes/pkg/client/unversioned/testclient"
- "k8s.io/kubernetes/pkg/runtime"
- "k8s.io/kubernetes/pkg/util/intstr"
- "k8s.io/kubernetes/pkg/util/sets"
- )
- func oldRc(replicas int, original int) *api.ReplicationController {
- return &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "foo-v1",
- UID: "7764ae47-9092-11e4-8393-42010af018ff",
- Annotations: map[string]string{
- originalReplicasAnnotation: fmt.Sprintf("%d", original),
- },
- },
- Spec: api.ReplicationControllerSpec{
- Replicas: int32(replicas),
- Selector: map[string]string{"version": "v1"},
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Name: "foo-v1",
- Labels: map[string]string{"version": "v1"},
- },
- },
- },
- Status: api.ReplicationControllerStatus{
- Replicas: int32(replicas),
- },
- }
- }
- func newRc(replicas int, desired int) *api.ReplicationController {
- rc := oldRc(replicas, replicas)
- rc.Spec.Template = &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Name: "foo-v2",
- Labels: map[string]string{"version": "v2"},
- },
- }
- rc.Spec.Selector = map[string]string{"version": "v2"}
- rc.ObjectMeta = api.ObjectMeta{
- Name: "foo-v2",
- Annotations: map[string]string{
- desiredReplicasAnnotation: fmt.Sprintf("%d", desired),
- sourceIdAnnotation: "foo-v1:7764ae47-9092-11e4-8393-42010af018ff",
- },
- }
- return rc
- }
- // TestUpdate performs complex scenario testing for rolling updates. It
- // provides fine grained control over the states for each update interval to
- // allow the expression of as many edge cases as possible.
- func TestUpdate(t *testing.T) {
- // up represents a simulated scale up event and expectation
- type up struct {
- // to is the expected replica count for a scale-up
- to int
- }
- // down represents a simulated scale down event and expectation
- type down struct {
- // oldReady is the number of oldRc replicas which will be seen
- // as ready during the scale down attempt
- oldReady int
- // newReady is the number of newRc replicas which will be seen
- // as ready during the scale up attempt
- newReady int
- // to is the expected replica count for the scale down
- to int
- // noop and to are mutually exclusive; if noop is true, that means for
- // this down event, no scaling attempt should be made (for example, if
- // by scaling down, the readiness minimum would be crossed.)
- noop bool
- }
- tests := []struct {
- name string
- // oldRc is the "from" deployment
- oldRc *api.ReplicationController
- // newRc is the "to" deployment
- newRc *api.ReplicationController
- // whether newRc existed (false means it was created)
- newRcExists bool
- maxUnavail intstr.IntOrString
- maxSurge intstr.IntOrString
- // expected is the sequence of up/down events that will be simulated and
- // verified
- expected []interface{}
- // output is the expected textual output written
- output string
- }{
- {
- name: "10->10 30/0 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("30%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 10, newReady: 0, to: 7},
- up{3},
- down{oldReady: 7, newReady: 3, to: 4},
- up{6},
- down{oldReady: 4, newReady: 6, to: 1},
- up{9},
- down{oldReady: 1, newReady: 9, to: 0},
- up{10},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 9
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 10
- `,
- },
- {
- name: "10->10 30/0 delayed readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("30%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 10, newReady: 0, to: 7},
- up{3},
- down{oldReady: 7, newReady: 0, noop: true},
- down{oldReady: 7, newReady: 1, to: 6},
- up{4},
- down{oldReady: 6, newReady: 4, to: 3},
- up{7},
- down{oldReady: 3, newReady: 7, to: 0},
- up{10},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 6
- Scaling foo-v2 up to 4
- Scaling foo-v1 down to 3
- Scaling foo-v2 up to 7
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 10
- `,
- }, {
- name: "10->10 30/0 fast readiness, continuation",
- oldRc: oldRc(7, 10),
- newRc: newRc(3, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("30%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 7, newReady: 3, to: 4},
- up{6},
- down{oldReady: 4, newReady: 6, to: 1},
- up{9},
- down{oldReady: 1, newReady: 9, to: 0},
- up{10},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 9
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 10
- `,
- }, {
- name: "10->10 30/0 fast readiness, continued after restart which prevented first scale-up",
- oldRc: oldRc(7, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("30%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 7, newReady: 0, noop: true},
- up{3},
- down{oldReady: 7, newReady: 3, to: 4},
- up{6},
- down{oldReady: 4, newReady: 6, to: 1},
- up{9},
- down{oldReady: 1, newReady: 9, to: 0},
- up{10},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 9
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 10
- `,
- }, {
- name: "10->10 0/30 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("30%"),
- expected: []interface{}{
- up{3},
- down{oldReady: 10, newReady: 3, to: 7},
- up{6},
- down{oldReady: 7, newReady: 6, to: 4},
- up{9},
- down{oldReady: 4, newReady: 9, to: 1},
- up{10},
- down{oldReady: 1, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 9
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 0/30 delayed readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("30%"),
- expected: []interface{}{
- up{3},
- down{oldReady: 10, newReady: 0, noop: true},
- down{oldReady: 10, newReady: 1, to: 9},
- up{4},
- down{oldReady: 9, newReady: 3, to: 7},
- up{6},
- down{oldReady: 7, newReady: 6, to: 4},
- up{9},
- down{oldReady: 4, newReady: 9, to: 1},
- up{10},
- down{oldReady: 1, newReady: 9, noop: true},
- down{oldReady: 1, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 9
- Scaling foo-v2 up to 4
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 9
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 10/20 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("20%"),
- expected: []interface{}{
- up{2},
- down{oldReady: 10, newReady: 2, to: 7},
- up{5},
- down{oldReady: 7, newReady: 5, to: 4},
- up{8},
- down{oldReady: 4, newReady: 8, to: 1},
- up{10},
- down{oldReady: 1, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 2
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 5
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 8
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 10/20 delayed readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("20%"),
- expected: []interface{}{
- up{2},
- down{oldReady: 10, newReady: 2, to: 7},
- up{5},
- down{oldReady: 7, newReady: 4, to: 5},
- up{7},
- down{oldReady: 5, newReady: 4, noop: true},
- down{oldReady: 5, newReady: 7, to: 2},
- up{10},
- down{oldReady: 2, newReady: 9, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 2
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 5
- Scaling foo-v1 down to 5
- Scaling foo-v2 up to 7
- Scaling foo-v1 down to 2
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 10/20 fast readiness continued after restart which prevented first scale-down",
- oldRc: oldRc(10, 10),
- newRc: newRc(2, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("20%"),
- expected: []interface{}{
- down{oldReady: 10, newReady: 2, to: 7},
- up{5},
- down{oldReady: 7, newReady: 5, to: 4},
- up{8},
- down{oldReady: 4, newReady: 8, to: 1},
- up{10},
- down{oldReady: 1, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 7
- Scaling foo-v2 up to 5
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 8
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 0/100 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("100%"),
- expected: []interface{}{
- up{10},
- down{oldReady: 10, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 0/100 delayed readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("100%"),
- expected: []interface{}{
- up{10},
- down{oldReady: 10, newReady: 0, noop: true},
- down{oldReady: 10, newReady: 2, to: 8},
- down{oldReady: 8, newReady: 7, to: 3},
- down{oldReady: 3, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 8
- Scaling foo-v1 down to 3
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 100/0 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("100%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 10, newReady: 0, to: 0},
- up{10},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 10
- `,
- }, {
- name: "1->1 25/25 maintain minimum availability",
- oldRc: oldRc(1, 1),
- newRc: newRc(0, 1),
- newRcExists: false,
- maxUnavail: intstr.FromString("25%"),
- maxSurge: intstr.FromString("25%"),
- expected: []interface{}{
- up{1},
- down{oldReady: 1, newReady: 0, noop: true},
- down{oldReady: 1, newReady: 1, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "1->1 0/10 delayed readiness",
- oldRc: oldRc(1, 1),
- newRc: newRc(0, 1),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("10%"),
- expected: []interface{}{
- up{1},
- down{oldReady: 1, newReady: 0, noop: true},
- down{oldReady: 1, newReady: 1, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "1->1 10/10 delayed readiness",
- oldRc: oldRc(1, 1),
- newRc: newRc(0, 1),
- newRcExists: false,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("10%"),
- expected: []interface{}{
- up{1},
- down{oldReady: 1, newReady: 0, noop: true},
- down{oldReady: 1, newReady: 1, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "3->3 1/1 fast readiness (absolute values)",
- oldRc: oldRc(3, 3),
- newRc: newRc(0, 3),
- newRcExists: false,
- maxUnavail: intstr.FromInt(0),
- maxSurge: intstr.FromInt(1),
- expected: []interface{}{
- up{1},
- down{oldReady: 3, newReady: 1, to: 2},
- up{2},
- down{oldReady: 2, newReady: 2, to: 1},
- up{3},
- down{oldReady: 1, newReady: 3, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 2
- Scaling foo-v2 up to 2
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 3
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->10 0/20 fast readiness, continued after restart which resulted in partial first scale-up",
- oldRc: oldRc(6, 10),
- newRc: newRc(5, 10),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("20%"),
- expected: []interface{}{
- up{6},
- down{oldReady: 6, newReady: 6, to: 4},
- up{8},
- down{oldReady: 4, newReady: 8, to: 2},
- up{10},
- down{oldReady: 1, newReady: 10, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 6
- Scaling foo-v1 down to 4
- Scaling foo-v2 up to 8
- Scaling foo-v1 down to 2
- Scaling foo-v2 up to 10
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "10->20 0/300 fast readiness",
- oldRc: oldRc(10, 10),
- newRc: newRc(0, 20),
- newRcExists: false,
- maxUnavail: intstr.FromString("0%"),
- maxSurge: intstr.FromString("300%"),
- expected: []interface{}{
- up{20},
- down{oldReady: 10, newReady: 20, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 20
- Scaling foo-v1 down to 0
- `,
- }, {
- name: "1->1 0/1 scale down unavailable rc to a ready rc (rollback)",
- oldRc: oldRc(1, 1),
- newRc: newRc(1, 1),
- newRcExists: true,
- maxUnavail: intstr.FromInt(0),
- maxSurge: intstr.FromInt(1),
- expected: []interface{}{
- up{1},
- down{oldReady: 0, newReady: 1, to: 0},
- },
- output: `Continuing update with existing controller foo-v2.
- 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)
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "3->0 1/1 desired 0 (absolute values)",
- oldRc: oldRc(3, 3),
- newRc: newRc(0, 0),
- newRcExists: true,
- maxUnavail: intstr.FromInt(1),
- maxSurge: intstr.FromInt(1),
- expected: []interface{}{
- down{oldReady: 3, newReady: 0, to: 0},
- },
- output: `Continuing update with existing controller foo-v2.
- 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)
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "3->0 10/10 desired 0 (percentages)",
- oldRc: oldRc(3, 3),
- newRc: newRc(0, 0),
- newRcExists: true,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("10%"),
- expected: []interface{}{
- down{oldReady: 3, newReady: 0, to: 0},
- },
- output: `Continuing update with existing controller foo-v2.
- 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)
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "3->0 10/10 desired 0 (create new RC)",
- oldRc: oldRc(3, 3),
- newRc: newRc(0, 0),
- newRcExists: false,
- maxUnavail: intstr.FromString("10%"),
- maxSurge: intstr.FromString("10%"),
- expected: []interface{}{
- down{oldReady: 3, newReady: 0, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "0->0 1/1 desired 0 (absolute values)",
- oldRc: oldRc(0, 0),
- newRc: newRc(0, 0),
- newRcExists: true,
- maxUnavail: intstr.FromInt(1),
- maxSurge: intstr.FromInt(1),
- expected: []interface{}{
- down{oldReady: 0, newReady: 0, to: 0},
- },
- output: `Continuing update with existing controller foo-v2.
- 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)
- `,
- }, {
- name: "30->2 50%/0",
- oldRc: oldRc(30, 30),
- newRc: newRc(0, 2),
- newRcExists: false,
- maxUnavail: intstr.FromString("50%"),
- maxSurge: intstr.FromInt(0),
- expected: []interface{}{
- down{oldReady: 30, newReady: 0, to: 1},
- up{1},
- down{oldReady: 1, newReady: 2, to: 0},
- up{2},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 2
- `,
- },
- {
- name: "2->2 1/0 blocked oldRc",
- oldRc: oldRc(2, 2),
- newRc: newRc(0, 2),
- newRcExists: false,
- maxUnavail: intstr.FromInt(1),
- maxSurge: intstr.FromInt(0),
- expected: []interface{}{
- down{oldReady: 1, newReady: 0, to: 1},
- up{1},
- down{oldReady: 1, newReady: 1, to: 0},
- up{2},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 2
- `,
- },
- {
- name: "1->1 1/0 allow maxUnavailability",
- oldRc: oldRc(1, 1),
- newRc: newRc(0, 1),
- newRcExists: false,
- maxUnavail: intstr.FromString("1%"),
- maxSurge: intstr.FromInt(0),
- expected: []interface{}{
- down{oldReady: 1, newReady: 0, to: 0},
- up{1},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 1
- `,
- },
- {
- name: "1->2 25/25 complex asymmetric deployment",
- oldRc: oldRc(1, 1),
- newRc: newRc(0, 2),
- newRcExists: false,
- maxUnavail: intstr.FromString("25%"),
- maxSurge: intstr.FromString("25%"),
- expected: []interface{}{
- up{2},
- down{oldReady: 1, newReady: 2, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 2
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "2->2 25/1 maxSurge trumps maxUnavailable",
- oldRc: oldRc(2, 2),
- newRc: newRc(0, 2),
- newRcExists: false,
- maxUnavail: intstr.FromString("25%"),
- maxSurge: intstr.FromString("1%"),
- expected: []interface{}{
- up{1},
- down{oldReady: 2, newReady: 1, to: 1},
- up{2},
- down{oldReady: 1, newReady: 2, to: 0},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 2
- Scaling foo-v1 down to 0
- `,
- },
- {
- name: "2->2 25/0 maxUnavailable resolves to zero, then one",
- oldRc: oldRc(2, 2),
- newRc: newRc(0, 2),
- newRcExists: false,
- maxUnavail: intstr.FromString("25%"),
- maxSurge: intstr.FromString("0%"),
- expected: []interface{}{
- down{oldReady: 2, newReady: 0, to: 1},
- up{1},
- down{oldReady: 1, newReady: 1, to: 0},
- up{2},
- },
- output: `Created foo-v2
- 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)
- Scaling foo-v1 down to 1
- Scaling foo-v2 up to 1
- Scaling foo-v1 down to 0
- Scaling foo-v2 up to 2
- `,
- },
- }
- for i, test := range tests {
- // Extract expectations into some makeshift FIFOs so they can be returned
- // in the correct order from the right places. This lets scale downs be
- // expressed a single event even though the data is used from multiple
- // interface calls.
- oldReady := []int{}
- newReady := []int{}
- upTo := []int{}
- downTo := []int{}
- for _, event := range test.expected {
- switch e := event.(type) {
- case down:
- oldReady = append(oldReady, e.oldReady)
- newReady = append(newReady, e.newReady)
- if !e.noop {
- downTo = append(downTo, e.to)
- }
- case up:
- upTo = append(upTo, e.to)
- }
- }
- // Make a way to get the next item from our FIFOs. Returns -1 if the array
- // is empty.
- next := func(s *[]int) int {
- slice := *s
- v := -1
- if len(slice) > 0 {
- v = slice[0]
- if len(slice) > 1 {
- *s = slice[1:]
- } else {
- *s = []int{}
- }
- }
- return v
- }
- t.Logf("running test %d (%s) (up: %v, down: %v, oldReady: %v, newReady: %v)", i, test.name, upTo, downTo, oldReady, newReady)
- updater := &RollingUpdater{
- ns: "default",
- scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
- // Return a scale up or scale down expectation depending on the rc,
- // and throw errors if there is no expectation expressed for this
- // call.
- expected := -1
- switch {
- case rc == test.newRc:
- t.Logf("scaling up %s to %d", rc.Name, rc.Spec.Replicas)
- expected = next(&upTo)
- case rc == test.oldRc:
- t.Logf("scaling down %s to %d", rc.Name, rc.Spec.Replicas)
- expected = next(&downTo)
- }
- if expected == -1 {
- t.Fatalf("unexpected scale of %s to %d", rc.Name, rc.Spec.Replicas)
- } else if e, a := expected, int(rc.Spec.Replicas); e != a {
- t.Fatalf("expected scale of %s to %d, got %d", rc.Name, e, a)
- }
- // Simulate the scale.
- rc.Status.Replicas = rc.Spec.Replicas
- return rc, nil
- },
- getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
- // Simulate a create vs. update of an existing controller.
- return test.newRc, test.newRcExists, nil
- },
- cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
- return nil
- },
- }
- // Set up a mock readiness check which handles the test assertions.
- updater.getReadyPods = func(oldRc, newRc *api.ReplicationController, minReadySecondsDeadline int32) (int32, int32, error) {
- // Return simulated readiness, and throw an error if this call has no
- // expectations defined.
- oldReady := next(&oldReady)
- newReady := next(&newReady)
- if oldReady == -1 || newReady == -1 {
- t.Fatalf("unexpected getReadyPods call for:\noldRc: %#v\nnewRc: %#v", oldRc, newRc)
- }
- return int32(oldReady), int32(newReady), nil
- }
- var buffer bytes.Buffer
- config := &RollingUpdaterConfig{
- Out: &buffer,
- OldRc: test.oldRc,
- NewRc: test.newRc,
- UpdatePeriod: 0,
- Interval: time.Millisecond,
- Timeout: time.Millisecond,
- CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
- MaxUnavailable: test.maxUnavail,
- MaxSurge: test.maxSurge,
- }
- err := updater.Update(config)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if buffer.String() != test.output {
- t.Errorf("Bad output. expected:\n%s\ngot:\n%s", test.output, buffer.String())
- }
- }
- }
- // TestUpdate_progressTimeout ensures that an update which isn't making any
- // progress will eventually time out with a specified error.
- func TestUpdate_progressTimeout(t *testing.T) {
- oldRc := oldRc(2, 2)
- newRc := newRc(0, 2)
- updater := &RollingUpdater{
- ns: "default",
- scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
- // Do nothing.
- return rc, nil
- },
- getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
- return newRc, false, nil
- },
- cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
- return nil
- },
- }
- updater.getReadyPods = func(oldRc, newRc *api.ReplicationController, minReadySeconds int32) (int32, int32, error) {
- // Coerce a timeout by pods never becoming ready.
- return 0, 0, nil
- }
- var buffer bytes.Buffer
- config := &RollingUpdaterConfig{
- Out: &buffer,
- OldRc: oldRc,
- NewRc: newRc,
- UpdatePeriod: 0,
- Interval: time.Millisecond,
- Timeout: time.Millisecond,
- CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
- MaxUnavailable: intstr.FromInt(0),
- MaxSurge: intstr.FromInt(1),
- }
- err := updater.Update(config)
- if err == nil {
- t.Fatalf("expected an error")
- }
- if e, a := "timed out waiting for any update progress to be made", err.Error(); e != a {
- t.Fatalf("expected error message: %s, got: %s", e, a)
- }
- }
- func TestUpdate_assignOriginalAnnotation(t *testing.T) {
- oldRc := oldRc(1, 1)
- delete(oldRc.Annotations, originalReplicasAnnotation)
- newRc := newRc(1, 1)
- var updatedOldRc *api.ReplicationController
- fake := &testclient.Fake{}
- fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
- switch a := action.(type) {
- case testclient.GetAction:
- return true, oldRc, nil
- case testclient.UpdateAction:
- updatedOldRc = a.GetObject().(*api.ReplicationController)
- return true, updatedOldRc, nil
- }
- return false, nil, nil
- })
- updater := &RollingUpdater{
- c: fake,
- ns: "default",
- scaleAndWait: func(rc *api.ReplicationController, retry *RetryParams, wait *RetryParams) (*api.ReplicationController, error) {
- return rc, nil
- },
- getOrCreateTargetController: func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) {
- return newRc, false, nil
- },
- cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error {
- return nil
- },
- getReadyPods: func(oldRc, newRc *api.ReplicationController, minReadySeconds int32) (int32, int32, error) {
- return 1, 1, nil
- },
- }
- var buffer bytes.Buffer
- config := &RollingUpdaterConfig{
- Out: &buffer,
- OldRc: oldRc,
- NewRc: newRc,
- UpdatePeriod: 0,
- Interval: time.Millisecond,
- Timeout: time.Millisecond,
- CleanupPolicy: DeleteRollingUpdateCleanupPolicy,
- MaxUnavailable: intstr.FromString("100%"),
- }
- err := updater.Update(config)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if updatedOldRc == nil {
- t.Fatalf("expected rc to be updated")
- }
- if e, a := "1", updatedOldRc.Annotations[originalReplicasAnnotation]; e != a {
- t.Fatalf("expected annotation value %s, got %s", e, a)
- }
- }
- func TestRollingUpdater_multipleContainersInPod(t *testing.T) {
- tests := []struct {
- oldRc *api.ReplicationController
- newRc *api.ReplicationController
- container string
- image string
- deploymentKey string
- }{
- {
- oldRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "foo",
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "old",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "old",
- },
- },
- Spec: api.PodSpec{
- Containers: []api.Container{
- {
- Name: "container1",
- Image: "image1",
- },
- {
- Name: "container2",
- Image: "image2",
- },
- },
- },
- },
- },
- },
- newRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "foo",
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "old",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "old",
- },
- },
- Spec: api.PodSpec{
- Containers: []api.Container{
- {
- Name: "container1",
- Image: "newimage",
- },
- {
- Name: "container2",
- Image: "image2",
- },
- },
- },
- },
- },
- },
- container: "container1",
- image: "newimage",
- deploymentKey: "dk",
- },
- {
- oldRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "bar",
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "old",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "old",
- },
- },
- Spec: api.PodSpec{
- Containers: []api.Container{
- {
- Name: "container1",
- Image: "image1",
- },
- },
- },
- },
- },
- },
- newRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "bar",
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "old",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "old",
- },
- },
- Spec: api.PodSpec{
- Containers: []api.Container{
- {
- Name: "container1",
- Image: "newimage",
- },
- },
- },
- },
- },
- },
- container: "container1",
- image: "newimage",
- deploymentKey: "dk",
- },
- }
- for _, test := range tests {
- fake := &testclient.Fake{}
- fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
- switch action.(type) {
- case testclient.GetAction:
- return true, test.oldRc, nil
- }
- return false, nil, nil
- })
- codec := testapi.Default.Codec()
- deploymentHash, err := api.HashObject(test.newRc, codec)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- test.newRc.Spec.Selector[test.deploymentKey] = deploymentHash
- test.newRc.Spec.Template.Labels[test.deploymentKey] = deploymentHash
- test.newRc.Name = fmt.Sprintf("%s-%s", test.newRc.Name, deploymentHash)
- config := &NewControllerConfig{
- OldName: test.oldRc.ObjectMeta.Name,
- NewName: test.newRc.ObjectMeta.Name,
- Image: test.image,
- Container: test.container,
- DeploymentKey: test.deploymentKey,
- }
- updatedRc, err := CreateNewControllerFromCurrentController(fake, codec, config)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if !reflect.DeepEqual(updatedRc, test.newRc) {
- t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.newRc, updatedRc)
- }
- }
- }
- // TestRollingUpdater_cleanupWithClients ensures that the cleanup policy is
- // correctly implemented.
- func TestRollingUpdater_cleanupWithClients(t *testing.T) {
- rc := oldRc(2, 2)
- rcExisting := newRc(1, 3)
- tests := []struct {
- name string
- policy RollingUpdaterCleanupPolicy
- responses []runtime.Object
- expected []string
- }{
- {
- name: "preserve",
- policy: PreserveRollingUpdateCleanupPolicy,
- responses: []runtime.Object{rcExisting},
- expected: []string{
- "get",
- "update",
- "get",
- "get",
- },
- },
- {
- name: "delete",
- policy: DeleteRollingUpdateCleanupPolicy,
- responses: []runtime.Object{rcExisting},
- expected: []string{
- "get",
- "update",
- "get",
- "get",
- "delete",
- },
- },
- //{
- // This cases is separated to a standalone
- // TestRollingUpdater_cleanupWithClients_Rename. We have to do this
- // because the unversioned fake client is unable to delete objects.
- // TODO: uncomment this case when the unversioned fake client uses
- // pkg/client/testing/core.
- // {
- // name: "rename",
- // policy: RenameRollingUpdateCleanupPolicy,
- // responses: []runtime.Object{rcExisting},
- // expected: []string{
- // "get",
- // "update",
- // "get",
- // "get",
- // "delete",
- // "create",
- // "delete",
- // },
- // },
- //},
- }
- for _, test := range tests {
- fake := testclient.NewSimpleFake(test.responses...)
- updater := &RollingUpdater{
- ns: "default",
- c: fake,
- }
- config := &RollingUpdaterConfig{
- Out: ioutil.Discard,
- OldRc: rc,
- NewRc: rcExisting,
- UpdatePeriod: 0,
- Interval: time.Millisecond,
- Timeout: time.Millisecond,
- CleanupPolicy: test.policy,
- }
- err := updater.cleanupWithClients(rc, rcExisting, config)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if len(fake.Actions()) != len(test.expected) {
- t.Fatalf("%s: unexpected actions: %v, expected %v", test.name, fake.Actions(), test.expected)
- }
- for j, action := range fake.Actions() {
- if e, a := test.expected[j], action.GetVerb(); e != a {
- t.Errorf("%s: unexpected action: expected %s, got %s", test.name, e, a)
- }
- }
- }
- }
- // TestRollingUpdater_cleanupWithClients_Rename tests the rename cleanup policy. It's separated to
- // a standalone test because the unversioned fake client is unable to delete
- // objects.
- // TODO: move this test back to TestRollingUpdater_cleanupWithClients
- // when the fake client uses pkg/client/testing/core in the future.
- func TestRollingUpdater_cleanupWithClients_Rename(t *testing.T) {
- rc := oldRc(2, 2)
- rcExisting := newRc(1, 3)
- expectedActions := []string{"delete", "get", "create"}
- fake := &testclient.Fake{}
- fake.AddReactor("*", "*", func(action testclient.Action) (handled bool, ret runtime.Object, err error) {
- switch action.(type) {
- case testclient.CreateAction:
- return true, nil, nil
- case testclient.GetAction:
- return true, nil, errors.NewNotFound(unversioned.GroupResource{}, "")
- case testclient.DeleteAction:
- return true, nil, nil
- }
- return false, nil, nil
- })
- err := Rename(fake, rcExisting, rc.Name)
- if err != nil {
- t.Fatal(err)
- }
- for j, action := range fake.Actions() {
- if e, a := expectedActions[j], action.GetVerb(); e != a {
- t.Errorf("unexpected action: expected %s, got %s", e, a)
- }
- }
- }
- func TestFindSourceController(t *testing.T) {
- ctrl1 := api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "foo",
- Annotations: map[string]string{
- sourceIdAnnotation: "bar:1234",
- },
- },
- }
- ctrl2 := api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Name: "bar",
- Annotations: map[string]string{
- sourceIdAnnotation: "foo:12345",
- },
- },
- }
- ctrl3 := api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Annotations: map[string]string{
- sourceIdAnnotation: "baz:45667",
- },
- },
- }
- tests := []struct {
- list *api.ReplicationControllerList
- expectedController *api.ReplicationController
- err error
- name string
- expectError bool
- }{
- {
- list: &api.ReplicationControllerList{},
- expectError: true,
- },
- {
- list: &api.ReplicationControllerList{
- Items: []api.ReplicationController{ctrl1},
- },
- name: "foo",
- expectError: true,
- },
- {
- list: &api.ReplicationControllerList{
- Items: []api.ReplicationController{ctrl1},
- },
- name: "bar",
- expectedController: &ctrl1,
- },
- {
- list: &api.ReplicationControllerList{
- Items: []api.ReplicationController{ctrl1, ctrl2},
- },
- name: "bar",
- expectedController: &ctrl1,
- },
- {
- list: &api.ReplicationControllerList{
- Items: []api.ReplicationController{ctrl1, ctrl2},
- },
- name: "foo",
- expectedController: &ctrl2,
- },
- {
- list: &api.ReplicationControllerList{
- Items: []api.ReplicationController{ctrl1, ctrl2, ctrl3},
- },
- name: "baz",
- expectedController: &ctrl3,
- },
- }
- for _, test := range tests {
- fakeClient := testclient.NewSimpleFake(test.list)
- ctrl, err := FindSourceController(fakeClient, "default", test.name)
- if test.expectError && err == nil {
- t.Errorf("unexpected non-error")
- }
- if !test.expectError && err != nil {
- t.Errorf("unexpected error")
- }
- if !reflect.DeepEqual(ctrl, test.expectedController) {
- t.Errorf("expected:\n%v\ngot:\n%v\n", test.expectedController, ctrl)
- }
- }
- }
- func TestUpdateExistingReplicationController(t *testing.T) {
- tests := []struct {
- rc *api.ReplicationController
- name string
- deploymentKey string
- deploymentValue string
- expectedRc *api.ReplicationController
- expectErr bool
- }{
- {
- rc: &api.ReplicationController{
- Spec: api.ReplicationControllerSpec{
- Template: &api.PodTemplateSpec{},
- },
- },
- name: "foo",
- deploymentKey: "dk",
- deploymentValue: "some-hash",
- expectedRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Annotations: map[string]string{
- "kubectl.kubernetes.io/next-controller-id": "foo",
- },
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "some-hash",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "some-hash",
- },
- },
- },
- },
- },
- },
- {
- rc: &api.ReplicationController{
- Spec: api.ReplicationControllerSpec{
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "some-other-hash",
- },
- },
- },
- Selector: map[string]string{
- "dk": "some-other-hash",
- },
- },
- },
- name: "foo",
- deploymentKey: "dk",
- deploymentValue: "some-hash",
- expectedRc: &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{
- Annotations: map[string]string{
- "kubectl.kubernetes.io/next-controller-id": "foo",
- },
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "dk": "some-other-hash",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "dk": "some-other-hash",
- },
- },
- },
- },
- },
- },
- }
- for _, test := range tests {
- buffer := &bytes.Buffer{}
- fakeClient := testclient.NewSimpleFake(test.expectedRc)
- rc, err := UpdateExistingReplicationController(fakeClient, test.rc, "default", test.name, test.deploymentKey, test.deploymentValue, buffer)
- if !reflect.DeepEqual(rc, test.expectedRc) {
- t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.expectedRc, rc)
- }
- if test.expectErr && err == nil {
- t.Errorf("unexpected non-error")
- }
- if !test.expectErr && err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- }
- }
- func TestUpdateRcWithRetries(t *testing.T) {
- codec := testapi.Default.Codec()
- rc := &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{Name: "rc",
- Labels: map[string]string{
- "foo": "bar",
- },
- },
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "foo": "bar",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "foo": "bar",
- },
- },
- Spec: apitesting.DeepEqualSafePodSpec(),
- },
- },
- }
- // Test end to end updating of the rc with retries. Essentially make sure the update handler
- // sees the right updates, failures in update/get are handled properly, and that the updated
- // rc with new resource version is returned to the caller. Without any of these rollingupdate
- // will fail cryptically.
- newRc := *rc
- newRc.ResourceVersion = "2"
- newRc.Spec.Selector["baz"] = "foobar"
- header := http.Header{}
- header.Set("Content-Type", runtime.ContentTypeJSON)
- updates := []*http.Response{
- {StatusCode: 409, Header: header, Body: objBody(codec, &api.ReplicationController{})}, // conflict
- {StatusCode: 409, Header: header, Body: objBody(codec, &api.ReplicationController{})}, // conflict
- {StatusCode: 200, Header: header, Body: objBody(codec, &newRc)},
- }
- gets := []*http.Response{
- {StatusCode: 500, Header: header, Body: objBody(codec, &api.ReplicationController{})},
- {StatusCode: 200, Header: header, Body: objBody(codec, rc)},
- }
- fakeClient := &fake.RESTClient{
- NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
- Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
- switch p, m := req.URL.Path, req.Method; {
- case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "PUT":
- update := updates[0]
- updates = updates[1:]
- // We should always get an update with a valid rc even when the get fails. The rc should always
- // contain the update.
- if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !reflect.DeepEqual(rc, c) {
- t.Errorf("Unexpected update body, got %+v expected %+v", c, rc)
- } else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" {
- t.Errorf("Expected selector label update, got %+v", c.Spec.Selector)
- } else {
- delete(c.Spec.Selector, "baz")
- }
- return update, nil
- case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "GET":
- get := gets[0]
- gets = gets[1:]
- return get, nil
- default:
- t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
- return nil, nil
- }
- }),
- }
- clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}
- client := client.NewOrDie(clientConfig)
- client.Client = fakeClient.Client
- if rc, err := updateRcWithRetries(
- client, "default", rc, func(c *api.ReplicationController) {
- c.Spec.Selector["baz"] = "foobar"
- }); err != nil {
- t.Errorf("unexpected error: %v", err)
- } else if sel, ok := rc.Spec.Selector["baz"]; !ok || sel != "foobar" || rc.ResourceVersion != "2" {
- t.Errorf("Expected updated rc, got %+v", rc)
- }
- if len(updates) != 0 || len(gets) != 0 {
- t.Errorf("Remaining updates %#v gets %#v", updates, gets)
- }
- }
- func readOrDie(t *testing.T, req *http.Request, codec runtime.Codec) runtime.Object {
- data, err := ioutil.ReadAll(req.Body)
- if err != nil {
- t.Errorf("Error reading: %v", err)
- t.FailNow()
- }
- obj, err := runtime.Decode(codec, data)
- if err != nil {
- t.Errorf("error decoding: %v", err)
- t.FailNow()
- }
- return obj
- }
- func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
- return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
- }
- func TestAddDeploymentHash(t *testing.T) {
- buf := &bytes.Buffer{}
- codec := testapi.Default.Codec()
- rc := &api.ReplicationController{
- ObjectMeta: api.ObjectMeta{Name: "rc"},
- Spec: api.ReplicationControllerSpec{
- Selector: map[string]string{
- "foo": "bar",
- },
- Template: &api.PodTemplateSpec{
- ObjectMeta: api.ObjectMeta{
- Labels: map[string]string{
- "foo": "bar",
- },
- },
- },
- },
- }
- podList := &api.PodList{
- Items: []api.Pod{
- {ObjectMeta: api.ObjectMeta{Name: "foo"}},
- {ObjectMeta: api.ObjectMeta{Name: "bar"}},
- {ObjectMeta: api.ObjectMeta{Name: "baz"}},
- },
- }
- seen := sets.String{}
- updatedRc := false
- fakeClient := &fake.RESTClient{
- NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
- Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
- header := http.Header{}
- header.Set("Content-Type", runtime.ContentTypeJSON)
- switch p, m := req.URL.Path, req.Method; {
- case p == testapi.Default.ResourcePath("pods", "default", "") && m == "GET":
- if req.URL.RawQuery != "labelSelector=foo%3Dbar" {
- t.Errorf("Unexpected query string: %s", req.URL.RawQuery)
- }
- return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, podList)}, nil
- case p == testapi.Default.ResourcePath("pods", "default", "foo") && m == "PUT":
- seen.Insert("foo")
- obj := readOrDie(t, req, codec)
- podList.Items[0] = *(obj.(*api.Pod))
- return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[0])}, nil
- case p == testapi.Default.ResourcePath("pods", "default", "bar") && m == "PUT":
- seen.Insert("bar")
- obj := readOrDie(t, req, codec)
- podList.Items[1] = *(obj.(*api.Pod))
- return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[1])}, nil
- case p == testapi.Default.ResourcePath("pods", "default", "baz") && m == "PUT":
- seen.Insert("baz")
- obj := readOrDie(t, req, codec)
- podList.Items[2] = *(obj.(*api.Pod))
- return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, &podList.Items[2])}, nil
- case p == testapi.Default.ResourcePath("replicationcontrollers", "default", "rc") && m == "PUT":
- updatedRc = true
- return &http.Response{StatusCode: 200, Header: header, Body: objBody(codec, rc)}, nil
- default:
- t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
- return nil, nil
- }
- }),
- }
- clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}
- client := client.NewOrDie(clientConfig)
- client.Client = fakeClient.Client
- if _, err := AddDeploymentKeyToReplicationController(rc, client, "dk", "hash", api.NamespaceDefault, buf); err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- for _, pod := range podList.Items {
- if !seen.Has(pod.Name) {
- t.Errorf("Missing update for pod: %s", pod.Name)
- }
- }
- if !updatedRc {
- t.Errorf("Failed to update replication controller with new labels")
- }
- }
- func TestRollingUpdater_readyPods(t *testing.T) {
- now := unversioned.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC)
- mkpod := func(owner *api.ReplicationController, ready bool, readyTime unversioned.Time) *api.Pod {
- labels := map[string]string{}
- for k, v := range owner.Spec.Selector {
- labels[k] = v
- }
- status := api.ConditionTrue
- if !ready {
- status = api.ConditionFalse
- }
- return &api.Pod{
- ObjectMeta: api.ObjectMeta{
- Name: "pod",
- Labels: labels,
- },
- Status: api.PodStatus{
- Conditions: []api.PodCondition{
- {
- Type: api.PodReady,
- Status: status,
- LastTransitionTime: readyTime,
- },
- },
- },
- }
- }
- tests := []struct {
- oldRc *api.ReplicationController
- newRc *api.ReplicationController
- // expectated old/new ready counts
- oldReady int32
- newReady int32
- // pods owned by the rcs; indicate whether they're ready
- oldPods []bool
- newPods []bool
- // specify additional time to wait for deployment to wait on top of the
- // pod ready time
- minReadySeconds int32
- podReadyTimeFn func() unversioned.Time
- nowFn func() unversioned.Time
- }{
- {
- oldRc: oldRc(4, 4),
- newRc: newRc(4, 4),
- oldReady: 4,
- newReady: 2,
- oldPods: []bool{
- true,
- true,
- true,
- true,
- },
- newPods: []bool{
- true,
- false,
- true,
- false,
- },
- },
- {
- oldRc: oldRc(4, 4),
- newRc: newRc(4, 4),
- oldReady: 0,
- newReady: 1,
- oldPods: []bool{
- false,
- },
- newPods: []bool{
- true,
- },
- },
- {
- oldRc: oldRc(4, 4),
- newRc: newRc(4, 4),
- oldReady: 1,
- newReady: 0,
- oldPods: []bool{
- true,
- },
- newPods: []bool{
- false,
- },
- },
- {
- oldRc: oldRc(4, 4),
- newRc: newRc(4, 4),
- oldReady: 0,
- newReady: 0,
- oldPods: []bool{
- true,
- },
- newPods: []bool{
- true,
- },
- minReadySeconds: 5,
- nowFn: func() unversioned.Time { return now },
- },
- {
- oldRc: oldRc(4, 4),
- newRc: newRc(4, 4),
- oldReady: 1,
- newReady: 1,
- oldPods: []bool{
- true,
- },
- newPods: []bool{
- true,
- },
- minReadySeconds: 5,
- nowFn: func() unversioned.Time { return unversioned.Time{Time: now.Add(time.Duration(6 * time.Second))} },
- podReadyTimeFn: func() unversioned.Time { return now },
- },
- }
- for i, test := range tests {
- t.Logf("evaluating test %d", i)
- if test.nowFn == nil {
- test.nowFn = func() unversioned.Time { return now }
- }
- if test.podReadyTimeFn == nil {
- test.podReadyTimeFn = test.nowFn
- }
- // Populate the fake client with pods associated with their owners.
- pods := []runtime.Object{}
- for _, ready := range test.oldPods {
- pods = append(pods, mkpod(test.oldRc, ready, test.podReadyTimeFn()))
- }
- for _, ready := range test.newPods {
- pods = append(pods, mkpod(test.newRc, ready, test.podReadyTimeFn()))
- }
- client := testclient.NewSimpleFake(pods...)
- updater := &RollingUpdater{
- ns: "default",
- c: client,
- nowFn: test.nowFn,
- }
- oldReady, newReady, err := updater.readyPods(test.oldRc, test.newRc, test.minReadySeconds)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- if e, a := test.oldReady, oldReady; e != a {
- t.Errorf("expected old ready %d, got %d", e, a)
- }
- if e, a := test.newReady, newReady; e != a {
- t.Errorf("expected new ready %d, got %d", e, a)
- }
- }
- }
|