Browse code

Introduce deployment rollback API

Rollbacks are implemented as a new REST endpoint, /generateRollback, which allows
the generation of a new DeploymentConfig based on an arbitrary previous deployment.
The generation process is configurable via a new DeploymentConfigRollback type.

See the deployment documentation for further details.

Dan Mace authored on 2015/01/20 07:08:05
Showing 26 changed files
... ...
@@ -4,17 +4,11 @@
4 4
 
5 5
 In OpenShift, deployment is an update to a single replication controller's pod template based on triggered events. The deployment subsystem provides:
6 6
 
7
-*  Ability to declaratively define a pod template deployment configuration and see the system eventually deploy the desired configuration for that template
8
-*  Ability to rollback to a previous pod template
7
+*  [Declarative definition](#defining a deploymentConfig) of a desired deployment configuration which drives automated deployments by the system
8
+*  [Triggers](#triggers) which drive new deployments in response to events
9
+*  [Rollbacks](#rollbacks) to a previous deployment
10
+*  [Strategies](strategies) for deployment rollout behavior which are user-customizable
9 11
 *  Audit history of deployed pod template configurations
10
-*  Ability to specify that certain events should trigger a new deployment:
11
-    *  When a new version of a referenced image becomes available
12
-    *  When an update to the pod template is made
13
-*  Ability to select from multiple deployment strategies, such as:
14
-    *  Canary or A/B deployment
15
-    *  Rolling deployment
16
-    *  User-defined strategy; allowing ad-hoc strategies or decoration of existing strategies
17
-*  Ability to manage multiple replication controllers with the same mechanism
18 12
 
19 13
 #### Concepts
20 14
 
... ...
@@ -170,3 +164,41 @@ Additionally, the following environment variables are provided by OpenShift to t
170 170
 * `OPENSHIFT_DEPLOYMENT_NAMESPACE` - the namespace of the `replicationController` representing the new `deployment`
171 171
 
172 172
 The replica count of the `replicationController` for the new deployment will be 0 initially. The responsibility of the `strategy` is to make the new `deployment` live using whatever logic best serves the needs of the user.
173
+
174
+## Rollbacks
175
+
176
+Rolling a deployment back to a previous state is a two step process accomplished by:
177
+
178
+1. POSTing a `rollback` API object to a special endpoint, which generates and returns a new `deploymentConfig` representing the rollback state
179
+2. POSTing the new `deploymentConfig` to the API server
180
+
181
+The `rollback` API object configures the generation process and provides the scope of the rollback. For example, given a previous deployment `deployment-1` and the current deployment `deployment-2`:
182
+
183
+```
184
+{
185
+  "metadata": {
186
+    "name": "rollback-1",
187
+    "namespace": "default"
188
+  },
189
+  "kind": "DeploymentConfigRollback",
190
+  "apiVersion": "v1beta1",
191
+  "spec": {
192
+    "from": {
193
+      "name": "deployment-1"
194
+    },
195
+    "includeTemplate": true,
196
+    "includeTriggers": false,
197
+    "includeReplicationMeta": false,
198
+    "includeStrategy": true
199
+  }
200
+}
201
+```
202
+
203
+With this rollback specification, a new `deploymentConfig` named `deployment-3` will be generated, containing the details of `deployment-2` with the specified portions of `deployment-1` overlayed. The generation options are:
204
+
205
+* `includeTemplate` - whether to roll back `podTemplate` of the `deploymentConfig`
206
+* `includeTriggers` - whether to roll back `triggers` of the `deploymentConfig`
207
+* `includeReplicationMeta` - whether to roll back `replicas` and `selector` of the `deploymentConfig`
208
+* `includeStrategy` - whether to roll back the `strategy` of the `deploymentConfig`
209
+
210
+Note that `namespace` is specified on the `rollback` itself, and will be used as the namespace from which to obtain the `deployment` specified in `from`.
... ...
@@ -1,18 +1,22 @@
1 1
 package deployer
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
5
+	"strconv"
6 6
 
7 7
 	"github.com/golang/glog"
8 8
 	"github.com/spf13/cobra"
9 9
 
10 10
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
12
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
11 13
 
12 14
 	"github.com/openshift/origin/pkg/api/latest"
13 15
 	"github.com/openshift/origin/pkg/cmd/util"
14 16
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
17
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
15 18
 	strategy "github.com/openshift/origin/pkg/deploy/strategy/recreate"
19
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
16 20
 )
17 21
 
18 22
 const longCommandDesc = `
... ...
@@ -27,7 +31,12 @@ type config struct {
27 27
 	Namespace      string
28 28
 }
29 29
 
30
-// NewCommandDeployer provides a CLI handler for deploy
30
+type replicationControllerGetter interface {
31
+	Get(namespace, name string) (*kapi.ReplicationController, error)
32
+	List(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error)
33
+}
34
+
35
+// NewCommandDeployer provides a CLI handler for deploy.
31 36
 func NewCommandDeployer(name string) *cobra.Command {
32 37
 	cfg := &config{
33 38
 		Config: clientcmd.NewConfig(),
... ...
@@ -38,7 +47,20 @@ func NewCommandDeployer(name string) *cobra.Command {
38 38
 		Short: "Run the OpenShift deployer",
39 39
 		Long:  longCommandDesc,
40 40
 		Run: func(c *cobra.Command, args []string) {
41
-			if err := deploy(cfg); err != nil {
41
+			kClient, _, err := cfg.Config.Clients()
42
+			if err != nil {
43
+				glog.Fatal(err)
44
+			}
45
+
46
+			if len(cfg.DeploymentName) == 0 {
47
+				glog.Fatal("deployment is required")
48
+			}
49
+
50
+			if len(cfg.Namespace) == 0 {
51
+				glog.Fatal("namespace is required")
52
+			}
53
+
54
+			if err = deploy(kClient, cfg.Namespace, cfg.DeploymentName); err != nil {
42 55
 				glog.Fatal(err)
43 56
 			}
44 57
 		},
... ...
@@ -52,18 +74,11 @@ func NewCommandDeployer(name string) *cobra.Command {
52 52
 	return cmd
53 53
 }
54 54
 
55
-// deploy starts the deployer
56
-func deploy(cfg *config) error {
57
-	kClient, _, err := cfg.Config.Clients()
58
-	if err != nil {
59
-		return err
60
-	}
61
-	if len(cfg.DeploymentName) == 0 {
62
-		return errors.New("No deployment name was specified.")
63
-	}
55
+// deploy executes a deployment strategy.
56
+func deploy(kClient kclient.Interface, namespace, deploymentName string) error {
57
+	newDeployment, oldDeployments, err := getDeployerContext(&realReplicationControllerGetter{kClient}, namespace, deploymentName)
64 58
 
65
-	var deployment *kapi.ReplicationController
66
-	if deployment, err = kClient.ReplicationControllers(cfg.Namespace).Get(cfg.DeploymentName); err != nil {
59
+	if err != nil {
67 60
 		return err
68 61
 	}
69 62
 
... ...
@@ -72,5 +87,76 @@ func deploy(cfg *config) error {
72 72
 		ReplicationController: strategy.RealReplicationController{KubeClient: kClient},
73 73
 		Codec: latest.Codec,
74 74
 	}
75
-	return strategy.Deploy(deployment)
75
+
76
+	return strategy.Deploy(newDeployment, oldDeployments)
77
+}
78
+
79
+// getDeployerContext finds the target deployment and any deployments it considers to be prior to the
80
+// target deployment. Only deployments whose LatestVersion is less than the target deployment are
81
+// considered to be prior.
82
+func getDeployerContext(controllerGetter replicationControllerGetter, namespace, deploymentName string) (*kapi.ReplicationController, []kapi.ObjectReference, error) {
83
+	var err error
84
+	var newDeployment *kapi.ReplicationController
85
+	var newConfig *deployapi.DeploymentConfig
86
+
87
+	// Look up the new deployment and its associated config.
88
+	if newDeployment, err = controllerGetter.Get(namespace, deploymentName); err != nil {
89
+		return nil, nil, err
90
+	}
91
+
92
+	if newConfig, err = deployutil.DecodeDeploymentConfig(newDeployment, latest.Codec); err != nil {
93
+		return nil, nil, err
94
+	}
95
+
96
+	glog.Infof("Found new deployment %s for config %s with latestVersion %d", newDeployment.Name, newConfig.Name, newConfig.LatestVersion)
97
+
98
+	// Collect all deployments that predate the new one by comparing all old ReplicationControllers with
99
+	// encoded DeploymentConfigs to the new one by LatestVersion. Treat a failure to interpret a given
100
+	// old deployment as a fatal error to prevent overlapping deployments.
101
+	var allControllers *kapi.ReplicationControllerList
102
+	oldDeployments := []kapi.ObjectReference{}
103
+
104
+	if allControllers, err = controllerGetter.List(newDeployment.Namespace, labels.Everything()); err != nil {
105
+		return nil, nil, fmt.Errorf("Unable to get list replication controllers in deployment namespace %s: %v", newDeployment.Namespace, err)
106
+	}
107
+
108
+	glog.Infof("Inspecting %d potential prior deployments", len(allControllers.Items))
109
+	for _, controller := range allControllers.Items {
110
+		if configName, hasConfigName := controller.Annotations[deployapi.DeploymentConfigAnnotation]; !hasConfigName {
111
+			glog.Infof("Disregarding replicationController %s (not a deployment)", controller.Name)
112
+			continue
113
+		} else if configName != newConfig.Name {
114
+			glog.Infof("Disregarding deployment %s (doesn't match target deploymentConfig %s)", controller.Name, configName)
115
+			continue
116
+		}
117
+
118
+		var oldVersion int
119
+		if oldVersion, err = strconv.Atoi(controller.Annotations[deployapi.DeploymentVersionAnnotation]); err != nil {
120
+			return nil, nil, fmt.Errorf("Couldn't determine version of deployment %s: %v", controller.Name, err)
121
+		}
122
+
123
+		if oldVersion < newConfig.LatestVersion {
124
+			glog.Infof("Marking deployment %s as a prior deployment", controller.Name)
125
+			oldDeployments = append(oldDeployments, kapi.ObjectReference{
126
+				Namespace: controller.Namespace,
127
+				Name:      controller.Name,
128
+			})
129
+		} else {
130
+			glog.Infof("Disregarding deployment %s (same as or newer than target)", controller.Name)
131
+		}
132
+	}
133
+
134
+	return newDeployment, oldDeployments, nil
135
+}
136
+
137
+type realReplicationControllerGetter struct {
138
+	kClient kclient.Interface
139
+}
140
+
141
+func (r *realReplicationControllerGetter) Get(namespace, name string) (*kapi.ReplicationController, error) {
142
+	return r.kClient.ReplicationControllers(namespace).Get(name)
143
+}
144
+
145
+func (r *realReplicationControllerGetter) List(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
146
+	return r.kClient.ReplicationControllers(namespace).List(selector)
76 147
 }
77 148
new file mode 100644
... ...
@@ -0,0 +1,188 @@
0
+package deployer
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
8
+
9
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
11
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
12
+)
13
+
14
+func TestGetDeploymentContextMissingDeployment(t *testing.T) {
15
+	getter := &testReplicationControllerGetter{
16
+		getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
17
+			return nil, kerrors.NewNotFound("replicationController", name)
18
+		},
19
+		listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
20
+			t.Fatal("unexpected list call")
21
+			return nil, nil
22
+		},
23
+	}
24
+
25
+	newDeployment, oldDeployments, err := getDeployerContext(getter, kapi.NamespaceDefault, "deployment")
26
+
27
+	if newDeployment != nil {
28
+		t.Fatalf("unexpected newDeployment: %#v", newDeployment)
29
+	}
30
+
31
+	if oldDeployments != nil {
32
+		t.Fatalf("unexpected oldDeployments: %#v", oldDeployments)
33
+	}
34
+
35
+	if err == nil {
36
+		t.Fatal("expected an error")
37
+	}
38
+}
39
+
40
+func TestGetDeploymentContextInvalidEncodedConfig(t *testing.T) {
41
+	getter := &testReplicationControllerGetter{
42
+		getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
43
+			return &kapi.ReplicationController{}, nil
44
+		},
45
+		listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
46
+			return &kapi.ReplicationControllerList{}, nil
47
+		},
48
+	}
49
+
50
+	newDeployment, oldDeployments, err := getDeployerContext(getter, kapi.NamespaceDefault, "deployment")
51
+
52
+	if newDeployment != nil {
53
+		t.Fatalf("unexpected newDeployment: %#v", newDeployment)
54
+	}
55
+
56
+	if oldDeployments != nil {
57
+		t.Fatalf("unexpected oldDeployments: %#v", oldDeployments)
58
+	}
59
+
60
+	if err == nil {
61
+		t.Fatal("expected an error")
62
+	}
63
+}
64
+
65
+func TestGetDeploymentContextNoPriorDeployments(t *testing.T) {
66
+	getter := &testReplicationControllerGetter{
67
+		getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
68
+			deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
69
+			return deployment, nil
70
+		},
71
+		listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
72
+			return &kapi.ReplicationControllerList{}, nil
73
+		},
74
+	}
75
+
76
+	newDeployment, oldDeployments, err := getDeployerContext(getter, kapi.NamespaceDefault, "deployment")
77
+
78
+	if err != nil {
79
+		t.Fatalf("unexpected error: %v", err)
80
+	}
81
+
82
+	if newDeployment == nil {
83
+		t.Fatal("expected deployment")
84
+	}
85
+
86
+	if oldDeployments == nil {
87
+		t.Fatal("expected non-nil oldDeployments")
88
+	}
89
+
90
+	if len(oldDeployments) > 0 {
91
+		t.Fatalf("unexpected non-empty oldDeployments: %#v", oldDeployments)
92
+	}
93
+}
94
+
95
+func TestGetDeploymentContextWithPriorDeployments(t *testing.T) {
96
+	getter := &testReplicationControllerGetter{
97
+		getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
98
+			deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(2), kapi.Codec)
99
+			return deployment, nil
100
+		},
101
+		listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
102
+			deployment1, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
103
+			deployment2, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(2), kapi.Codec)
104
+			deployment3, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(3), kapi.Codec)
105
+			deployment4, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
106
+			deployment4.Annotations[deployapi.DeploymentConfigAnnotation] = "another-config"
107
+			return &kapi.ReplicationControllerList{
108
+				Items: []kapi.ReplicationController{
109
+					*deployment1,
110
+					*deployment2,
111
+					*deployment3,
112
+					*deployment4,
113
+					{},
114
+				},
115
+			}, nil
116
+		},
117
+	}
118
+
119
+	newDeployment, oldDeployments, err := getDeployerContext(getter, kapi.NamespaceDefault, "deployment")
120
+
121
+	if err != nil {
122
+		t.Fatalf("unexpected error: %v", err)
123
+	}
124
+
125
+	if newDeployment == nil {
126
+		t.Fatal("expected deployment")
127
+	}
128
+
129
+	if oldDeployments == nil {
130
+		t.Fatal("expected non-nil oldDeployments")
131
+	}
132
+
133
+	if e, a := 1, len(oldDeployments); e != a {
134
+		t.Fatalf("expected oldDeployments with size %d, got %d: %#v", e, a, oldDeployments)
135
+	}
136
+}
137
+
138
+func TestGetDeploymentContextInvalidPriorDeployment(t *testing.T) {
139
+	getter := &testReplicationControllerGetter{
140
+		getFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
141
+			deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
142
+			return deployment, nil
143
+		},
144
+		listFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
145
+			return &kapi.ReplicationControllerList{
146
+				Items: []kapi.ReplicationController{
147
+					{
148
+						ObjectMeta: kapi.ObjectMeta{
149
+							Name: "corrupt-deployment",
150
+							Annotations: map[string]string{
151
+								deployapi.DeploymentConfigAnnotation:  "config",
152
+								deployapi.DeploymentVersionAnnotation: "junk",
153
+							},
154
+						},
155
+					},
156
+				},
157
+			}, nil
158
+		},
159
+	}
160
+
161
+	newDeployment, oldDeployments, err := getDeployerContext(getter, kapi.NamespaceDefault, "deployment")
162
+
163
+	if newDeployment != nil {
164
+		t.Fatalf("unexpected newDeployment: %#v", newDeployment)
165
+	}
166
+
167
+	if oldDeployments != nil {
168
+		t.Fatalf("unexpected oldDeployments: %#v", oldDeployments)
169
+	}
170
+
171
+	if err == nil {
172
+		t.Fatal("expected an error")
173
+	}
174
+}
175
+
176
+type testReplicationControllerGetter struct {
177
+	getFunc  func(namespace, name string) (*kapi.ReplicationController, error)
178
+	listFunc func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error)
179
+}
180
+
181
+func (t *testReplicationControllerGetter) Get(namespace, name string) (*kapi.ReplicationController, error) {
182
+	return t.getFunc(namespace, name)
183
+}
184
+
185
+func (t *testReplicationControllerGetter) List(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
186
+	return t.listFunc(namespace, selector)
187
+}
... ...
@@ -44,11 +44,13 @@ import (
44 44
 	osclient "github.com/openshift/origin/pkg/client"
45 45
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
46 46
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
47
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
47 48
 	deploycontrollerfactory "github.com/openshift/origin/pkg/deploy/controller/factory"
48 49
 	deployconfiggenerator "github.com/openshift/origin/pkg/deploy/generator"
49 50
 	deployregistry "github.com/openshift/origin/pkg/deploy/registry/deploy"
50 51
 	deployconfigregistry "github.com/openshift/origin/pkg/deploy/registry/deployconfig"
51 52
 	deployetcd "github.com/openshift/origin/pkg/deploy/registry/etcd"
53
+	deployrollback "github.com/openshift/origin/pkg/deploy/rollback"
52 54
 	imageetcd "github.com/openshift/origin/pkg/image/registry/etcd"
53 55
 	"github.com/openshift/origin/pkg/image/registry/image"
54 56
 	"github.com/openshift/origin/pkg/image/registry/imagerepository"
... ...
@@ -197,13 +199,18 @@ func (c *MasterConfig) InstallAPI(container *restful.Container) []string {
197 197
 	userEtcd := useretcd.New(c.EtcdHelper, user.NewDefaultUserInitStrategy())
198 198
 	oauthEtcd := oauthetcd.New(c.EtcdHelper)
199 199
 
200
+	osclient, kclient := c.DeploymentConfigControllerClients()
200 201
 	deployConfigGenerator := &deployconfiggenerator.DeploymentConfigGenerator{
201
-		DeploymentInterface:       &clientDeploymentInterface{c.DeploymentClient()},
202
+		DeploymentInterface:       &oldClientDeploymentInterface{kclient},
202 203
 		DeploymentConfigInterface: deployEtcd,
203 204
 		ImageRepositoryInterface:  imageEtcd,
204 205
 		Codec: latest.Codec,
205 206
 	}
206 207
 
208
+	deployRollbackGenerator := &deployrollback.RollbackGenerator{}
209
+	rollbackDeploymentGetter := &clientDeploymentInterface{kclient}
210
+	rollbackDeploymentConfigGetter := &clientDeploymentConfigInterface{osclient}
211
+
207 212
 	defaultRegistry := env("OPENSHIFT_DEFAULT_REGISTRY", "")
208 213
 
209 214
 	// initialize OpenShift API
... ...
@@ -220,6 +227,7 @@ func (c *MasterConfig) InstallAPI(container *restful.Container) []string {
220 220
 		"deployments":               deployregistry.NewREST(deployEtcd),
221 221
 		"deploymentConfigs":         deployconfigregistry.NewREST(deployEtcd),
222 222
 		"generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, v1beta1.Codec),
223
+		"deploymentConfigRollbacks": deployrollback.NewREST(deployRollbackGenerator, rollbackDeploymentGetter, rollbackDeploymentConfigGetter, latest.Codec),
223 224
 
224 225
 		"templateConfigs": templateregistry.NewREST(),
225 226
 
... ...
@@ -561,10 +569,26 @@ func (c ClientWebhookInterface) GetBuildConfig(namespace, name string) (*buildap
561 561
 	return c.Client.BuildConfigs(namespace).Get(name)
562 562
 }
563 563
 
564
+type oldClientDeploymentInterface struct {
565
+	KubeClient kclient.Interface
566
+}
567
+
568
+func (c *oldClientDeploymentInterface) GetDeployment(ctx api.Context, name string) (*api.ReplicationController, error) {
569
+	return c.KubeClient.ReplicationControllers(api.Namespace(ctx)).Get(name)
570
+}
571
+
564 572
 type clientDeploymentInterface struct {
565 573
 	KubeClient kclient.Interface
566 574
 }
567 575
 
568
-func (c *clientDeploymentInterface) GetDeployment(ctx api.Context, id string) (*api.ReplicationController, error) {
569
-	return c.KubeClient.ReplicationControllers(api.Namespace(ctx)).Get(id)
576
+func (c *clientDeploymentInterface) GetDeployment(namespace, name string) (*api.ReplicationController, error) {
577
+	return c.KubeClient.ReplicationControllers(namespace).Get(name)
578
+}
579
+
580
+type clientDeploymentConfigInterface struct {
581
+	Client osclient.Interface
582
+}
583
+
584
+func (c *clientDeploymentConfigInterface) GetDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error) {
585
+	return c.Client.DeploymentConfigs(namespace).Get(name)
570 586
 }
... ...
@@ -10,10 +10,12 @@ func init() {
10 10
 		&DeploymentList{},
11 11
 		&DeploymentConfig{},
12 12
 		&DeploymentConfigList{},
13
+		&DeploymentConfigRollback{},
13 14
 	)
14 15
 }
15 16
 
16
-func (*Deployment) IsAnAPIObject()           {}
17
-func (*DeploymentList) IsAnAPIObject()       {}
18
-func (*DeploymentConfig) IsAnAPIObject()     {}
19
-func (*DeploymentConfigList) IsAnAPIObject() {}
17
+func (*Deployment) IsAnAPIObject()               {}
18
+func (*DeploymentList) IsAnAPIObject()           {}
19
+func (*DeploymentConfig) IsAnAPIObject()         {}
20
+func (*DeploymentConfigList) IsAnAPIObject()     {}
21
+func (*DeploymentConfigRollback) IsAnAPIObject() {}
... ...
@@ -1,38 +1,43 @@
1 1
 package test
2 2
 
3 3
 import (
4
+	"speter.net/go/exp/math/dec/inf"
5
+
4 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
5
-	"github.com/openshift/origin/pkg/deploy/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
8
+
9
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
6 10
 )
7 11
 
8
-func OkStrategy() api.DeploymentStrategy {
9
-	return api.DeploymentStrategy{
10
-		Type: api.DeploymentStrategyTypeRecreate,
12
+func OkStrategy() deployapi.DeploymentStrategy {
13
+	return deployapi.DeploymentStrategy{
14
+		Type: deployapi.DeploymentStrategyTypeRecreate,
11 15
 	}
12 16
 }
13 17
 
14
-func OkCustomStrategy() api.DeploymentStrategy {
15
-	return api.DeploymentStrategy{
16
-		Type:         api.DeploymentStrategyTypeCustom,
18
+func OkCustomStrategy() deployapi.DeploymentStrategy {
19
+	return deployapi.DeploymentStrategy{
20
+		Type:         deployapi.DeploymentStrategyTypeCustom,
17 21
 		CustomParams: OkCustomParams(),
18 22
 	}
19 23
 }
20 24
 
21
-func OkCustomParams() *api.CustomDeploymentStrategyParams {
22
-	return &api.CustomDeploymentStrategyParams{
25
+func OkCustomParams() *deployapi.CustomDeploymentStrategyParams {
26
+	return &deployapi.CustomDeploymentStrategyParams{
23 27
 		Image: "openshift/origin-deployer",
24 28
 	}
25 29
 }
26 30
 
27 31
 func OkControllerTemplate() kapi.ReplicationControllerSpec {
28 32
 	return kapi.ReplicationControllerSpec{
33
+		Replicas: 1,
29 34
 		Selector: OkSelector(),
30 35
 		Template: OkPodTemplate(),
31 36
 	}
32 37
 }
33 38
 
34
-func OkDeploymentTemplate() api.DeploymentTemplate {
35
-	return api.DeploymentTemplate{
39
+func OkDeploymentTemplate() deployapi.DeploymentTemplate {
40
+	return deployapi.DeploymentTemplate{
36 41
 		Strategy:           OkStrategy(),
37 42
 		ControllerTemplate: OkControllerTemplate(),
38 43
 	}
... ...
@@ -44,9 +49,55 @@ func OkSelector() map[string]string {
44 44
 
45 45
 func OkPodTemplate() *kapi.PodTemplateSpec {
46 46
 	return &kapi.PodTemplateSpec{
47
-		Spec: kapi.PodSpec{},
47
+		Spec: kapi.PodSpec{
48
+			Containers: []kapi.Container{
49
+				{
50
+					Name:   "container1",
51
+					Image:  "registry:8080/repo1:ref1",
52
+					CPU:    resource.Quantity{Amount: inf.NewDec(0, 3), Format: "DecimalSI"},
53
+					Memory: resource.Quantity{Amount: inf.NewDec(0, 0), Format: "DecimalSI"},
54
+				},
55
+				{
56
+					Name:   "container2",
57
+					Image:  "registry:8080/repo1:ref2",
58
+					CPU:    resource.Quantity{Amount: inf.NewDec(0, 3), Format: "DecimalSI"},
59
+					Memory: resource.Quantity{Amount: inf.NewDec(0, 0), Format: "DecimalSI"},
60
+				},
61
+			},
62
+		},
48 63
 		ObjectMeta: kapi.ObjectMeta{
49 64
 			Labels: OkSelector(),
50 65
 		},
51 66
 	}
52 67
 }
68
+
69
+func OkConfigChangeTrigger() deployapi.DeploymentTriggerPolicy {
70
+	return deployapi.DeploymentTriggerPolicy{
71
+		Type: deployapi.DeploymentTriggerOnConfigChange,
72
+	}
73
+}
74
+
75
+func OkImageChangeTrigger() deployapi.DeploymentTriggerPolicy {
76
+	return deployapi.DeploymentTriggerPolicy{
77
+		Type: deployapi.DeploymentTriggerOnImageChange,
78
+		ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
79
+			Automatic: true,
80
+			ContainerNames: []string{
81
+				"container1",
82
+			},
83
+			RepositoryName: "registry:8080/repo1",
84
+			Tag:            "tag1",
85
+		},
86
+	}
87
+}
88
+
89
+func OkDeploymentConfig(version int) *deployapi.DeploymentConfig {
90
+	return &deployapi.DeploymentConfig{
91
+		ObjectMeta:    kapi.ObjectMeta{Name: "config"},
92
+		LatestVersion: version,
93
+		Triggers: []deployapi.DeploymentTriggerPolicy{
94
+			OkImageChangeTrigger(),
95
+		},
96
+		Template: OkDeploymentTemplate(),
97
+	}
98
+}
... ...
@@ -206,3 +206,24 @@ type DeploymentConfigList struct {
206 206
 	kapi.ListMeta `json:"metadata,omitempty"`
207 207
 	Items         []DeploymentConfig `json:"items"`
208 208
 }
209
+
210
+// DeploymentConfigRollback provides the input to rollback generation.
211
+type DeploymentConfigRollback struct {
212
+	kapi.TypeMeta `json:",inline"`
213
+	// Spec defines the options to rollback generation.
214
+	Spec DeploymentConfigRollbackSpec `json:"spec"`
215
+}
216
+
217
+// DeploymentConfigRollbackSpec represents the options for rollback generation.
218
+type DeploymentConfigRollbackSpec struct {
219
+	// From points to a ReplicationController which is a deployment.
220
+	From kapi.ObjectReference `json:"from"`
221
+	// IncludeTriggers specifies whether to include config Triggers.
222
+	IncludeTriggers bool `json:"includeTriggers`
223
+	// IncludeTemplate specifies whether to include the PodTemplateSpec.
224
+	IncludeTemplate bool `json:"includeTemplate`
225
+	// IncludeReplicationMeta specifies whether to include the replica count and selector.
226
+	IncludeReplicationMeta bool `json:"includeReplicationMeta`
227
+	// IncludeStrategy specifies whether to include the deployment Strategy.
228
+	IncludeStrategy bool `json:"includeStrategy`
229
+}
... ...
@@ -10,10 +10,12 @@ func init() {
10 10
 		&DeploymentList{},
11 11
 		&DeploymentConfig{},
12 12
 		&DeploymentConfigList{},
13
+		&DeploymentConfigRollback{},
13 14
 	)
14 15
 }
