/* Copyright 2014 The Kubernetes Authors All rights reserved. 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" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" ) type updaterFake struct { *testclient.Fake ctrl client.ReplicationControllerInterface } func (c *updaterFake) ReplicationControllers(namespace string) client.ReplicationControllerInterface { return c.ctrl } func fakeClientFor(namespace string, responses []fakeResponse) client.Interface { fake := testclient.Fake{} return &updaterFake{ &fake, &fakeRc{ &testclient.FakeReplicationControllers{ Fake: &fake, Namespace: namespace, }, responses, }, } } type fakeResponse struct { controller *api.ReplicationController err error } type fakeRc struct { *testclient.FakeReplicationControllers responses []fakeResponse } func (c *fakeRc) Get(name string) (*api.ReplicationController, error) { action := testclient.FakeAction{Action: "get-controller", Value: name} if len(c.responses) == 0 { return nil, fmt.Errorf("Unexpected Action: %s", action) } c.Fake.Actions = append(c.Fake.Actions, action) result := c.responses[0] c.responses = c.responses[1:] return result.controller, result.err } func (c *fakeRc) Create(controller *api.ReplicationController) (*api.ReplicationController, error) { c.Fake.Actions = append(c.Fake.Actions, testclient.FakeAction{Action: "create-controller", Value: controller.ObjectMeta.Name}) return controller, nil } func (c *fakeRc) Update(controller *api.ReplicationController) (*api.ReplicationController, error) { c.Fake.Actions = append(c.Fake.Actions, testclient.FakeAction{Action: "update-controller", Value: controller.ObjectMeta.Name}) return controller, nil } func oldRc(replicas int) *api.ReplicationController { return &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "foo-v1", UID: "7764ae47-9092-11e4-8393-42010af018ff", }, Spec: api.ReplicationControllerSpec{ Replicas: 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: replicas, }, } } func newRc(replicas int, desired int) *api.ReplicationController { rc := oldRc(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 } func TestUpdate(t *testing.T) { tests := []struct { oldRc, newRc *api.ReplicationController responses []fakeResponse output string }{ { oldRc(1), newRc(1, 1), []fakeResponse{ // no existing newRc {nil, fmt.Errorf("not found")}, // 3 gets for each resize {newRc(1, 1), nil}, {newRc(1, 1), nil}, {newRc(1, 1), nil}, {newRc(1, 1), nil}, {oldRc(0), nil}, {oldRc(0), nil}, {oldRc(0), nil}, // {oldRc(0), nil}, // cleanup annotations {newRc(1, 1), nil}, {newRc(1, 1), nil}, }, `Creating foo-v2 Updating foo-v1 replicas: 0, foo-v2 replicas: 1 Update succeeded. Deleting foo-v1 `, }, { oldRc(2), newRc(2, 2), []fakeResponse{ // no existing newRc {nil, fmt.Errorf("not found")}, // 3 gets for each resize {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {oldRc(1), nil}, {oldRc(1), nil}, {oldRc(1), nil}, // {oldRc(1), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {oldRc(0), nil}, {oldRc(0), nil}, {oldRc(0), nil}, // {oldRc(0), nil}, // cleanup annotations {newRc(2, 2), nil}, {newRc(2, 2), nil}, }, `Creating foo-v2 Updating foo-v1 replicas: 1, foo-v2 replicas: 1 Updating foo-v1 replicas: 0, foo-v2 replicas: 2 Update succeeded. Deleting foo-v1 `, }, { oldRc(2), newRc(7, 7), []fakeResponse{ // no existing newRc {nil, fmt.Errorf("not found")}, // 3 gets for each resize {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {oldRc(1), nil}, {oldRc(1), nil}, {oldRc(1), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {oldRc(0), nil}, {oldRc(0), nil}, {oldRc(0), nil}, // final resize on newRc {newRc(7, 7), nil}, {newRc(7, 7), nil}, {newRc(7, 7), nil}, // cleanup annotations {newRc(7, 7), nil}, {newRc(7, 7), nil}, }, `Creating foo-v2 Updating foo-v1 replicas: 1, foo-v2 replicas: 1 Updating foo-v1 replicas: 0, foo-v2 replicas: 2 Resizing foo-v2 replicas: 2 -> 7 Update succeeded. Deleting foo-v1 `, }, { oldRc(7), newRc(2, 2), []fakeResponse{ // no existing newRc {nil, fmt.Errorf("not found")}, // 3 gets for each update {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {newRc(1, 2), nil}, {oldRc(6), nil}, {oldRc(6), nil}, {oldRc(6), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {oldRc(5), nil}, {oldRc(5), nil}, {oldRc(5), nil}, // stop oldRc {oldRc(0), nil}, {oldRc(0), nil}, // cleanup annotations {newRc(2, 2), nil}, {newRc(2, 2), nil}, }, `Creating foo-v2 Updating foo-v1 replicas: 6, foo-v2 replicas: 1 Updating foo-v1 replicas: 5, foo-v2 replicas: 2 Stopping foo-v1 replicas: 5 -> 0 Update succeeded. Deleting foo-v1 `, }, } for _, test := range tests { updater := RollingUpdater{ NewRollingUpdaterClient(fakeClientFor("default", test.responses)), "default", } var buffer bytes.Buffer config := &RollingUpdaterConfig{ Out: &buffer, OldRc: test.oldRc, NewRc: test.newRc, UpdatePeriod: 0, Interval: time.Millisecond, Timeout: time.Millisecond, CleanupPolicy: DeleteRollingUpdateCleanupPolicy, } if err := updater.Update(config); err != nil { t.Errorf("Update failed: %v", err) } if buffer.String() != test.output { t.Errorf("Bad output. expected:\n%s\ngot:\n%s", test.output, buffer.String()) } } } func PTestUpdateRecovery(t *testing.T) { // Test recovery from interruption rc := oldRc(2) rcExisting := newRc(1, 3) output := `Continuing update with existing controller foo-v2. Updating foo-v1 replicas: 1, foo-v2 replicas: 2 Updating foo-v1 replicas: 0, foo-v2 replicas: 3 Update succeeded. Deleting foo-v1 ` responses := []fakeResponse{ // Existing newRc {rcExisting, nil}, // 3 gets for each resize {newRc(2, 2), nil}, {newRc(2, 2), nil}, {newRc(2, 2), nil}, {oldRc(1), nil}, {oldRc(1), nil}, {oldRc(1), nil}, {newRc(3, 3), nil}, {newRc(3, 3), nil}, {newRc(3, 3), nil}, {oldRc(0), nil}, {oldRc(0), nil}, {oldRc(0), nil}, // cleanup annotations {newRc(3, 3), nil}, {newRc(3, 3), nil}, } updater := RollingUpdater{NewRollingUpdaterClient(fakeClientFor("default", responses)), "default"} var buffer bytes.Buffer config := &RollingUpdaterConfig{ Out: &buffer, OldRc: rc, NewRc: rcExisting, UpdatePeriod: 0, Interval: time.Millisecond, Timeout: time.Millisecond, CleanupPolicy: DeleteRollingUpdateCleanupPolicy, } if err := updater.Update(config); err != nil { t.Errorf("Update failed: %v", err) } if buffer.String() != output { t.Errorf("Output was not as expected. Expected:\n%s\nGot:\n%s", output, buffer.String()) } } // TestRollingUpdater_preserveCleanup ensures that the old controller isn't // deleted following a successful deployment. func TestRollingUpdater_preserveCleanup(t *testing.T) { rc := oldRc(2) rcExisting := newRc(1, 3) updater := &RollingUpdater{ ns: "default", c: &rollingUpdaterClientImpl{ GetReplicationControllerFn: func(namespace, name string) (*api.ReplicationController, error) { switch name { case rc.Name: return rc, nil case rcExisting.Name: return rcExisting, nil default: return nil, fmt.Errorf("unexpected get call for %s/%s", namespace, name) } }, UpdateReplicationControllerFn: func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) { return rc, nil }, CreateReplicationControllerFn: func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) { t.Fatalf("unexpected call to create %s/rc:%#v", namespace, rc) return nil, nil }, DeleteReplicationControllerFn: func(namespace, name string) error { t.Fatalf("unexpected call to delete %s/%s", namespace, name) return nil }, ControllerHasDesiredReplicasFn: func(rc *api.ReplicationController) wait.ConditionFunc { return func() (done bool, err error) { return true, nil } }, }, } config := &RollingUpdaterConfig{ Out: ioutil.Discard, OldRc: rc, NewRc: rcExisting, UpdatePeriod: 0, Interval: time.Millisecond, Timeout: time.Millisecond, CleanupPolicy: PreserveRollingUpdateCleanupPolicy, } err := updater.Update(config) if err != nil { t.Errorf("unexpected error: %v", err) } } func TestRename(t *testing.T) { tests := []struct { namespace string newName string oldName string err error expectError bool }{ { namespace: "default", newName: "bar", oldName: "foo", }, { namespace: "default", newName: "bar", oldName: "foo", err: fmt.Errorf("Test Error"), expectError: true, }, } for _, test := range tests { fakeClient := &rollingUpdaterClientImpl{ CreateReplicationControllerFn: func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) { if namespace != test.namespace { t.Errorf("unexepected namespace: %s, expected %s", namespace, test.namespace) } if rc.Name != test.newName { t.Errorf("unexepected name: %s, expected %s", rc.Name, test.newName) } return rc, test.err }, DeleteReplicationControllerFn: func(namespace, name string) error { if namespace != test.namespace { t.Errorf("unexepected namespace: %s, expected %s", namespace, test.namespace) } if name != test.oldName { t.Errorf("unexepected name: %s, expected %s", name, test.oldName) } return nil }, } err := Rename(fakeClient, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: test.namespace, Name: test.oldName}}, test.newName) if err != nil && !test.expectError { t.Errorf("unexpected error: %v", err) } if err == nil && test.expectError { t.Errorf("unexpected non-error") } } } 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 := rollingUpdaterClientImpl{ ListReplicationControllersFn: func(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error) { return test.list, test.err }, } 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 := fakeClientFor("default", []fakeResponse{}) 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 TestUpdateWithRetries(t *testing.T) { codec := testapi.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: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, }, }, } // 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" updates := []*http.Response{ {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 200, Body: objBody(codec, &newRc)}, } gets := []*http.Response{ {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 200, Body: objBody(codec, rc)}, } fakeClient := &client.FakeRESTClient{ Codec: codec, Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1beta3/namespaces/default/replicationcontrollers/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 == "/api/v1beta3/namespaces/default/replicationcontrollers/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 := &client.Config{Version: latest.Version} client := client.NewOrDie(clientConfig) client.Client = fakeClient.Client if rc, err := updateWithRetries( client.ReplicationControllers("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 := codec.Decode(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.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 := util.StringSet{} updatedRc := false fakeClient := &client.FakeRESTClient{ Codec: codec, Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1beta3/namespaces/default/pods" && m == "GET": if req.URL.RawQuery != "labelSelector=foo%3Dbar" { t.Errorf("Unexpected query string: %s", req.URL.RawQuery) } return &http.Response{StatusCode: 200, Body: objBody(codec, podList)}, nil case p == "/api/v1beta3/namespaces/default/pods/foo" && m == "PUT": seen.Insert("foo") obj := readOrDie(t, req, codec) podList.Items[0] = *(obj.(*api.Pod)) return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[0])}, nil case p == "/api/v1beta3/namespaces/default/pods/bar" && m == "PUT": seen.Insert("bar") obj := readOrDie(t, req, codec) podList.Items[1] = *(obj.(*api.Pod)) return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[1])}, nil case p == "/api/v1beta3/namespaces/default/pods/baz" && m == "PUT": seen.Insert("baz") obj := readOrDie(t, req, codec) podList.Items[2] = *(obj.(*api.Pod)) return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[2])}, nil case p == "/api/v1beta3/namespaces/default/replicationcontrollers/rc" && m == "PUT": updatedRc = true return &http.Response{StatusCode: 200, Body: objBody(codec, rc)}, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil } }), } clientConfig := &client.Config{Version: latest.Version} 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") } } // rollingUpdaterClientImpl is a dynamic RollingUpdaterClient. type rollingUpdaterClientImpl struct { ListReplicationControllersFn func(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error) GetReplicationControllerFn func(namespace, name string) (*api.ReplicationController, error) UpdateReplicationControllerFn func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) CreateReplicationControllerFn func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) DeleteReplicationControllerFn func(namespace, name string) error ControllerHasDesiredReplicasFn func(rc *api.ReplicationController) wait.ConditionFunc } func (c *rollingUpdaterClientImpl) ListReplicationControllers(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error) { return c.ListReplicationControllersFn(namespace, selector) } func (c *rollingUpdaterClientImpl) GetReplicationController(namespace, name string) (*api.ReplicationController, error) { return c.GetReplicationControllerFn(namespace, name) } func (c *rollingUpdaterClientImpl) UpdateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) { return c.UpdateReplicationControllerFn(namespace, rc) } func (c *rollingUpdaterClientImpl) CreateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) { return c.CreateReplicationControllerFn(namespace, rc) } func (c *rollingUpdaterClientImpl) DeleteReplicationController(namespace, name string) error { return c.DeleteReplicationControllerFn(namespace, name) } func (c *rollingUpdaterClientImpl) ControllerHasDesiredReplicas(rc *api.ReplicationController) wait.ConditionFunc { return c.ControllerHasDesiredReplicasFn(rc) }