diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index d3483896fdd..a2ed65b45ad 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -228,6 +228,10 @@ type DeploymentSpec struct { // Value of this key is hash of DeploymentSpec.PodTemplateSpec. // No label is added if this is set to empty string. UniqueLabelKey string `json:"uniqueLabelKey,omitempty"` + + // Indicates that the deployment is paused and will not be processed by the + // deployment controller. + Paused bool `json:"paused,omitempty"` } const ( diff --git a/pkg/apis/extensions/v1beta1/conversion.go b/pkg/apis/extensions/v1beta1/conversion.go index b293a12146a..c5a0b911957 100644 --- a/pkg/apis/extensions/v1beta1/conversion.go +++ b/pkg/apis/extensions/v1beta1/conversion.go @@ -262,6 +262,7 @@ func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions. } out.UniqueLabelKey = new(string) *out.UniqueLabelKey = in.UniqueLabelKey + out.Paused = in.Paused return nil } @@ -289,6 +290,7 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *DeploymentS if in.UniqueLabelKey != nil { out.UniqueLabelKey = *in.UniqueLabelKey } + out.Paused = in.Paused return nil } diff --git a/pkg/apis/extensions/v1beta1/types.go b/pkg/apis/extensions/v1beta1/types.go index 564af4d7246..896c285a252 100644 --- a/pkg/apis/extensions/v1beta1/types.go +++ b/pkg/apis/extensions/v1beta1/types.go @@ -213,6 +213,10 @@ type DeploymentSpec struct { // Value of this key is hash of DeploymentSpec.PodTemplateSpec. // No label is added if this is set to empty string. UniqueLabelKey *string `json:"uniqueLabelKey,omitempty"` + + // Indicates that the deployment is paused and will not be processed by the + // deployment controller. + Paused bool `json:"paused,omitempty"` } const ( diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index b53d62ab1a0..0877e821030 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -410,6 +410,11 @@ func (dc *DeploymentController) syncDeployment(key string) error { return nil } + if d.Spec.Paused { + // Ignore paused deployments + glog.V(4).Infof("Ignoring paused deployment %s/%s", d.Namespace, d.Name) + return nil + } switch d.Spec.Strategy.Type { case extensions.RecreateDeploymentStrategyType: return dc.syncRecreateDeployment(d) diff --git a/pkg/registry/deployment/etcd/etcd_test.go b/pkg/registry/deployment/etcd/etcd_test.go index dc6fae4060e..c327a04efd7 100755 --- a/pkg/registry/deployment/etcd/etcd_test.go +++ b/pkg/registry/deployment/etcd/etcd_test.go @@ -75,21 +75,6 @@ func validNewDeployment() *extensions.Deployment { var validDeployment = *validNewDeployment() -func validNewScale() *extensions.Scale { - return &extensions.Scale{ - ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, - Spec: extensions.ScaleSpec{ - Replicas: validDeployment.Spec.Replicas, - }, - Status: extensions.ScaleStatus{ - Replicas: validDeployment.Status.Replicas, - Selector: validDeployment.Spec.Template.Labels, - }, - } -} - -var validScale = *validNewScale() - func TestCreate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) @@ -192,6 +177,21 @@ func TestWatch(t *testing.T) { ) } +func validNewScale() *extensions.Scale { + return &extensions.Scale{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, + Spec: extensions.ScaleSpec{ + Replicas: validDeployment.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: validDeployment.Status.Replicas, + Selector: validDeployment.Spec.Template.Labels, + }, + } +} + +var validScale = *validNewScale() + func TestScaleGet(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) @@ -204,10 +204,10 @@ func TestScaleGet(t *testing.T) { expect := &validScale obj, err := storage.Scale.Get(ctx, name) - scale := obj.(*extensions.Scale) if err != nil { t.Fatalf("unexpected error: %v", err) } + scale := obj.(*extensions.Scale) if e, a := expect, scale; !api.Semantic.DeepDerivative(e, a) { t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a)) } @@ -216,6 +216,7 @@ func TestScaleGet(t *testing.T) { func TestScaleUpdate(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) + ctx := api.WithNamespace(api.NewContext(), namespace) key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil { @@ -232,12 +233,11 @@ func TestScaleUpdate(t *testing.T) { if _, _, err := storage.Scale.Update(ctx, &update); err != nil { t.Fatalf("unexpected error: %v", err) } - obj, err := storage.Scale.Get(ctx, name) + obj, err := storage.Deployment.Get(ctx, name) if err != nil { t.Fatalf("unexpected error: %v", err) } - - deployment := obj.(*extensions.Scale) + deployment := obj.(*extensions.Deployment) if deployment.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) } diff --git a/test/e2e/deployment.go b/test/e2e/deployment.go index cfdb90c0b61..b37f6ed67a9 100644 --- a/test/e2e/deployment.go +++ b/test/e2e/deployment.go @@ -18,9 +18,11 @@ package e2e import ( "fmt" + "time" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" deploymentutil "k8s.io/kubernetes/pkg/util/deployment" "k8s.io/kubernetes/pkg/util/intstr" @@ -46,6 +48,9 @@ var _ = Describe("Deployment", func() { It("deployment should support rollover [Flaky]", func() { testRolloverDeployment(f) }) + It("paused deployment should be ignored by the controller", func() { + testPausedDeployment(f) + }) }) func newRC(rcName string, replicas int, rcPodLabels map[string]string, imageName string, image string) *api.ReplicationController { @@ -390,3 +395,74 @@ func testRolloverDeployment(f *Framework) { newRC, err = deploymentutil.GetNewRC(*deployment, c) Expect(newRC.Spec.Template.Spec.Containers[0].Image).Should(Equal(updatedDeploymentImage)) } + +func testPausedDeployment(f *Framework) { + ns := f.Namespace.Name + c := f.Client + deploymentName := "nginx" + podLabels := map[string]string{"name": "nginx"} + d := newDeployment(deploymentName, 1, podLabels, "nginx", "nginx", extensions.RollingUpdateDeploymentStrategyType) + d.Spec.Paused = true + Logf("Creating paused deployment %s", deploymentName) + _, err := c.Deployments(ns).Create(d) + Expect(err).NotTo(HaveOccurred()) + defer func() { + _, err := c.Deployments(ns).Get(deploymentName) + Expect(err).NotTo(HaveOccurred()) + Logf("deleting deployment %s", deploymentName) + Expect(c.Deployments(ns).Delete(deploymentName, nil)).NotTo(HaveOccurred()) + }() + // Check that deployment is created fine. + deployment, err := c.Deployments(ns).Get(deploymentName) + Expect(err).NotTo(HaveOccurred()) + + // Verify that there is no latest state realized for the new deployment. + rc, err := deploymentutil.GetNewRC(*deployment, c) + Expect(err).NotTo(HaveOccurred()) + if rc != nil { + err = fmt.Errorf("unexpected new rc/%s for deployment/%s", rc.Name, deployment.Name) + Expect(err).NotTo(HaveOccurred()) + } + + // Update the deployment to run + deployment.Spec.Paused = false + deployment, err = c.Deployments(ns).Update(deployment) + Expect(err).NotTo(HaveOccurred()) + + opts := api.ListOptions{LabelSelector: labels.Set(deployment.Spec.Selector).AsSelector()} + w, err := c.ReplicationControllers(ns).Watch(opts) + Expect(err).NotTo(HaveOccurred()) + + select { + case <-w.ResultChan(): + // this is it + case <-time.After(time.Minute): + err = fmt.Errorf("expected a new rc to be created") + Expect(err).NotTo(HaveOccurred()) + } + + // Pause the deployment and delete the replication controller. + // The paused deployment shouldn't recreate a new one. + deployment.Spec.Paused = true + deployment.ResourceVersion = "" + deployment, err = c.Deployments(ns).Update(deployment) + Expect(err).NotTo(HaveOccurred()) + + newRC, err := deploymentutil.GetNewRC(*deployment, c) + Expect(err).NotTo(HaveOccurred()) + Expect(c.ReplicationControllers(ns).Delete(newRC.Name)).NotTo(HaveOccurred()) + + deployment, err = c.Deployments(ns).Get(deploymentName) + Expect(err).NotTo(HaveOccurred()) + + if !deployment.Spec.Paused { + err = fmt.Errorf("deployment %q should be paused", deployment.Name) + Expect(err).NotTo(HaveOccurred()) + } + shouldBeNil, err := deploymentutil.GetNewRC(*deployment, c) + Expect(err).NotTo(HaveOccurred()) + if shouldBeNil != nil { + err = fmt.Errorf("deployment %q shouldn't have a rc but there is %q", deployment.Name, shouldBeNil.Name) + Expect(err).NotTo(HaveOccurred()) + } +}