15 16
 
16
-func (*Deployment) IsAnAPIObject()           {}
17
-func (*DeploymentList) IsAnAPIObject()       {}
18
-func (*DeploymentConfig) IsAnAPIObject()     {}
19
-func (*DeploymentConfigList) IsAnAPIObject() {}
17
+func (*Deployment) IsAnAPIObject()               {}
18
+func (*DeploymentList) IsAnAPIObject()           {}
19
+func (*DeploymentConfig) IsAnAPIObject()         {}
20
+func (*DeploymentConfigList) IsAnAPIObject()     {}
21
+func (*DeploymentConfigRollback) IsAnAPIObject() {}
... ...
@@ -207,3 +207,24 @@ type DeploymentConfigList struct {
207 207
 	v1beta3.ListMeta `json:"metadata,omitempty"`
208 208
 	Items            []DeploymentConfig `json:"items"`
209 209
 }
210
+
211
+// DeploymentConfigRollback provides the input to rollback generation.
212
+type DeploymentConfigRollback struct {
213
+	v1beta3.TypeMeta `json:",inline"`
214
+	// Spec defines the options to rollback generation.
215
+	Spec DeploymentConfigRollbackSpec `json:"spec"`
216
+}
217
+
218
+// DeploymentConfigRollbackSpec represents the options for rollback generation.
219
+type DeploymentConfigRollbackSpec struct {
220
+	// From points to a ReplicationController which is a deployment.
221
+	From v1beta3.ObjectReference `json:"from"`
222
+	// IncludeTriggers specifies whether to include config Triggers.
223
+	IncludeTriggers bool `json:"includeTriggers`
224
+	// IncludeTemplate specifies whether to include the PodTemplateSpec.
225
+	IncludeTemplate bool `json:"includeTemplate`
226
+	// IncludeReplicationMeta specifies whether to include the replica count and selector.
227
+	IncludeReplicationMeta bool `json:"includeReplicationMeta`
228
+	// IncludeStrategy specifies whether to include the deployment Strategy.
229
+	IncludeStrategy bool `json:"includeStrategy`
230
+}
... ...
@@ -32,6 +32,24 @@ func ValidateDeploymentConfig(config *deployapi.DeploymentConfig) errors.Validat
32 32
 	return result
