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.
| ... | ... |
@@ -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 |
-} |
| 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 |
+} |