extensions: support paused deployments
This commit adds support for paused deployments so a user can choose when to run a deployment that exists in the system instead of having the deployment controller automatically reconciling it after every change or sync interval.
This commit is contained in:
		| @@ -228,6 +228,10 @@ type DeploymentSpec struct { | |||||||
| 	// Value of this key is hash of DeploymentSpec.PodTemplateSpec. | 	// Value of this key is hash of DeploymentSpec.PodTemplateSpec. | ||||||
| 	// No label is added if this is set to empty string. | 	// No label is added if this is set to empty string. | ||||||
| 	UniqueLabelKey string `json:"uniqueLabelKey,omitempty"` | 	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 ( | const ( | ||||||
|   | |||||||
| @@ -262,6 +262,7 @@ func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions. | |||||||
| 	} | 	} | ||||||
| 	out.UniqueLabelKey = new(string) | 	out.UniqueLabelKey = new(string) | ||||||
| 	*out.UniqueLabelKey = in.UniqueLabelKey | 	*out.UniqueLabelKey = in.UniqueLabelKey | ||||||
|  | 	out.Paused = in.Paused | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -289,6 +290,7 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *DeploymentS | |||||||
| 	if in.UniqueLabelKey != nil { | 	if in.UniqueLabelKey != nil { | ||||||
| 		out.UniqueLabelKey = *in.UniqueLabelKey | 		out.UniqueLabelKey = *in.UniqueLabelKey | ||||||
| 	} | 	} | ||||||
|  | 	out.Paused = in.Paused | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -213,6 +213,10 @@ type DeploymentSpec struct { | |||||||
| 	// Value of this key is hash of DeploymentSpec.PodTemplateSpec. | 	// Value of this key is hash of DeploymentSpec.PodTemplateSpec. | ||||||
| 	// No label is added if this is set to empty string. | 	// No label is added if this is set to empty string. | ||||||
| 	UniqueLabelKey *string `json:"uniqueLabelKey,omitempty"` | 	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 ( | const ( | ||||||
|   | |||||||
| @@ -410,6 +410,11 @@ func (dc *DeploymentController) syncDeployment(key string) error { | |||||||
| 		return nil | 		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 { | 	switch d.Spec.Strategy.Type { | ||||||
| 	case extensions.RecreateDeploymentStrategyType: | 	case extensions.RecreateDeploymentStrategyType: | ||||||
| 		return dc.syncRecreateDeployment(d) | 		return dc.syncRecreateDeployment(d) | ||||||
|   | |||||||
| @@ -75,21 +75,6 @@ func validNewDeployment() *extensions.Deployment { | |||||||
|  |  | ||||||
| var validDeployment = *validNewDeployment() | 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) { | func TestCreate(t *testing.T) { | ||||||
| 	storage, server := newStorage(t) | 	storage, server := newStorage(t) | ||||||
| 	defer server.Terminate(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) { | func TestScaleGet(t *testing.T) { | ||||||
| 	storage, server := newStorage(t) | 	storage, server := newStorage(t) | ||||||
| 	defer server.Terminate(t) | 	defer server.Terminate(t) | ||||||
| @@ -204,10 +204,10 @@ func TestScaleGet(t *testing.T) { | |||||||
|  |  | ||||||
| 	expect := &validScale | 	expect := &validScale | ||||||
| 	obj, err := storage.Scale.Get(ctx, name) | 	obj, err := storage.Scale.Get(ctx, name) | ||||||
| 	scale := obj.(*extensions.Scale) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error: %v", err) | 		t.Fatalf("unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	scale := obj.(*extensions.Scale) | ||||||
| 	if e, a := expect, scale; !api.Semantic.DeepDerivative(e, a) { | 	if e, a := expect, scale; !api.Semantic.DeepDerivative(e, a) { | ||||||
| 		t.Errorf("unexpected scale: %s", util.ObjectDiff(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) { | func TestScaleUpdate(t *testing.T) { | ||||||
| 	storage, server := newStorage(t) | 	storage, server := newStorage(t) | ||||||
| 	defer server.Terminate(t) | 	defer server.Terminate(t) | ||||||
|  |  | ||||||
| 	ctx := api.WithNamespace(api.NewContext(), namespace) | 	ctx := api.WithNamespace(api.NewContext(), namespace) | ||||||
| 	key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) | 	key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) | ||||||
| 	if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil { | 	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 { | 	if _, _, err := storage.Scale.Update(ctx, &update); err != nil { | ||||||
| 		t.Fatalf("unexpected error: %v", err) | 		t.Fatalf("unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| 	obj, err := storage.Scale.Get(ctx, name) | 	obj, err := storage.Deployment.Get(ctx, name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("unexpected error: %v", err) | 		t.Fatalf("unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
|  | 	deployment := obj.(*extensions.Deployment) | ||||||
| 	deployment := obj.(*extensions.Scale) |  | ||||||
| 	if deployment.Spec.Replicas != replicas { | 	if deployment.Spec.Replicas != replicas { | ||||||
| 		t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) | 		t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -18,9 +18,11 @@ package e2e | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"k8s.io/kubernetes/pkg/api" | 	"k8s.io/kubernetes/pkg/api" | ||||||
| 	"k8s.io/kubernetes/pkg/apis/extensions" | 	"k8s.io/kubernetes/pkg/apis/extensions" | ||||||
|  | 	"k8s.io/kubernetes/pkg/labels" | ||||||
| 	deploymentutil "k8s.io/kubernetes/pkg/util/deployment" | 	deploymentutil "k8s.io/kubernetes/pkg/util/deployment" | ||||||
| 	"k8s.io/kubernetes/pkg/util/intstr" | 	"k8s.io/kubernetes/pkg/util/intstr" | ||||||
|  |  | ||||||
| @@ -46,6 +48,9 @@ var _ = Describe("Deployment", func() { | |||||||
| 	It("deployment should support rollover [Flaky]", func() { | 	It("deployment should support rollover [Flaky]", func() { | ||||||
| 		testRolloverDeployment(f) | 		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 { | 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) | 	newRC, err = deploymentutil.GetNewRC(*deployment, c) | ||||||
| 	Expect(newRC.Spec.Template.Spec.Containers[0].Image).Should(Equal(updatedDeploymentImage)) | 	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()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michail Kargakis
					Michail Kargakis