33 33
 }
34 34
 
35
+func ValidateDeploymentConfigRollback(rollback *deployapi.DeploymentConfigRollback) errors.ValidationErrorList {
36
+	result := errors.ValidationErrorList{}
37
+
38
+	if len(rollback.Spec.From.Name) == 0 {
39
+		result = append(result, errors.NewFieldRequired("spec.from.name", ""))
40
+	}
41
+
42
+	if len(rollback.Spec.From.Kind) == 0 {
43
+		rollback.Spec.From.Kind = "ReplicationController"
44
+	}
45
+
46
+	if rollback.Spec.From.Kind != "ReplicationController" {
47
+		result = append(result, errors.NewFieldInvalid("spec.from.kind", rollback.Spec.From.Kind, "the kind of the rollback target must be 'ReplicationController'"))
48
+	}
49
+
50
+	return result
51
+}
52
+
35 53
 func validateDeploymentStrategy(strategy *deployapi.DeploymentStrategy) errors.ValidationErrorList {
36 54
 	result := errors.ValidationErrorList{}
37 55
 
... ...
@@ -3,7 +3,9 @@ package validation
3 3
 import (
4 4
 	"testing"
5 5
 
6
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6 7
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
8
+
7 9
 	"github.com/openshift/origin/pkg/deploy/api"
8 10
 	"github.com/openshift/origin/pkg/deploy/api/test"
9 11
 )
... ...
@@ -180,3 +182,67 @@ func TestValidateDeploymentConfigMissingFields(t *testing.T) {
180 180
 		}
181 181
 	}
182 182
 }
183
+
184
+func TestValidateDeploymentConfigRollbackOK(t *testing.T) {
185
+	rollback := &api.DeploymentConfigRollback{
186
+		Spec: api.DeploymentConfigRollbackSpec{
187
+			From: kapi.ObjectReference{
188
+				Name: "deployment",
189
+			},
190
+		},
191
+	}
192
+
193
+	errs := ValidateDeploymentConfigRollback(rollback)
194
+	if len(errs) > 0 {
195
+		t.Errorf("Unxpected non-empty error list: %v", errs)
196
+	}
197
+
198
+	if e, a := "ReplicationController", rollback.Spec.From.Kind; e != a {
199
+		t.Errorf("expected kind %s, got %s")
200
+	}
201
+}
202
+
203
+func TestValidateDeploymentConfigRollbackInvalidFields(t *testing.T) {
204
+	errorCases := map[string]struct {
205
+		D api.DeploymentConfigRollback
206
+		T errors.ValidationErrorType
207
+		F string
208
+	}{
209
+		"missing spec.from.name": {
210
+			api.DeploymentConfigRollback{
211
+				Spec: api.DeploymentConfigRollbackSpec{
212
+					From: kapi.ObjectReference{},
213
+				},
214
+			},
215
+			errors.ValidationErrorTypeRequired,
216
+			"spec.from.name",
217
+		},
218
+		"wrong spec.from.kind": {
219
+			api.DeploymentConfigRollback{
220
+				Spec: api.DeploymentConfigRollbackSpec{
221
+					From: kapi.ObjectReference{
222
+						Kind: "unknown",
223
+						Name: "deployment",
224
+					},
225
+				},
226
+			},
227
+			errors.ValidationErrorTypeInvalid,
228
+			"spec.from.kind",
229
+		},
230
+	}
231
+
232
+	for k, v := range errorCases {
233
+		errs := ValidateDeploymentConfigRollback(&v.D)
234
+		if len(errs) == 0 {
235
+			t.Errorf("Expected failure for scenario %s", k)
236
+		}
237
+		for i := range errs {
238
+			if errs[i].(*errors.ValidationError).Type != v.T {
239
+				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
240
+			}
241
+			if errs[i].(*errors.ValidationError).Field != v.F {
242
+				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
243
+			}
244
+		}
245
+	}
246
+}
... ...
@@ -13,10 +13,9 @@ import (
13 13
 )
14 14
 
15 15
 // DeploymentConfigChangeController watches for changes to DeploymentConfigs and regenerates them only
16
-// when detecting a change to the PodTemplate of a DeploymentConfig containing a ConfigChange
17
-// trigger.
16
+// when detecting a change to the PodTemplate of a DeploymentConfig containing a ConfigChange trigger.
18 17
 type DeploymentConfigChangeController struct {
19
-	ChangeStrategy       changeStrategy
18
+	ChangeStrategy       ChangeStrategy
20 19
 	NextDeploymentConfig func() *deployapi.DeploymentConfig
21 20
 	DeploymentStore      cache.Store
22 21
 	Codec                runtime.Codec
... ...
@@ -24,7 +23,8 @@ type DeploymentConfigChangeController struct {
24 24
 	Stop <-chan struct{}
25 25
 }
26 26
 
27
-type changeStrategy interface {
27
+// ChangeStrategy knows how to generate and update DeploymentConfigs.
28
+type ChangeStrategy interface {
28 29
 	GenerateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error)
29 30
 	UpdateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
30 31
 }
... ...
@@ -3,13 +3,11 @@ package controller
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"speter.net/go/exp/math/dec/inf"
7
-
8 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
10 7
 
11 8
 	api "github.com/openshift/origin/pkg/api/latest"
12 9
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
+	deployapitest "github.com/openshift/origin/pkg/deploy/api/test"
13 11
 	deploytest "github.com/openshift/origin/pkg/deploy/controller/test"
14 12
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
15 13
 )
... ...
@@ -32,7 +30,9 @@ func TestNewConfigWithoutTrigger(t *testing.T) {
32 32
 			},
33 33
 		},
34 34
 		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
35
-			return newConfigWithoutTrigger()
35
+			config := deployapitest.OkDeploymentConfig(1)
36
+			config.Triggers = []deployapi.DeploymentTriggerPolicy{}
37
+			return config
36 38
 		},
37 39
 		DeploymentStore: deploytest.NewFakeDeploymentStore(nil),
38 40
 	}
... ...
@@ -49,17 +49,13 @@ func TestNewConfigWithoutTrigger(t *testing.T) {
49 49
 }
50 50
 
51 51
 func TestNewConfigWithTrigger(t *testing.T) {
52
-	var (
53
-		generatedName string
54
-		updated       *deployapi.DeploymentConfig
55
-	)
52
+	var updated *deployapi.DeploymentConfig
56 53
 
57 54
 	controller := &DeploymentConfigChangeController{
58 55
 		Codec: api.Codec,
59 56
 		ChangeStrategy: &testChangeStrategy{
60 57
 			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
61
-				generatedName = name
62
-				return generatedConfig(), nil
58
+				return deployapitest.OkDeploymentConfig(1), nil
63 59
 			},
64 60
 			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
65 61
 				updated = config
... ...
@@ -67,20 +63,24 @@ func TestNewConfigWithTrigger(t *testing.T) {
67 67
 			},
68 68
 		},
69 69
 		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
70
-			return newConfigWithTrigger()
70
+			config := deployapitest.OkDeploymentConfig(0)
71
+			config.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()}
72
+			return config
71 73
 		},
72 74
 		DeploymentStore: deploytest.NewFakeDeploymentStore(nil),
73 75
 	}
74 76
 
75 77
 	controller.HandleDeploymentConfig()
76 78
 
77
-	if generatedName != "test-deploy-config" {
78
-		t.Fatalf("Unexpected generated config id.  Expected test-deploy-config, got: %v", generatedName)
79
+	if updated == nil {
80
+		t.Fatalf("expected config to be updated")
79 81
 	}
80 82
 
81
-	if updated.Name != "test-deploy-config" {
82
-		t.Fatalf("Unexpected updated config id.  Expected test-deploy-config, got: %v", updated.Name)
83
-	} else if updated.Details == nil {
83
+	if e, a := 1, updated.LatestVersion; e != a {
84
+		t.Fatalf("expected update to latestversion=%d, got %d", e, a)
85
+	}
86
+
87
+	if updated.Details == nil {
84 88
 		t.Fatalf("expected config change details to be set")
85 89
 	} else if updated.Details.Causes == nil {
86 90
 		t.Fatalf("expected config change causes to be set")
... ...
@@ -91,17 +91,14 @@ func TestNewConfigWithTrigger(t *testing.T) {
91 91
 
92 92
 // Test the controller's response when the pod template is changed
93 93
 func TestChangeWithTemplateDiff(t *testing.T) {
94
-	var (
95
-		generatedName string
96
-		updated       *deployapi.DeploymentConfig
97
-	)
94
+	var updated *deployapi.DeploymentConfig
95
+	deployment, _ := deployutil.MakeDeployment(deployapitest.OkDeploymentConfig(1), kapi.Codec)
98 96
 
99 97
 	controller := &DeploymentConfigChangeController{
100 98
 		Codec: api.Codec,
101 99
 		ChangeStrategy: &testChangeStrategy{
102 100
 			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
103
-				generatedName = name
104
-				return generatedExistingConfig(), nil
101
+				return deployapitest.OkDeploymentConfig(2), nil
105 102
 			},
106 103
 			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
107 104
 				updated = config
... ...
@@ -109,20 +106,25 @@ func TestChangeWithTemplateDiff(t *testing.T) {
109 109
 			},
110 110
 		},
111 111
 		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
112
-			return diffedConfig()
112
+			config := deployapitest.OkDeploymentConfig(1)
113
+			config.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()}
114
+			config.Template.ControllerTemplate.Template.Spec.Containers[1].Name = "modified"
115
+			return config
113 116
 		},
114
-		DeploymentStore: deploytest.NewFakeDeploymentStore(matchingInitialDeployment(generatedConfig())),
117
+		DeploymentStore: deploytest.NewFakeDeploymentStore(deployment),
115 118
 	}
116 119
 
117 120
 	controller.HandleDeploymentConfig()
118 121
 
119
-	if generatedName != "test-deploy-config" {
120
-		t.Fatalf("Unexpected generated config id.  Expected test-deploy-config, got: %v", generatedName)
122
+	if updated == nil {
123
+		t.Fatalf("expected config to be updated")
121 124
 	}
122 125
 
123
-	if updated.Name != "test-deploy-config" {
124
-		t.Fatalf("Unexpected updated config id.  Expected test-deploy-config, got: %v", updated.Name)
125
-	} else if updated.Details == nil {
126
+	if e, a := 2, updated.LatestVersion; e != a {
127
+		t.Fatalf("expected update to latestversion=%d, got %d", e, a)
128
+	}
129
+
130
+	if updated.Details == nil {
126 131
 		t.Fatalf("expected config change details to be set")
127 132
 	} else if updated.Details.Causes == nil {
128 133
 		t.Fatalf("expected config change causes to be set")
... ...
@@ -132,7 +134,11 @@ func TestChangeWithTemplateDiff(t *testing.T) {
132 132
 }
133 133
 
134 134
 func TestChangeWithoutTemplateDiff(t *testing.T) {
135
-	config := existingConfigWithTrigger()
135
+	config := deployapitest.OkDeploymentConfig(1)
136
+	config.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()}
137
+
138
+	deployment, _ := deployutil.MakeDeployment(deployapitest.OkDeploymentConfig(1), kapi.Codec)
139
+
136 140
 	generated := false
137 141
 	updated := false
138 142
 
... ...
@@ -151,7 +157,7 @@ func TestChangeWithoutTemplateDiff(t *testing.T) {
151 151
 		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
152 152
 			return config
153 153
 		},
154
-		DeploymentStore: deploytest.NewFakeDeploymentStore(matchingInitialDeployment(config)),
154
+		DeploymentStore: deploytest.NewFakeDeploymentStore(deployment),
155 155
 	}
156 156
 
157 157
 	controller.HandleDeploymentConfig()
... ...
@@ -177,145 +183,3 @@ func (i *testChangeStrategy) GenerateDeploymentConfig(namespace, name string) (*
177 177
 func (i *testChangeStrategy) UpdateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
178 178
 	return i.UpdateDeploymentConfigFunc(namespace, config)
179 179
 }
180
-
181
-func existingConfigWithTrigger() *deployapi.DeploymentConfig {
182
-	return &deployapi.DeploymentConfig{
183
-		ObjectMeta: kapi.ObjectMeta{Name: "test-deploy-config"},
184
-		Triggers: []deployapi.DeploymentTriggerPolicy{
185
-			{
186
-				Type: deployapi.DeploymentTriggerOnConfigChange,
187
-			},
188
-		},
189
-		LatestVersion: 2,
190
-		Template: deployapi.DeploymentTemplate{
191
-			ControllerTemplate: kapi.ReplicationControllerSpec{
192
-				Replicas: 1,
193
-				Selector: map[string]string{
194
-					"name": "test-pod",
195
-				},
196
-				Template: &kapi.PodTemplateSpec{
197
-					ObjectMeta: kapi.ObjectMeta{
198
-						Labels: map[string]string{
199
-							"name": "test-pod",
200
-						},
201
-					},
202
-					Spec: kapi.PodSpec{
203
-						Containers: []kapi.Container{
204
-							{
205
-								Name:   "container-1",
206
-								Image:  "registry:8080/openshift/test-image:ref-1",
207
-								CPU:    resource.Quantity{Amount: inf.NewDec(0, 3), Format: "DecimalSI"},
208
-								Memory: resource.Quantity{Amount: inf.NewDec(0, 0), Format: "DecimalSI"},
209
-							},
210
-						},
211
-					},
212
-				},
213
-			},
214
-		},
215
-	}
216
-}
217
-
218
-func newConfigWithTrigger() *deployapi.DeploymentConfig {
219
-	config := existingConfigWithTrigger()
220
-	config.LatestVersion = 0
221
-	return config
222
-}
223
-
224
-func newConfigWithoutTrigger() *deployapi.DeploymentConfig {
225
-	config := existingConfigWithTrigger()
226
-	config.LatestVersion = 0
227
-	config.Triggers = []deployapi.DeploymentTriggerPolicy{}
228
-	return config
229
-}
230
-
231
-func diffedConfig() *deployapi.DeploymentConfig {
232
-	return &deployapi.DeploymentConfig{
233
-		ObjectMeta: kapi.ObjectMeta{Name: "test-deploy-config"},
234
-		Triggers: []deployapi.DeploymentTriggerPolicy{
235
-			{
236
-				Type: deployapi.DeploymentTriggerOnConfigChange,
237
-			},
238
-		},
239
-		LatestVersion: 2,
240
-		Template: deployapi.DeploymentTemplate{
241
-			ControllerTemplate: kapi.ReplicationControllerSpec{
242
-				Replicas: 1,
243
-				Selector: map[string]string{
244
-					"name": "test-pod-2",
245
-				},
246
-				Template: &kapi.PodTemplateSpec{
247
-					ObjectMeta: kapi.ObjectMeta{
248
-						Labels: map[string]string{
249
-							"name": "test-pod-2",
250
-						},
251
-					},
252
-					Spec: kapi.PodSpec{
253
-						Containers: []kapi.Container{
254
-							{
255
-								Name:  "container-2",
256
-								Image: "registry:8080/openshift/test-image:ref-1",
257
-							},
258
-						},
259
-					},
260
-				},
261
-			},
262
-		},
263
-	}
264
-}
265
-
266
-func generatedExistingConfig() *deployapi.DeploymentConfig {
267
-	return &deployapi.DeploymentConfig{
268
-		ObjectMeta: kapi.ObjectMeta{Name: "test-deploy-config"},
269
-		Triggers: []deployapi.DeploymentTriggerPolicy{
270
-			{
271
-				Type: deployapi.DeploymentTriggerOnConfigChange,
272
-			},
273
-		},
274
-		LatestVersion: 3,
275
-		Template: deployapi.DeploymentTemplate{
276
-			ControllerTemplate: kapi.ReplicationControllerSpec{
277
-				Replicas: 1,
278
-				Selector: map[string]string{
279
-					"name": "test-pod",
280
-				},
281
-				Template: &kapi.PodTemplateSpec{
282
-					ObjectMeta: kapi.ObjectMeta{
283
-						Labels: map[string]string{
284
-							"name": "test-pod",
285
-						},
286
-					},
287
-					Spec: kapi.PodSpec{
288
-						Containers: []kapi.Container{
289
-							{
290
-								Name:  "container-1",
291
-								Image: "registry:8080/openshift/test-image:ref-2",
292
-							},
293
-						},
294
-					},
295
-				},
296
-			},
297
-		},
298
-	}
299
-}
300
-
301
-func generatedConfig() *deployapi.DeploymentConfig {
302
-	config := generatedExistingConfig()
303
-	config.LatestVersion = 0
304
-	return config
305
-}
306
-
307
-func matchingInitialDeployment(config *deployapi.DeploymentConfig) *kapi.ReplicationController {
308
-	encodedConfig, _ := deployutil.EncodeDeploymentConfig(config, api.Codec)
309
-
310
-	return &kapi.ReplicationController{
311
-		ObjectMeta: kapi.ObjectMeta{
312
-			Name: deployutil.LatestDeploymentIDForConfig(config),
313
-			Annotations: map[string]string{
314
-				deployapi.DeploymentConfigAnnotation:        config.Name,
315
-				deployapi.DeploymentStatusAnnotation:        string(deployapi.DeploymentStatusNew),
316
-				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
317
-			},
318
-		},
319
-		Spec: config.Template.ControllerTemplate,
320
-	}
321
-}
... ...
@@ -1,8 +1,6 @@
1 1
 package controller
2 2
 
3 3
 import (
4
-	"strconv"
5
-
6 4
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7 5
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
8 6
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
... ...
@@ -55,9 +53,13 @@ func (c *DeploymentConfigController) HandleDeploymentConfig() {
55 55
 		return
56 56
 	}
57 57
 
58
-	err = c.deploy(config)
59
-	if err != nil {
60
-		glog.V(2).Infof("Error deploying config %s: %v", config.Name, err)
58
+	if deployment, err := deployutil.MakeDeployment(config, c.Codec); err != nil {
59
+		glog.V(2).Infof("Error making deployment from config %s: %v", config.Name, err)
60
+	} else {
61
+		glog.V(2).Infof("Creating new deployment from config %s", config.Name)
62
+		if _, deployErr := c.DeploymentInterface.CreateDeployment(config.Namespace, deployment); deployErr != nil {
63
+			glog.V(2).Infof("Error deploying config %s: %v", config.Name, deployErr)
64
+		}
61 65
 	}
62 66
 }
63 67
 
... ...
@@ -68,11 +70,8 @@ func (c *DeploymentConfigController) shouldDeploy(config *deployapi.DeploymentCo
68 68
 		return false, nil
69 69
 	}
70 70
 
71
-	deployment, err := c.latestDeploymentForConfig(config)
72
-	if deployment != nil {
73
-		glog.V(4).Infof("Shouldn't deploy because a deployment '%s' already exists for latest config %s", deployment.Name, config.Name)
74
-		return false, nil
75
-	}
71
+	latestDeploymentID := deployutil.LatestDeploymentIDForConfig(config)
72
+	deployment, err := c.DeploymentInterface.GetDeployment(config.Namespace, latestDeploymentID)
76 73
 
77 74
 	if err != nil {
78 75
 		if errors.IsNotFound(err) {
... ...
@@ -83,51 +82,6 @@ func (c *DeploymentConfigController) shouldDeploy(config *deployapi.DeploymentCo
83 83
 		return false, err
84 84
 	}
85 85
 
86
-	// TODO: what state would this represent?
86
+	glog.V(4).Infof("Shouldn't deploy because a deployment '%s' already exists for config %s", deployment.Name, config.Name)
87 87
 	return false, nil
88 88
 }
89
-
90
-// TODO: reduce code duplication between trigger and config controllers
91
-func (c *DeploymentConfigController) latestDeploymentForConfig(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {
92
-	latestDeploymentID := deployutil.LatestDeploymentIDForConfig(config)
93
-	deployment, err := c.DeploymentInterface.GetDeployment(config.Namespace, latestDeploymentID)
94
-	if err != nil {
95
-		// TODO: probably some error / race handling to do here
96
-		return nil, err
97
-	}
98
-
99
-	return deployment, nil
100
-}
101
-
102
-// deploy performs the work of actually creating a Deployment from the given DeploymentConfig.
103
-func (c *DeploymentConfigController) deploy(config *deployapi.DeploymentConfig) error {
104
-	var err error
105
-	var encodedConfig string
106
-
107
-	if encodedConfig, err = deployutil.EncodeDeploymentConfig(config, c.Codec); err != nil {
108
-		return err
109
-	}
110
-
111
-	deployment := &kapi.ReplicationController{
112
-		ObjectMeta: kapi.ObjectMeta{
113
-			Name: deployutil.LatestDeploymentIDForConfig(config),
114
-			Annotations: map[string]string{
115
-				deployapi.DeploymentConfigAnnotation:        config.Name,
116
-				deployapi.DeploymentStatusAnnotation:        string(deployapi.DeploymentStatusNew),
117
-				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
118
-				deployapi.DeploymentVersionAnnotation:       strconv.Itoa(config.LatestVersion),
119
-			},
120
-			Labels: config.Labels,
121
-		},
122
-		Spec: config.Template.ControllerTemplate,
123
-	}
124
-
125
-	deployment.Spec.Replicas = 0
126
-	deployment.Spec.Template.Labels[deployapi.DeploymentConfigLabel] = config.Name
127
-	// TODO: Switch this to an annotation once upstream supports annotations on a PodTemplate
128
-	deployment.Spec.Template.Labels[deployapi.DeploymentLabel] = deployment.Name
129
-
130
-	glog.V(4).Infof("Creating new deployment from config %s", config.Name)
131
-	_, err = c.DeploymentInterface.CreateDeployment(config.Namespace, deployment)
132
-	return err
133
-}
... ...
@@ -1,7 +1,6 @@
1 1
 package controller
2 2
 
3 3
 import (
4
-	"strconv"
5 4
 	"testing"
6 5
 
7 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
... ...
@@ -9,6 +8,7 @@ import (
9 9
 
10 10
 	api "github.com/openshift/origin/pkg/api/latest"
11 11
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
12 13
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
13 14
 )
14 15
 
... ...
@@ -26,9 +26,7 @@ func TestHandleNewDeploymentConfig(t *testing.T) {
26 26
 			},
27 27
 		},
28 28
 		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
29
-			deploymentConfig := manualDeploymentConfig()
30
-			deploymentConfig.LatestVersion = 0
31
-			return deploymentConfig
29
+			return deploytest.OkDeploymentConfig(0)
32 30
 		},
33 31
 	}
34 32
 
... ...
@@ -36,9 +34,7 @@ func TestHandleNewDeploymentConfig(t *testing.T) {
36 36
 }
37 37
 
38 38
 func TestHandleInitialDeployment(t *testing.T) {
39
-	deploymentConfig := manualDeploymentConfig()
40
-	deploymentConfig.LatestVersion = 1
41
-
39
+	deploymentConfig := deploytest.OkDeploymentConfig(1)
42 40
 	var deployed *kapi.ReplicationController
43 41
 
44 42
 	controller := &DeploymentConfigController{
... ...
@@ -62,35 +58,17 @@ func TestHandleInitialDeployment(t *testing.T) {
62 62
 	if deployed == nil {
63 63
 		t.Fatalf("expected a deployment")
64 64
 	}
65
-
66
-	expectedAnnotations := map[string]string{
67
-		deployapi.DeploymentConfigAnnotation:  deploymentConfig.Name,
68
-		deployapi.DeploymentStatusAnnotation:  string(deployapi.DeploymentStatusNew),
69
-		deployapi.DeploymentVersionAnnotation: strconv.Itoa(deploymentConfig.LatestVersion),
70
-	}
71
-
72
-	for key, expected := range expectedAnnotations {
73
-		if actual := deployed.Annotations[key]; actual != expected {
74
-			t.Fatalf("expected deployment annotation %s=%s, got %s", key, expected, actual)
75
-		}
76
-	}
77
-
78
-	// TODO: add stronger assertion on the encoded value once the controller methods are free
79
-	// of side effects on the deploymentConfig
80
-	if len(deployed.Annotations[deployapi.DeploymentEncodedConfigAnnotation]) == 0 {
81
-		t.Fatalf("expected deployment with DeploymentEncodedConfigAnnotation annotation")
82
-	}
83 65
 }
84 66
 
85
-func TestHandleConfigChangeNoPodTemplateDiff(t *testing.T) {
86
-	deploymentConfig := manualDeploymentConfig()
87
-	deploymentConfig.LatestVersion = 0
67
+func TestHandleConfigChangeLatestAlreadyDeployed(t *testing.T) {
68
+	deploymentConfig := deploytest.OkDeploymentConfig(0)
88 69
 
89 70
 	controller := &DeploymentConfigController{
90 71
 		Codec: api.Codec,
91 72
 		DeploymentInterface: &testDeploymentInterface{
92 73
 			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
93
-				return matchingDeployment(deploymentConfig), nil
74
+				deployment, _ := deployutil.MakeDeployment(deploymentConfig, kapi.Codec)
75
+				return deployment, nil
94 76
 			},
95 77
 			CreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {
96 78
 				t.Fatalf("unexpected call to to create deployment: %v", deployment)
... ...
@@ -105,40 +83,6 @@ func TestHandleConfigChangeNoPodTemplateDiff(t *testing.T) {
105 105
 	controller.HandleDeploymentConfig()
106 106
 }
107 107
 
108
-func TestHandleConfigChangeWithPodTemplateDiff(t *testing.T) {
109
-	deploymentConfig := manualDeploymentConfig()
110
-	deploymentConfig.LatestVersion = 2
111
-	deploymentConfig.Template.ControllerTemplate.Template.Labels["foo"] = "bar"
112
-
113
-	var deployed *kapi.ReplicationController
114
-
115
-	controller := &DeploymentConfigController{
116
-		Codec: api.Codec,
117
-		DeploymentInterface: &testDeploymentInterface{
118
-			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
119
-				return nil, kerrors.NewNotFound("deployment", name)
120
-			},
121
-			CreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {
122
-				deployed = deployment
123
-				return deployment, nil
124
-			},
125
-		},
126
-		NextDeploymentConfig: func() *deployapi.DeploymentConfig {
127
-			return deploymentConfig
128
-		},
129
-	}
130
-
131
-	controller.HandleDeploymentConfig()
132
-
133
-	if deployed == nil {
134
-		t.Fatalf("expected a deployment")
135
-	}
136
-
137
-	if e, a := deploymentConfig.Name, deployed.Annotations[deployapi.DeploymentConfigAnnotation]; e != a {
138
-		t.Fatalf("expected deployment annotated with deploymentConfig %s, got %s", e, a)
139
-	}
140
-}
141
-
142 108
 type testDeploymentInterface struct {
143 109
 	GetDeploymentFunc    func(namespace, name string) (*kapi.ReplicationController, error)
144 110
 	CreateDeploymentFunc func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error)
... ...
@@ -151,75 +95,3 @@ func (i *testDeploymentInterface) GetDeployment(namespace, name string) (*kapi.R
151 151
 func (i *testDeploymentInterface) CreateDeployment(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {
152 152
 	return i.CreateDeploymentFunc(namespace, deployment)
153 153
 }
154
-
155
-func manualDeploymentConfig() *deployapi.DeploymentConfig {
156
-	return &deployapi.DeploymentConfig{
157
-		ObjectMeta: kapi.ObjectMeta{Name: "manual-deploy-config"},
158
-		Triggers: []deployapi.DeploymentTriggerPolicy{
159
-			{
160
-				Type: deployapi.DeploymentTriggerManual,
161
-			},
162
-		},
163
-		Template: deployapi.DeploymentTemplate{
164
-			Strategy: deployapi.DeploymentStrategy{
165
-				Type: deployapi.DeploymentStrategyTypeRecreate,
166
-			},
167
-			ControllerTemplate: kapi.ReplicationControllerSpec{
168
-				Replicas: 1,
169
-				Selector: map[string]string{
170
-					"name": "test-pod",
171
-				},
172
-				Template: &kapi.PodTemplateSpec{
173
-					ObjectMeta: kapi.ObjectMeta{
174
-						Labels: map[string]string{
175
-							"name": "test-pod",
176
-						},
177
-					},
178
-					Spec: kapi.PodSpec{
179
-						Containers: []kapi.Container{
180
-							{
181
-								Name:  "container-1",
182
-								Image: "registry:8080/openshift/test-image:ref-1",
183
-							},
184
-						},
185
-					},
186
-				},
187
-			},
188
-		},
189
-	}
190
-}
191
-
192
-func matchingDeployment(config *deployapi.DeploymentConfig) *kapi.ReplicationController {
193
-	encodedConfig, _ := deployutil.EncodeDeploymentConfig(config, api.Codec)
194
-	return &kapi.ReplicationController{
195
-		ObjectMeta: kapi.ObjectMeta{
196
-			Name: deployutil.LatestDeploymentIDForConfig(config),
197
-			Annotations: map[string]string{
198
-				deployapi.DeploymentConfigAnnotation:        config.Name,
199
-				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
200
-			},
201
-			Labels: config.Labels,
202
-		},
203
-		Spec: kapi.ReplicationControllerSpec{
204
-			Replicas: 1,
205
-			Selector: map[string]string{
206
-				"name": "test-pod",
207
-			},
208
-			Template: &kapi.PodTemplateSpec{
209
-				ObjectMeta: kapi.ObjectMeta{
210
-					Labels: map[string]string{
211
-						"name": "test-pod",
212
-					},
213
-				},
214
-				Spec: kapi.PodSpec{
215
-					Containers: []kapi.Container{
216
-						{
217
-							Name:  "container-1",
218
-							Image: "registry:8080/openshift/test-image:ref-1",
219
-						},
220
-					},
221
-				},
222
-			},
223
-		},
224
-	}
225
-}
... ...
@@ -4,7 +4,9 @@ import (
4 4
 	"testing"
5 5
 
6 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+
7 8
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
9
+	deployapitest "github.com/openshift/origin/pkg/deploy/api/test"
8 10
 	deploytest "github.com/openshift/origin/pkg/deploy/controller/test"
9 11
 	imageapi "github.com/openshift/origin/pkg/image/api"
10 12
 )
... ...
@@ -26,8 +28,8 @@ const (
26 26
 )
27 27
 
28 28
 func TestUnregisteredContainer(t *testing.T) {
29
-	config := unregisteredConfig()
30
-	config.Triggers[0].ImageChangeParams.Automatic = false
29
+	config := deployapitest.OkDeploymentConfig(1)
30
+	config.Triggers[0].ImageChangeParams.ContainerNames = []string{"container-3"}
31 31
 
32 32
 	controller := &ImageChangeController{
33 33
 		DeploymentConfigInterface: &testIcDeploymentConfigInterface{
... ...
@@ -51,7 +53,7 @@ func TestUnregisteredContainer(t *testing.T) {
51 51
 }
52 52
 
53 53
 func TestImageChangeForNonAutomaticTag(t *testing.T) {
54
-	config := imageChangeDeploymentConfig()
54
+	config := deployapitest.OkDeploymentConfig(1)
55 55
 	config.Triggers[0].ImageChangeParams.Automatic = false
56 56
 
57 57
 	controller := &ImageChangeController{
... ...
@@ -3,15 +3,13 @@ package generator
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"speter.net/go/exp/math/dec/inf"
7
-
8 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9 7
 	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
10
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
11 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
12 9
 
13 10
 	api "github.com/openshift/origin/pkg/api/latest"
14 11
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
15 13
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
16 14
 	imageapi "github.com/openshift/origin/pkg/image/api"
17 15
 )
... ...
@@ -42,17 +40,18 @@ func TestGenerateFromConfigWithoutTagChange(t *testing.T) {
42 42
 		Codec: api.Codec,
43 43
 		DeploymentConfigInterface: &testDeploymentConfigInterface{
44 44
 			GetDeploymentConfigFunc: func(id string) (*deployapi.DeploymentConfig, error) {
45
-				return basicDeploymentConfig(), nil
45
+				return deploytest.OkDeploymentConfig(1), nil
46 46
 			},
47 47
 		},
48 48
 		ImageRepositoryInterface: &testImageRepositoryInterface{
49 49
 			ListImageRepositoriesFunc: func(labels labels.Selector) (*imageapi.ImageRepositoryList, error) {
50
-				return basicImageRepo(), nil
50
+				return okImageRepoList(), nil
51 51
 			},
52 52
 		},
53 53
 		DeploymentInterface: &testDeploymentInterface{
54 54
 			GetDeploymentFunc: func(id string) (*kapi.ReplicationController, error) {
55
-				return basicDeployment(), nil
55
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
56
+				return deployment, nil
56 57
 			},
57 58
 		},
58 59
 	}
... ...
@@ -77,12 +76,12 @@ func TestGenerateFromConfigWithNoDeployment(t *testing.T) {
77 77
 		Codec: api.Codec,
78 78
 		DeploymentConfigInterface: &testDeploymentConfigInterface{
79 79
 			GetDeploymentConfigFunc: func(id string) (*deployapi.DeploymentConfig, error) {
80
-				return basicDeploymentConfig(), nil
80
+				return deploytest.OkDeploymentConfig(1), nil
81 81
 			},
82 82
 		},
83 83
 		ImageRepositoryInterface: &testImageRepositoryInterface{
84 84
 			ListImageRepositoriesFunc: func(labels labels.Selector) (*imageapi.ImageRepositoryList, error) {
85
-				return basicImageRepo(), nil
85
+				return okImageRepoList(), nil
86 86
 			},
87 87
 		},
88 88
 		DeploymentInterface: &testDeploymentInterface{
... ...
@@ -112,17 +111,20 @@ func TestGenerateFromConfigWithUpdatedImageRef(t *testing.T) {
112 112
 		Codec: api.Codec,
113 113
 		DeploymentConfigInterface: &testDeploymentConfigInterface{
114 114
 			GetDeploymentConfigFunc: func(id string) (*deployapi.DeploymentConfig, error) {
115
-				return basicDeploymentConfig(), nil
115
+				return deploytest.OkDeploymentConfig(1), nil
116 116
 			},
117 117
 		},
118 118
 		ImageRepositoryInterface: &testImageRepositoryInterface{
119 119
 			ListImageRepositoriesFunc: func(labels labels.Selector) (*imageapi.ImageRepositoryList, error) {
120
-				return updatedImageRepo(), nil
120
+				list := okImageRepoList()
121
+				list.Items[0].Tags["tag1"] = "ref2"
122
+				return list, nil
121 123
 			},
122 124
 		},
123 125
 		DeploymentInterface: &testDeploymentInterface{
124 126
 			GetDeploymentFunc: func(id string) (*kapi.ReplicationController, error) {
125
-				return basicDeployment(), nil
127
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
128
+				return deployment, nil
126 129
 			},
127 130
 		},
128 131
 	}
... ...
@@ -172,72 +174,7 @@ func (i *testImageRepositoryInterface) ListImageRepositories(ctx kapi.Context, l
172 172
 	return i.ListImageRepositoriesFunc(labels)
173 173
 }
174 174
 
175
-func basicPodTemplate() *kapi.PodTemplateSpec {
176
-	return &kapi.PodTemplateSpec{
177
-		Spec: kapi.PodSpec{
178
-			Containers: []kapi.Container{
179
-				{
180
-					Name:   "container1",
181
-					Image:  "registry:8080/repo1:ref1",
182
-					CPU:    resource.Quantity{Amount: inf.NewDec(0, 3), Format: "DecimalSI"},
183
-					Memory: resource.Quantity{Amount: inf.NewDec(0, 0), Format: "DecimalSI"},
184
-				},
185
-				{
186
-					Name:   "container2",
187
-					Image:  "registry:8080/repo1:ref2",
188
-					CPU:    resource.Quantity{Amount: inf.NewDec(0, 3), Format: "DecimalSI"},
189
-					Memory: resource.Quantity{Amount: inf.NewDec(0, 0), Format: "DecimalSI"},
190
-				},
191
-			},
192
-		},
193
-	}
194
-}
195
-
196
-func basicDeploymentConfig() *deployapi.DeploymentConfig {
197
-	return &deployapi.DeploymentConfig{
198
-		ObjectMeta:    kapi.ObjectMeta{Name: "deploy1"},
199
-		LatestVersion: 1,
200
-		Triggers: []deployapi.DeploymentTriggerPolicy{
201
-			{
202
-				Type: deployapi.DeploymentTriggerOnImageChange,
203
-				ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
204
-					ContainerNames: []string{
205
-						"container1",
206
-					},
207
-					RepositoryName: "registry:8080/repo1",
208
-					Tag:            "tag1",
209
-				},
210
-			},
211
-		},
212
-		Template: deployapi.DeploymentTemplate{
213
-			ControllerTemplate: kapi.ReplicationControllerSpec{
214
-				Template: basicPodTemplate(),
215
-			},
216
-		},
217
-	}
218
-}
219
-
220
-func basicDeployment() *kapi.ReplicationController {
221
-	config := basicDeploymentConfig()
222
-	encodedConfig, _ := deployutil.EncodeDeploymentConfig(config, api.Codec)
223
-
224
-	return &kapi.ReplicationController{
225
-		ObjectMeta: kapi.ObjectMeta{
226
-			Name: deployutil.LatestDeploymentIDForConfig(config),
227
-			Annotations: map[string]string{
228
-				deployapi.DeploymentConfigAnnotation:        config.Name,
229
-				deployapi.DeploymentStatusAnnotation:        string(deployapi.DeploymentStatusNew),
230
-				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
231
-			},
232
-			Labels: config.Labels,
233
-		},
234
-		Spec: kapi.ReplicationControllerSpec{
235
-			Template: basicPodTemplate(),
236
-		},
237
-	}
238
-}
239
-
240
-func basicImageRepo() *imageapi.ImageRepositoryList {
175
+func okImageRepoList() *imageapi.ImageRepositoryList {
241 176
 	return &imageapi.ImageRepositoryList{
242 177
 		Items: []imageapi.ImageRepository{
243 178
 			{
... ...
@@ -250,17 +187,3 @@ func basicImageRepo() *imageapi.ImageRepositoryList {
250 250
 		},
251 251
 	}
252 252
 }
253
-
254
-func updatedImageRepo() *imageapi.ImageRepositoryList {
255
-	return &imageapi.ImageRepositoryList{
256
-		Items: []imageapi.ImageRepository{
257
-			{
258
-				ObjectMeta:            kapi.ObjectMeta{Name: "imageRepo1"},
259
-				DockerImageRepository: "registry:8080/repo1",
260
-				Tags: map[string]string{
261
-					"tag1": "ref2",
262
-				},
263
-			},
264
-		},
265
-	}
266
-}
267 253
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// Package rollback contains the code for generating DeploymentConfigs representing
1
+// rollbacks as well as REST support for API clients.
2
+package rollback
0 3
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+package rollback
1
+
2
+import (
3
+	"fmt"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
9
+
10
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
11
+	"github.com/openshift/origin/pkg/deploy/api/validation"
12
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
13
+)
14
+
15
+// REST provides a rollback generation endpoint. Only the Create method is implemented.
16
+type REST struct {
17
+	generator              GeneratorClient
18
+	deploymentGetter       DeploymentGetter
19
+	deploymentConfigGetter DeploymentConfigGetter
20
+	codec                  runtime.Codec
21
+}
22
+
23
+// GeneratorClient defines a local interface to a rollback generator for testability.
24
+type GeneratorClient interface {
25
+	Generate(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
26
+}
27
+
28
+// DeploymentGetter is a local interface to ReplicationControllers for testability.
29
+type DeploymentGetter interface {
30
+	GetDeployment(namespace, name string) (*kapi.ReplicationController, error)
31
+}
32
+
33
+// DeploymentConfigGetter is a local interface to DeploymentConfigs for testability.
34
+type DeploymentConfigGetter interface {
35
+	GetDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error)
36
+}
37
+
38
+// NewREST safely creates a new REST.
39
+func NewREST(generator GeneratorClient, deploymentGetter DeploymentGetter, configGetter DeploymentConfigGetter, codec runtime.Codec) apiserver.RESTStorage {
40
+	return &REST{
41
+		generator:              generator,
42
+		deploymentGetter:       deploymentGetter,
43
+		deploymentConfigGetter: configGetter,
44
+		codec: codec,
45
+	}
46
+}
47
+
48
+func (s *REST) New() runtime.Object {
49
+	return &deployapi.DeploymentConfigRollback{}
50
+}
51
+
52
+// Create generates a new DeploymentConfig representing a rollback.
53
+func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
54
+	rollback, ok := obj.(*deployapi.DeploymentConfigRollback)
55
+	if !ok {
56
+		return nil, fmt.Errorf("not a rollback spec: %#v", obj)
57
+	}
58
+
59
+	if errs := validation.ValidateDeploymentConfigRollback(rollback); len(errs) > 0 {
60
+		return nil, kerrors.NewInvalid("deploymentConfigRollback", "", errs)
61
+	}
62
+
63
+	namespace, namespaceOk := kapi.NamespaceFrom(ctx)
64
+	if !namespaceOk {
65
+		return nil, fmt.Errorf("namespace %s is invalid", ctx.Value)
66
+	}
67
+
68
+	// Roll back "from" the current deployment "to" a target deployment
69
+	var from, to *deployapi.DeploymentConfig
70
+	var err error
71
+
72
+	// Find the target ("to") deployment and decode the DeploymentConfig
73
+	if targetDeployment, err := s.deploymentGetter.GetDeployment(namespace, rollback.Spec.From.Name); err != nil {
74
+		// TODO: correct error type?
75
+		return nil, kerrors.NewBadRequest(fmt.Sprintf("Couldn't get specified deployment: %v", err))
76
+	} else {
77
+		if to, err = deployutil.DecodeDeploymentConfig(targetDeployment, s.codec); err != nil {
78
+			// TODO: correct error type?
79
+			return nil, kerrors.NewBadRequest(fmt.Sprintf("deploymentConfig on target deployment is invalid: %v", err))
80
+		}
81
+	}
82
+
83
+	// Find the current ("from") version of the target deploymentConfig
84
+	if from, err = s.deploymentConfigGetter.GetDeploymentConfig(namespace, to.Name); err != nil {
85
+		// TODO: correct error type?
86
+		return nil, kerrors.NewBadRequest(fmt.Sprintf("Couldn't find current deploymentConfig %s/%s: %v", namespace, to.Name, err))
87
+	}
88
+
89
+	return apiserver.MakeAsync(func() (runtime.Object, error) {
90
+		return s.generator.Generate(from, to, &rollback.Spec)
91
+	}), nil
92
+}
0 93
new file mode 100644
... ...
@@ -0,0 +1,296 @@
0
+package rollback
1
+
2
+import (
3
+	"errors"
4
+	"testing"
5
+	"time"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
9
+
10
+	api "github.com/openshift/origin/pkg/api/latest"
11
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
13
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
14
+)
15
+
16
+func TestCreateError(t *testing.T) {
17
+	rest := REST{}
18
+
19
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfig{})
20
+
21
+	if err == nil {
22
+		t.Errorf("Expected an error")
23
+	}
24
+
25
+	if obj != nil {
26
+		t.Errorf("Unexpected non-nil object: %#v", obj)
27
+	}
28
+}
29
+
30
+func TestCreateInvalid(t *testing.T) {
31
+	rest := REST{}
32
+
33
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{})
34
+
35
+	if err == nil {
36
+		t.Errorf("Expected an error")
37
+	}
38
+
39
+	if obj != nil {
40
+		t.Errorf("Unexpected non-nil object: %#v", obj)
41
+	}
42
+}
43
+
44
+func TestCreateOk(t *testing.T) {
45
+	rest := REST{
46
+		generator: &testGenerator{
47
+			generateFunc: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
48
+				return &deployapi.DeploymentConfig{}, nil
49
+			},
50
+		},
51
+		codec: api.Codec,
52
+		deploymentGetter: &testDeploymentGetter{
53
+			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
54
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
55
+				return deployment, nil
56
+			},
57
+		},
58
+		deploymentConfigGetter: &testDeploymentConfigGetter{
59
+			GetDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
60
+				return deploytest.OkDeploymentConfig(1), nil
61
+			},
62
+		},
63
+	}
64
+
65
+	channel, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
66
+		Spec: deployapi.DeploymentConfigRollbackSpec{
67
+			From: kapi.ObjectReference{
68
+				Name:      "deployment",
69
+				Namespace: kapi.NamespaceDefault,
70
+			},
71
+		},
72
+	})
73
+
74
+	if err != nil {
75
+		t.Errorf("Unexpected error: %v", err)
76
+	}
77
+
78
+	if channel == nil {
79
+		t.Errorf("Expected a result channel")
80
+	}
81
+
82
+	select {
83
+	case result := <-channel:
84
+		if _, ok := result.Object.(*deployapi.DeploymentConfig); !ok {
85
+			t.Errorf("expected a DeploymentConfig, got a %#v", result.Object)
86
+		}
87
+	case <-time.After(50 * time.Millisecond):
88
+		t.Errorf("Timed out waiting for result")
89
+	}
90
+}
91
+
92
+func TestCreateGeneratorError(t *testing.T) {
93
+	rest := REST{
94
+		generator: &testGenerator{
95
+			generateFunc: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
96
+				return nil, errors.New("something terrible happened")
97
+			},
98
+		},
99
+		codec: api.Codec,
100
+		deploymentGetter: &testDeploymentGetter{
101
+			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
102
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
103
+				return deployment, nil
104
+			},
105
+		},
106
+		deploymentConfigGetter: &testDeploymentConfigGetter{
107
+			GetDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
108
+				return deploytest.OkDeploymentConfig(1), nil
109
+			},
110
+		},
111
+	}
112
+
113
+	channel, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
114
+		Spec: deployapi.DeploymentConfigRollbackSpec{
115
+			From: kapi.ObjectReference{
116
+				Name:      "deployment",
117
+				Namespace: kapi.NamespaceDefault,
118
+			},
119
+		},
120
+	})
121
+
122
+	if err != nil {
123
+		t.Errorf("Unexpected error: %v", err)
124
+	}
125
+
126
+	if channel == nil {
127
+		t.Errorf("Expected a result channel")
128
+	}
129
+
130
+	select {
131
+	case result := <-channel:
132
+		status, ok := result.Object.(*kapi.Status)
133
+		if !ok {
134
+			t.Errorf("Expected status, got %#v", result)
135
+		}
136
+		if status.Status != kapi.StatusFailure {
137
+			t.Errorf("Expected status=failure, message=foo, got %#v", status)
138
+		}
139
+	case <-time.After(50 * time.Millisecond):
140
+		t.Errorf("Timed out waiting for result")
141
+	}
142
+}
143
+
144
+func TestCreateMissingDeployment(t *testing.T) {
145
+	rest := REST{
146
+		generator: &testGenerator{
147
+			generateFunc: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
148
+				t.Fatal("unexpected call to generator")
149
+				return nil, errors.New("something terrible happened")
150
+			},
151
+		},
152
+		codec: api.Codec,
153
+		deploymentGetter: &testDeploymentGetter{
154
+			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
155
+				return nil, kerrors.NewNotFound("replicationController", name)
156
+			},
157
+		},
158
+		deploymentConfigGetter: &testDeploymentConfigGetter{
159
+			GetDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
160
+				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
161
+				return nil, kerrors.NewNotFound("deploymentConfig", name)
162
+			},
163
+		},
164
+	}
165
+
166
+	channel, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
167
+		Spec: deployapi.DeploymentConfigRollbackSpec{
168
+			From: kapi.ObjectReference{
169
+				Name:      "deployment",
170
+				Namespace: kapi.NamespaceDefault,
171
+			},
172
+		},
173
+	})
174
+
175
+	if err == nil {
176
+		t.Errorf("Expected an error")
177
+	}
178
+
179
+	if channel != nil {
180
+		t.Error("Unexpected result channel")
181
+	}
182
+}
183
+
184
+func TestCreateInvalidDeployment(t *testing.T) {
185
+	rest := REST{
186
+		generator: &testGenerator{
187
+			generateFunc: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
188
+				t.Fatal("unexpected call to generator")
189
+				return nil, errors.New("something terrible happened")
190
+			},
191
+		},
192
+		codec: api.Codec,
193
+		deploymentGetter: &testDeploymentGetter{
194
+			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
195
+				// invalidate the encoded config
196
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
197
+				deployment.Annotations[deployapi.DeploymentEncodedConfigAnnotation] = ""
198
+				return deployment, nil
199
+			},
200
+		},
201
+		deploymentConfigGetter: &testDeploymentConfigGetter{
202
+			GetDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
203
+				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
204
+				return nil, kerrors.NewNotFound("deploymentConfig", name)
205
+			},
206
+		},
207
+	}
208
+
209
+	channel, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
210
+		Spec: deployapi.DeploymentConfigRollbackSpec{
211
+			From: kapi.ObjectReference{
212
+				Name:      "deployment",
213
+				Namespace: kapi.NamespaceDefault,
214
+			},
215
+		},
216
+	})
217
+
218
+	if err == nil {
219
+		t.Errorf("Expected an error")
220
+	}
221
+
222
+	if channel != nil {
223
+		t.Error("Unexpected result channel")
224
+	}
225
+}
226
+
227
+func TestCreateMissingDeploymentConfig(t *testing.T) {
228
+	rest := REST{
229
+		generator: &testGenerator{
230
+			generateFunc: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
231
+				t.Fatal("unexpected call to generator")
232
+				return nil, errors.New("something terrible happened")
233
+			},
234
+		},
235
+		codec: api.Codec,
236
+		deploymentGetter: &testDeploymentGetter{
237
+			GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
238
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
239
+				return deployment, nil
240
+			},
241
+		},
242
+		deploymentConfigGetter: &testDeploymentConfigGetter{
243
+			GetDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
244
+				return nil, kerrors.NewNotFound("deploymentConfig", name)
245
+			},
246
+		},
247
+	}
248
+
249
+	channel, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
250
+		Spec: deployapi.DeploymentConfigRollbackSpec{
251
+			From: kapi.ObjectReference{
252
+				Name:      "deployment",
253
+				Namespace: kapi.NamespaceDefault,
254
+			},
255
+		},
256
+	})
257
+
258
+	if err == nil {
259
+		t.Errorf("Expected an error")
260
+	}
261
+
262
+	if channel != nil {
263
+		t.Error("Unexpected result channel")
264
+	}
265
+}
266
+
267
+func TestNew(t *testing.T) {
268
+	// :)
269
+	rest := NewREST(&testGenerator{}, &testDeploymentGetter{}, &testDeploymentConfigGetter{}, api.Codec)
270
+	rest.New()
271
+}
272
+
273
+type testGenerator struct {
274
+	generateFunc func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
275
+}
276
+
277
+func (g *testGenerator) Generate(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
278
+	return g.generateFunc(from, to, spec)
279
+}
280
+
281
+type testDeploymentGetter struct {
282
+	GetDeploymentFunc func(namespace, name string) (*kapi.ReplicationController, error)
283
+}
284
+
285
+func (i *testDeploymentGetter) GetDeployment(namespace, name string) (*kapi.ReplicationController, error) {
286
+	return i.GetDeploymentFunc(namespace, name)
287
+}
288
+
289
+type testDeploymentConfigGetter struct {
290
+	GetDeploymentConfigFunc func(namespace, name string) (*deployapi.DeploymentConfig, error)
291
+}
292
+
293
+func (i *testDeploymentConfigGetter) GetDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error) {
294
+	return i.GetDeploymentConfigFunc(namespace, name)
295
+}
0 296
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package rollback
1
+
2
+import (
3
+	"fmt"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+
7
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
8
+)
9
+
10
+// RollbackGenerator generates a new DeploymentConfig by merging a pair of DeploymentConfigs
11
+// in a configurable way.
12
+type RollbackGenerator struct {
13
+}
14
+
15
+// Generate creates a new DeploymentConfig by merging to onto from based on the options provided
16
+// by spec. The LatestVersion of the result is unconditionally incremented, as rollback candidates are
17
+// should be possible to be deployed manually regardless of other system behavior such as triggering.
18
+func (g *RollbackGenerator) Generate(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
19
+	rollback := &deployapi.DeploymentConfig{}
20
+
21
+	if err := kapi.Scheme.Convert(&from, &rollback); err != nil {
22
+		return nil, fmt.Errorf("Couldn't clone 'from' deploymentConfig: %v", err)
23
+	}
24
+
25
+	// construct the candidate deploymentConfig based on the rollback spec
26
+	if spec.IncludeTemplate {
27
+		if err := kapi.Scheme.Convert(&to.Template.ControllerTemplate.Template, &rollback.Template.ControllerTemplate.Template); err != nil {
28
+			return nil, fmt.Errorf("Couldn't copy template to rollback:: %v", err)
29
+		}
30
+	}
31
+
32
+	if spec.IncludeReplicationMeta {
33
+		rollback.Template.ControllerTemplate.Replicas = to.Template.ControllerTemplate.Replicas
34
+		rollback.Template.ControllerTemplate.Selector = map[string]string{}
35
+		for k, v := range to.Template.ControllerTemplate.Selector {
36
+			rollback.Template.ControllerTemplate.Selector[k] = v
37
+		}
38
+	}
39
+
40
+	if spec.IncludeTriggers {
41
+		if err := kapi.Scheme.Convert(&to.Triggers, &rollback.Triggers); err != nil {
42
+			return nil, fmt.Errorf("Couldn't copy triggers to rollback:: %v", err)
43
+		}
44
+	}
45
+
46
+	if spec.IncludeStrategy {
47
+		if err := kapi.Scheme.Convert(&to.Template.Strategy, &rollback.Template.Strategy); err != nil {
48
+			return nil, fmt.Errorf("Couldn't copy strategy to rollback:: %v", err)
49
+		}
50
+	}
51
+
52
+	// TODO: add a new cause?
53
+	rollback.LatestVersion++
54
+
55
+	return rollback, nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,114 @@
0
+package rollback
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+
7
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
8
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
9
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
10
+)
11
+
12
+func TestGeneration(t *testing.T) {
13
+	from := deploytest.OkDeploymentConfig(2)
14
+	from.Template.Strategy = deployapi.DeploymentStrategy{
15
+		Type: deployapi.DeploymentStrategyTypeCustom,
16
+	}
17
+	from.Triggers = append(from.Triggers, deployapi.DeploymentTriggerPolicy{Type: deployapi.DeploymentTriggerOnConfigChange})
18
+	from.Template.ControllerTemplate.Template.Spec.Containers[0].Name = "changed"
19
+	from.Template.ControllerTemplate.Replicas = 5
20
+	from.Template.ControllerTemplate.Selector = map[string]string{
21
+		"new1": "new2",
22
+		"new2": "new2",
23
+	}
24
+
25
+	to := deploytest.OkDeploymentConfig(1)
26
+
27
+	// Generate a rollback for every combination of flag (using 1 bit per flag).
28
+	rollbackSpecs := []*deployapi.DeploymentConfigRollbackSpec{}
29
+	for i := 0; i < 15; i++ {
30
+		spec := &deployapi.DeploymentConfigRollbackSpec{
31
+			From: kapi.ObjectReference{
32
+				Name:      "deployment",
33
+				Namespace: kapi.NamespaceDefault,
34
+			},
35
+			IncludeTriggers:        i&(1<<0) > 0,
36
+			IncludeTemplate:        i&(1<<1) > 0,
37
+			IncludeReplicationMeta: i&(1<<2) > 0,
38
+			IncludeStrategy:        i&(1<<3) > 0,
39
+		}
40
+		rollbackSpecs = append(rollbackSpecs, spec)
41
+	}
42
+
43
+	generator := &RollbackGenerator{}
44
+
45
+	// Test every combination.
46
+	for _, spec := range rollbackSpecs {
47
+		t.Logf("testing spec %#v", spec)
48
+
49
+		if rollback, err := generator.Generate(from, to, spec); err != nil {
50
+			t.Fatalf("Unexpected error: %v", err)
51
+		} else {
52
+			if hasStrategyDiff(from, rollback) && !spec.IncludeStrategy {
53
+				t.Fatalf("unexpected strategy diff: from=%v, rollback=%v", from, rollback)
54
+			}
55
+
56
+			if hasTriggerDiff(from, rollback) && !spec.IncludeTriggers {
57
+				t.Fatalf("unexpected trigger diff: from=%v, rollback=%v", from, rollback)
58
+			}
59
+
60
+			if hasPodTemplateDiff(from, rollback) && !spec.IncludeTemplate {
61
+				t.Fatalf("unexpected template diff: from=%v, rollback=%v", from, rollback)
62
+			}
63
+
64
+			if hasReplicationMetaDiff(from, rollback) && !spec.IncludeReplicationMeta {
65
+				t.Fatalf("unexpected replication meta diff: from=%v, rollback=%v", from, rollback)
66
+			}
67
+		}
68
+	}
69
+}
70
+
71
+func hasStrategyDiff(a, b *deployapi.DeploymentConfig) bool {
72
+	return a.Template.Strategy.Type != b.Template.Strategy.Type
73
+}
74
+
75
+func hasTriggerDiff(a, b *deployapi.DeploymentConfig) bool {
76
+	if len(a.Triggers) != len(b.Triggers) {
77
+		return true
78
+	}
79
+
80
+	for _, triggerA := range a.Triggers {
81
+		bHasTrigger := false
82
+		for _, triggerB := range b.Triggers {
83
+			if triggerB.Type == triggerA.Type {
84
+				bHasTrigger = true
85
+				break
86
+			}
87
+		}
88
+
89
+		if !bHasTrigger {
90
+			return true
91
+		}
92
+	}
93
+
94
+	return false
95
+}
96
+
97
+func hasReplicationMetaDiff(a, b *deployapi.DeploymentConfig) bool {
98
+	if a.Template.ControllerTemplate.Replicas != b.Template.ControllerTemplate.Replicas {
99
+		return true
100
+	}
101
+
102
+	for keyA, valueA := range a.Template.ControllerTemplate.Selector {
103
+		if valueB, exists := b.Template.ControllerTemplate.Selector[keyA]; !exists || valueA != valueB {
104
+			return true
105
+		}
106
+	}
107
+
108
+	return false
109
+}
110
+
111
+func hasPodTemplateDiff(a, b *deployapi.DeploymentConfig) bool {
112
+	return !deployutil.PodSpecsEqual(a.Template.ControllerTemplate.Template.Spec, b.Template.ControllerTemplate.Template.Spec)
113
+}
... ...
@@ -7,105 +7,82 @@ import (
7 7
 
8 8
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9 9
 	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
10
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
11 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
12 11
 
13 12
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
14 13
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
15 14
 )
16 15
 
17
-// DeploymentStrategy is a simple strategy appropriate as a default. Its behavior
18
-// is to create new replication controllers as defined on a Deployment, and delete any previously
19
-// existing replication controllers for the same DeploymentConfig associated with the deployment.
16
+// DeploymentStrategy is a simple strategy appropriate as a default. Its behavior is to increase the
17
+// replica count of the new deployment to 1, and to decrease the replica count of previous deployments
18
+// to zero.
20 19
 //
21
-// A failure to remove any existing ReplicationController will be considered a deployment failure.
20
+// A failure to disable any existing deployments will be considered a deployment failure.
22 21
 type DeploymentStrategy struct {
22
+	// ReplicationController is used to interact with ReplicatonControllers.
23 23
 	ReplicationController ReplicationControllerInterface
24
-	Codec                 runtime.Codec
24
+	// Codec is used to decode DeploymentConfigs contained in deployments.
25
+	Codec runtime.Codec
25 26
 }
26 27
 
27
-type ReplicationControllerInterface interface {
28
-	listReplicationControllers(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error)
29
-	getReplicationController(namespace, id string) (*kapi.ReplicationController, error)
30
-	updateReplicationController(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error)
31
-	deleteReplicationController(namespace string, id string) error
32
-}
33
-
34
-type RealReplicationController struct {
35
-	KubeClient kclient.Interface
36
-}
37
-
38
-func (r RealReplicationController) listReplicationControllers(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
39
-	return r.KubeClient.ReplicationControllers(namespace).List(selector)
40
-}
41
-
42
-func (r RealReplicationController) getReplicationController(namespace string, id string) (*kapi.ReplicationController, error) {
43
-	return r.KubeClient.ReplicationControllers(namespace).Get(id)
44
-}
45
-
46
-func (r RealReplicationController) updateReplicationController(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error) {
47
-	return r.KubeClient.ReplicationControllers(namespace).Update(ctrl)
48
-}
49
-
50
-func (r RealReplicationController) deleteReplicationController(namespace string, id string) error {
51
-	return r.KubeClient.ReplicationControllers(namespace).Delete(id)
52
-}
53
-
54
-func (s *DeploymentStrategy) Deploy(deployment *kapi.ReplicationController) error {
55
-	controllers := &kapi.ReplicationControllerList{}
56
-	namespace := deployment.Namespace
28
+// Deploy makes deployment active and disables oldDeployments.
29
+func (s *DeploymentStrategy) Deploy(deployment *kapi.ReplicationController, oldDeployments []kapi.ObjectReference) error {
57 30
 	var err error
58
-
59
-	configID, hasConfigID := deployment.Annotations[deployapi.DeploymentConfigAnnotation]
60
-	if !hasConfigID {
61
-		return fmt.Errorf("This strategy is only compatible with deployments associated with a DeploymentConfig")
62
-	}
63
-
64
-	selector, _ := labels.ParseSelector(deployapi.DeploymentConfigLabel + "=" + configID)
65
-	controllers, err = s.ReplicationController.listReplicationControllers(namespace, selector)
66
-	if err != nil {
67
-		return fmt.Errorf("Unable to get list of replication controllers for previous deploymentConfig %s: %v\n", configID, err)
68
-	}
69
-
70 31
 	var deploymentConfig *deployapi.DeploymentConfig
71
-	var decodeError error
72
-	if deploymentConfig, decodeError = deployutil.DecodeDeploymentConfig(deployment, s.Codec); decodeError != nil {
73
-		return fmt.Errorf("Couldn't decode DeploymentConfig from deployment %s: %v", deployment.Name, decodeError)
32
+
33
+	if deploymentConfig, err = deployutil.DecodeDeploymentConfig(deployment, s.Codec); err != nil {
34
+		return fmt.Errorf("Couldn't decode DeploymentConfig from deployment %s: %v", deployment.Name, err)
74 35
 	}
75 36
 
76 37
 	deployment.Spec.Replicas = deploymentConfig.Template.ControllerTemplate.Replicas
77
-	glog.Infof("Updating replicationController for deployment %s to replica count %d", deployment.Name, deployment.Spec.Replicas)
78
-	if _, err := s.ReplicationController.updateReplicationController(namespace, deployment); err != nil {
79
-		return fmt.Errorf("An error occurred updating the replication controller for deployment %s: %v", deployment.Name, err)
38
+	glog.Infof("Updating deployment %s replica count to %d", deployment.Name, deployment.Spec.Replicas)
39
+	if _, err := s.ReplicationController.updateReplicationController(deployment.Namespace, deployment); err != nil {
40
+		return fmt.Errorf("Error updating deployment %s replica count to %d: %v", deployment.Name, deployment.Spec.Replicas, err)
80 41
 	}
81 42
 
82
-	// For this simple deploy, remove previous replication controllers.
43
+	// For this simple deploy, disable previous replication controllers.
83 44
 	// TODO: This isn't transactional, and we don't actually wait for the replica count to
84 45
 	// become zero before deleting them.
46
+	glog.Infof("Found %d prior deployments to disable", len(oldDeployments))
85 47
 	allProcessed := true
86
-	for _, oldController := range controllers.Items {
87
-		glog.Infof("Stopping replication controller for previous deploymentConfig %s: %v", configID, oldController.Name)
88
-
89
-		oldController.Spec.Replicas = 0
90
-		glog.Infof("Settings Replicas=0 for replicationController %s for previous deploymentConfig %s", oldController.Name, configID)
91
-		if _, err := s.ReplicationController.updateReplicationController(namespace, &oldController); err != nil {
92
-			glog.Errorf("Unable to stop replication controller %s for previous deploymentConfig %s: %#v\n", oldController.Name, configID, err)
48
+	for _, oldDeployment := range oldDeployments {
49
+		oldController, oldErr := s.ReplicationController.getReplicationController(oldDeployment.Namespace, oldDeployment.Name)
50
+		if oldErr != nil {
51
+			glog.Errorf("Error getting old deployment %s for disabling: %v", oldDeployment.Name, oldErr)
93 52
 			allProcessed = false
94 53
 			continue
95 54
 		}
96 55
 
97
-		glog.Infof("Deleting replication controller %s for previous deploymentConfig %s", oldController.Name, configID)
98
-		err := s.ReplicationController.deleteReplicationController(namespace, oldController.Name)
99
-		if err != nil {
100
-			glog.Errorf("Unable to remove replication controller %s for previous deploymentConfig %s:%#v\n", oldController.Name, configID, err)
56
+		glog.Infof("Setting replicas to zero for old deployment %s", oldController.Name)
57
+		oldController.Spec.Replicas = 0
58
+		if _, err := s.ReplicationController.updateReplicationController(oldController.Namespace, oldController); err != nil {
59
+			glog.Errorf("Error updating replicas to zero for old deployment %s: %#v", oldController.Name, err)
101 60
 			allProcessed = false
102 61
 			continue
103 62
 		}
104 63
 	}
105 64
 
106 65
 	if !allProcessed {
107
-		return fmt.Errorf("Failed to clean up all replication controllers")
66
+		return fmt.Errorf("Failed to disable all prior deployments for new deployment %s", deployment.Name)
108 67
 	}
109 68
 
69
+	glog.Infof("Deployment %s successfully made active", deployment.Name)
110 70
 	return nil
111 71
 }
72
+
73
+type ReplicationControllerInterface interface {
74
+	getReplicationController(namespace, name string) (*kapi.ReplicationController, error)
75
+	updateReplicationController(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error)
76
+}
77
+
78
+type RealReplicationController struct {
79
+	KubeClient kclient.Interface
80
+}
81
+
82
+func (r RealReplicationController) getReplicationController(namespace string, name string) (*kapi.ReplicationController, error) {
83
+	return r.KubeClient.ReplicationControllers(namespace).Get(name)
84
+}
85
+
86
+func (r RealReplicationController) updateReplicationController(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error) {
87
+	return r.KubeClient.ReplicationControllers(namespace).Update(ctrl)
88
+}
... ...
@@ -4,37 +4,31 @@ import (
4 4
 	"testing"
5 5
 
6 6
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
8 7
 
9 8
 	api "github.com/openshift/origin/pkg/api/latest"
10
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
9
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
11 10
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
12 11
 )
13 12
 
14 13
 func TestFirstDeployment(t *testing.T) {
15
-	var (
16
-		updatedController *kapi.ReplicationController
17
-		deployment        = okDeployment(okDeploymentConfig())
18
-	)
14
+	var updatedController *kapi.ReplicationController
15
+	deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
19 16
 
20 17
 	strategy := &DeploymentStrategy{
21 18
 		Codec: api.Codec,
22 19
 		ReplicationController: &testControllerClient{
23
-			listReplicationControllersFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
24
-				return &kapi.ReplicationControllerList{}, nil
20
+			getReplicationControllerFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
21
+				t.Fatalf("unexpected call to getReplicationController")
22
+				return nil, nil
25 23
 			},
26 24
 			updateReplicationControllerFunc: func(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error) {
27 25
 				updatedController = ctrl
28 26
 				return ctrl, nil
29 27
 			},
30
-			deleteReplicationControllerFunc: func(namespaceBravo, id string) error {
31
-				t.Fatalf("unexpected call to DeleteReplicationController")
32
-				return nil
33
-			},
34 28
 		},
35 29
 	}
36 30
 
37
-	err := strategy.Deploy(deployment)
31
+	err := strategy.Deploy(deployment, []kapi.ObjectReference{})
38 32
 
39 33
 	if err != nil {
40 34
 		t.Fatalf("unexpected deploy error: %#v", err)
... ...
@@ -44,29 +38,27 @@ func TestFirstDeployment(t *testing.T) {
44 44
 		t.Fatalf("expected a ReplicationController")
45 45
 	}
46 46
 
47
-	if e, a := 2, updatedController.Spec.Replicas; e != a {
47
+	if e, a := 1, updatedController.Spec.Replicas; e != a {
48 48
 		t.Fatalf("expected controller replicas to be %d, got %d", e, a)
49 49
 	}
50 50
 }
51 51
 
52 52
 func TestSecondDeployment(t *testing.T) {
53
-	var deletedControllerID string
54 53
 	updatedControllers := make(map[string]*kapi.ReplicationController)
55
-	oldDeployment := okDeployment(okDeploymentConfig())
54
+	oldDeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec)
56 55
 
57 56
 	strategy := &DeploymentStrategy{
58 57
 		Codec: api.Codec,
59 58
 		ReplicationController: &testControllerClient{
60
-			listReplicationControllersFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
61
-				return &kapi.ReplicationControllerList{
62
-					Items: []kapi.ReplicationController{
63
-						*oldDeployment,
64
-					},
65
-				}, nil
66
-			},
67
-			deleteReplicationControllerFunc: func(namespace, id string) error {
68
-				deletedControllerID = id
69
-				return nil
59
+			getReplicationControllerFunc: func(namespace, name string) (*kapi.ReplicationController, error) {
60
+				if e, a := oldDeployment.Namespace, namespace; e != a {
61
+					t.Fatalf("expected getReplicationController call with %s, got %s", e, a)
62
+				}
63
+
64
+				if e, a := oldDeployment.Name, name; e != a {
65
+					t.Fatalf("expected getReplicationController call with %s, got %s", e, a)
66
+				}
67
+				return oldDeployment, nil
70 68
 			},
71 69
 			updateReplicationControllerFunc: func(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error) {
72 70
 				updatedControllers[ctrl.Name] = ctrl
... ...
@@ -75,11 +67,15 @@ func TestSecondDeployment(t *testing.T) {
75 75
 		},
76 76
 	}
77 77
 
78
-	newConfig := okDeploymentConfig()
79
-	newConfig.LatestVersion = 2
80
-	newDeployment := okDeployment(newConfig)
78
+	newConfig := deploytest.OkDeploymentConfig(2)
79
+	newDeployment, _ := deployutil.MakeDeployment(newConfig, kapi.Codec)
81 80
 
82
-	err := strategy.Deploy(newDeployment)
81
+	err := strategy.Deploy(newDeployment, []kapi.ObjectReference{
82
+		{
83
+			Namespace: oldDeployment.Namespace,
84
+			Name:      oldDeployment.Name,
85
+		},
86
+	})
83 87
 
84 88
 	if err != nil {
85 89
 		t.Fatalf("unexpected deploy error: %#v", err)
... ...
@@ -89,78 +85,20 @@ func TestSecondDeployment(t *testing.T) {
89 89
 		t.Fatalf("expected old controller replicas to be %d, got %d", e, a)
90 90
 	}
91 91
 
92
-	if e, a := oldDeployment.Name, deletedControllerID; e != a {
93
-		t.Fatalf("expected deletion of controller %s, got %s", e, a)
94
-	}
95
-
96
-	if e, a := 2, updatedControllers[newDeployment.Name].Spec.Replicas; e != a {
92
+	if e, a := 1, updatedControllers[newDeployment.Name].Spec.Replicas; e != a {
97 93
 		t.Fatalf("expected new controller replicas to be %d, got %d", e, a)
98 94
 	}
99 95
 }
100 96
 
101 97
 type testControllerClient struct {
102
-	listReplicationControllersFunc  func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error)
103
-	getReplicationControllerFunc    func(namespace, id string) (*kapi.ReplicationController, error)
98
+	getReplicationControllerFunc    func(namespace, name string) (*kapi.ReplicationController, error)
104 99
 	updateReplicationControllerFunc func(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error)
105
-	deleteReplicationControllerFunc func(namespace, id string) error
106 100
 }
107 101
 
108
-func (t *testControllerClient) listReplicationControllers(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) {
109
-	return t.listReplicationControllersFunc(namespace, selector)
110
-}
111
-
112
-func (t *testControllerClient) getReplicationController(namespace, id string) (*kapi.ReplicationController, error) {
113
-	return t.getReplicationControllerFunc(namespace, id)
102
+func (t *testControllerClient) getReplicationController(namespace, name string) (*kapi.ReplicationController, error) {
103
+	return t.getReplicationControllerFunc(namespace, name)
114 104
 }
115 105
 
116 106
 func (t *testControllerClient) updateReplicationController(namespace string, ctrl *kapi.ReplicationController) (*kapi.ReplicationController, error) {
117 107
 	return t.updateReplicationControllerFunc(namespace, ctrl)
118 108
 }
119
-
120
-func (t *testControllerClient) deleteReplicationController(namespace, id string) error {
121
-	return t.deleteReplicationControllerFunc(namespace, id)
122
-}
123
-
124
-func okDeploymentConfig() *deployapi.DeploymentConfig {
125
-	return &deployapi.DeploymentConfig{
126
-		ObjectMeta:    kapi.ObjectMeta{Name: "deploymentConfig"},
127
-		LatestVersion: 1,
128
-		Template: deployapi.DeploymentTemplate{
129
-			Strategy: deployapi.DeploymentStrategy{
130
-				Type: deployapi.DeploymentStrategyTypeRecreate,
131
-			},
132
-			ControllerTemplate: kapi.ReplicationControllerSpec{
133
-				Replicas: 2,
134
-				Template: &kapi.PodTemplateSpec{
135
-					Spec: kapi.PodSpec{
136
-						Containers: []kapi.Container{
137
-							{
138
-								Name:  "container1",
139
-								Image: "registry:8080/repo1:ref1",
140
-							},
141
-						},
142
-					},
143
-				},
144
-			},
145
-		},
146
-	}
147
-}
148
-
149
-func okDeployment(config *deployapi.DeploymentConfig) *kapi.ReplicationController {
150
-	encodedConfig, _ := deployutil.EncodeDeploymentConfig(config, api.Codec)
151
-	controller := &kapi.ReplicationController{
152
-		ObjectMeta: kapi.ObjectMeta{
153
-			Name: deployutil.LatestDeploymentIDForConfig(config),
154
-			Annotations: map[string]string{
155
-				deployapi.DeploymentConfigAnnotation:        config.Name,
156
-				deployapi.DeploymentStatusAnnotation:        string(deployapi.DeploymentStatusNew),
157
-				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
158
-			},
159
-			Labels: config.Labels,
160
-		},
161
-		Spec: config.Template.ControllerTemplate,
162
-	}
163
-
164
-	controller.Spec.Replicas = 0
165
-	return controller
166
-}
... ...
@@ -10,12 +10,14 @@ import (
10 10
 	"github.com/golang/glog"
11 11
 
12 12
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
13
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
13 14
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
14 15
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
15 16
 
16 17
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
17 18
 )
18 19
 
20
+// LatestDeploymentIDForConfig returns a stable identifier for config based on its version.
19 21
 func LatestDeploymentIDForConfig(config *deployapi.DeploymentConfig) string {
20 22
 	return config.Name + "-" + strconv.Itoa(config.LatestVersion)
21 23
 }
... ...
@@ -72,7 +74,16 @@ func ParseContainerImage(image string) (string, string) {
72 72
 	return tokens[0], tokens[1]
73 73
 }
74 74
 
75
+// HashPodSpecs hashes a PodSpec into a uint64.
76
+// TODO: Resources are currently ignored due to the formats not surviving encoding/decoding
77
+// in a consistent manner (e.g. 0 is represented sometimes as 0.000)
75 78
 func HashPodSpec(t api.PodSpec) uint64 {
79
+
80
+	for i := range t.Containers {
81
+		t.Containers[i].CPU = resource.Quantity{}
82
+		t.Containers[i].Memory = resource.Quantity{}
83
+	}
84
+
76 85
 	jsonString, err := json.Marshal(t)
77 86
 	if err != nil {
78 87
 		glog.Errorf("An error occurred marshalling pod state: %v", err)
... ...
@@ -83,10 +94,13 @@ func HashPodSpec(t api.PodSpec) uint64 {
83 83
 	return uint64(hash.Sum32())
84 84
 }
85 85
 
86
+// PodSpecsEqual returns true if the given PodSpecs are the same.
86 87
 func PodSpecsEqual(a, b api.PodSpec) bool {
87 88
 	return HashPodSpec(a) == HashPodSpec(b)
88 89
 }
89 90
 
91
+// DecodeDeploymentConfig decodes a DeploymentConfig from controller using codec. An error is returned
92
+// if the controller doesn't contain an encoded config.
90 93
 func DecodeDeploymentConfig(controller *api.ReplicationController, codec runtime.Codec) (*deployapi.DeploymentConfig, error) {
91 94
 	encodedConfig := []byte(controller.Annotations[deployapi.DeploymentEncodedConfigAnnotation])
92 95
 	if decoded, err := codec.Decode(encodedConfig); err == nil {
... ...
@@ -100,6 +114,7 @@ func DecodeDeploymentConfig(controller *api.ReplicationController, codec runtime
100 100
 	}
101 101
 }
102 102
 
103
+// EncodeDeploymentConfig encodes config as a string using codec.
103 104
 func EncodeDeploymentConfig(config *deployapi.DeploymentConfig, codec runtime.Codec) (string, error) {
104 105
 	if bytes, err := codec.Encode(config); err == nil {
105 106
 		return string(bytes[:]), nil
... ...
@@ -107,3 +122,41 @@ func EncodeDeploymentConfig(config *deployapi.DeploymentConfig, codec runtime.Co
107 107
 		return "", err
108 108
 	}
109 109
 }
110
+
111
+// MakeDeployment creates a deployment represented as a ReplicationController and based on the given
112
+// DeploymentConfig. The controller replica count will be zero.
113
+func MakeDeployment(config *deployapi.DeploymentConfig, codec runtime.Codec) (*api.ReplicationController, error) {
114
+	var err error
115
+	var encodedConfig string
116
+
117
+	if encodedConfig, err = EncodeDeploymentConfig(config, codec); err != nil {
118
+		return nil, err
119
+	}
120
+
121
+	deployment := &api.ReplicationController{
122
+		ObjectMeta: api.ObjectMeta{
123
+			Name: LatestDeploymentIDForConfig(config),
124
+			Annotations: map[string]string{
125
+				deployapi.DeploymentConfigAnnotation:        config.Name,
126
+				deployapi.DeploymentStatusAnnotation:        string(deployapi.DeploymentStatusNew),
127
+				deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
128
+				deployapi.DeploymentVersionAnnotation:       strconv.Itoa(config.LatestVersion),
129
+			},
130
+			Labels: config.Labels,
131
+		},
132
+		Spec: config.Template.ControllerTemplate,
133
+	}
134
+
135
+	// The deployment should be inactive initially
136
+	deployment.Spec.Replicas = 0
137
+
138
+	// Ensure that pods created by this deployment controller can be safely associated back
139
+	// to the controller, and that multiple deployment controllers for the same config don't
140
+	// manipulate each others' pods.
141
+	deployment.Spec.Template.Labels[deployapi.DeploymentConfigLabel] = config.Name
142
+	deployment.Spec.Template.Labels[deployapi.DeploymentLabel] = deployment.Name
143
+	deployment.Spec.Selector[deployapi.DeploymentConfigLabel] = config.Name
144
+	deployment.Spec.Selector[deployapi.DeploymentLabel] = deployment.Name
145
+
146
+	return deployment, nil
147
+}
... ...
@@ -1,9 +1,11 @@
1 1
 package util
2 2
 
3 3
 import (
4
+	"strconv"
4 5
 	"testing"
5 6
 
6 7
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
7 9
 	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
8 10
 )
9 11
 
... ...
@@ -73,3 +75,57 @@ func TestPodSpecsEqualAdditionalContainerInManifest(t *testing.T) {
73 73
 		t.Fatalf("Unexpected true result for PodSpecsEqual")
74 74
 	}
75 75
 }
76
+
77
+func TestMakeDeploymentOk(t *testing.T) {
78
+	config := deploytest.OkDeploymentConfig(1)
79
+	deployment, err := MakeDeployment(config, kapi.Codec)
80
+
81
+	if err != nil {
82
+		t.Fatalf("unexpected error: %#v", err)
83
+	}
84
+
85
+	expectedAnnotations := map[string]string{
86
+		deployapi.DeploymentConfigAnnotation:  config.Name,
87
+		deployapi.DeploymentStatusAnnotation:  string(deployapi.DeploymentStatusNew),
88
+		deployapi.DeploymentVersionAnnotation: strconv.Itoa(config.LatestVersion),
89
+	}
90
+
91
+	for key, expected := range expectedAnnotations {
92
+		if actual := deployment.Annotations[key]; actual != expected {
93
+			t.Fatalf("expected deployment annotation %s=%s, got %s", key, expected, actual)
94
+		}
95
+	}
96
+
97
+	if len(deployment.Annotations[deployapi.DeploymentEncodedConfigAnnotation]) == 0 {
98
+		t.Fatalf("expected deployment with DeploymentEncodedConfigAnnotation annotation")
99
+	}
100
+
101
+	if decodedConfig, err := DecodeDeploymentConfig(deployment, kapi.Codec); err != nil {
102
+		t.Fatalf("invalid encoded config on deployment: %v", err)
103
+	} else {
104
+		if e, a := config.Name, decodedConfig.Name; e != a {
105
+			t.Fatalf("encoded config name doesn't match source config")
106
+		}
107
+		// TODO: more assertions
108
+	}
109
+
110
+	if deployment.Spec.Replicas != 0 {
111
+		t.Fatalf("expected deployment replicas to be 0")
112
+	}
113
+
114
+	if e, a := config.Name, deployment.Spec.Template.Labels[deployapi.DeploymentConfigLabel]; e != a {
115
+		t.Fatalf("expected label DeploymentConfigLabel=%s, got %s", e, a)
116
+	}
117
+
118
+	if e, a := deployment.Name, deployment.Spec.Template.Labels[deployapi.DeploymentLabel]; e != a {
119
+		t.Fatalf("expected label DeploymentLabel=%s, got %s", e, a)
120
+	}
121
+
122
+	if e, a := config.Name, deployment.Spec.Selector[deployapi.DeploymentConfigLabel]; e != a {
123
+		t.Fatalf("expected selector DeploymentConfigLabel=%s, got %s", e, a)
124
+	}
125
+
126
+	if e, a := deployment.Name, deployment.Spec.Selector[deployapi.DeploymentLabel]; e != a {
127
+		t.Fatalf("expected selector DeploymentLabel=%s, got %s", e, a)
128
+	}
129
+}