{"action":"created","comment":{"url":"https://api.github.com/repos/openshift/origin/pulls/comments/45636448","id":45636448,"diff_hunk":"@@ -1,835 +1,570 @@\n package deploymentconfig\n \n import (\n-\t\"errors\"\n-\t\"fmt\"\n-\t\"reflect\"\n \t\"sort\"\n+\t\"strconv\"\n+\t\"strings\"\n \t\"testing\"\n \n \tkapi \"k8s.io/kubernetes/pkg/api\"\n-\tkerrors \"k8s.io/kubernetes/pkg/api/errors\"\n+\t\"k8s.io/kubernetes/pkg/client/record\"\n \tktestclient \"k8s.io/kubernetes/pkg/client/unversioned/testclient\"\n \t\"k8s.io/kubernetes/pkg/runtime\"\n+\tkutil \"k8s.io/kubernetes/pkg/util\"\n \n-\tapi \"github.com/openshift/origin/pkg/api/latest\"\n-\tbuildapi \"github.com/openshift/origin/pkg/build/api\"\n \t\"github.com/openshift/origin/pkg/client/testclient\"\n \tdeployapi \"github.com/openshift/origin/pkg/deploy/api\"\n \tdeploytest \"github.com/openshift/origin/pkg/deploy/api/test\"\n \tdeployutil \"github.com/openshift/origin/pkg/deploy/util\"\n-\timageapi \"github.com/openshift/origin/pkg/image/api\"\n )\n \n-// TestHandle_initialOk ensures that an initial config (version 0) doesn't result\n-// in a new deployment.\n-func TestHandle_initialOk(t *testing.T) {\n-\tcontroller := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(config, api.Codec)\n-\t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn &kapi.ReplicationControllerList{}, nil\n-\t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n-\t\t},\n-\t\tosClient: testclient.NewSimpleFake(deploytest.OkDeploymentConfig(0)),\n-\t}\n-\n-\terr := controller.Handle(deploytest.OkDeploymentConfig(0))\n-\n-\tif err != nil {\n-\t\tt.Fatalf(\"unexpected error: %v\", err)\n-\t}\n-}\n-\n-// TestHandle_updateOk ensures that an updated config (version >0) results in\n-// a new deployment with the appropriate replica count based on a variety of\n-// existing prior deployments.\n-func TestHandle_updateOk(t *testing.T) {\n-\tvar (\n-\t\tconfig              *deployapi.DeploymentConfig\n-\t\tdeployed            *kapi.ReplicationController\n-\t\texistingDeployments *kapi.ReplicationControllerList\n-\t)\n-\n-\tcontroller := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(config, api.Codec)\n-\t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tdeployed = deployment\n-\t\t\t\treturn deployment, nil\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn existingDeployments, nil\n-\t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n-\t\t},\n-\t\tosClient: testclient.NewSimpleFake(),\n-\t}\n-\n-\ttype existing struct {\n-\t\tversion  int\n+func TestHandleScenarios(t *testing.T) {\n+\ttype deployment struct {\n+\t\t// version is the deployment version\n+\t\tversion int\n+\t\t// replicas is the spec replicas of the deployment\n \t\treplicas int\n-\t\tstatus   deployapi.DeploymentStatus\n+\t\t// replicasA is the annotated replica value for backwards compat checks\n+\t\treplicasA *int\n+\t\tdesiredA  *int\n+\t\tstatus    deployapi.DeploymentStatus\n+\t\tcancelled bool\n \t}\n \n-\ttype scenario struct {\n-\t\tversion          int\n-\t\texpectedReplicas int\n-\t\texisting         []existing\n-\t}\n-\n-\tscenarios := []scenario{\n-\t\t{1, 1, []existing{}},\n-\t\t{2, 1, []existing{\n-\t\t\t{1, 1, deployapi.DeploymentStatusComplete},\n-\t\t}},\n-\t\t{3, 4, []existing{\n-\t\t\t{1, 0, deployapi.DeploymentStatusComplete},\n-\t\t\t{2, 4, deployapi.DeploymentStatusComplete},\n-\t\t}},\n-\t\t{3, 4, []existing{\n-\t\t\t{1, 4, deployapi.DeploymentStatusComplete},\n-\t\t\t{2, 1, deployapi.DeploymentStatusFailed},\n-\t\t}},\n-\t\t{4, 2, []existing{\n-\t\t\t{1, 0, deployapi.DeploymentStatusComplete},\n-\t\t\t{2, 0, deployapi.DeploymentStatusFailed},\n-\t\t\t{3, 2, deployapi.DeploymentStatusComplete},\n-\t\t}},\n-\t\t// Scramble the order of the previous to ensure we still get it right.\n-\t\t{4, 2, []existing{\n-\t\t\t{2, 0, deployapi.DeploymentStatusFailed},\n-\t\t\t{3, 2, deployapi.DeploymentStatusComplete},\n-\t\t\t{1, 0, deployapi.DeploymentStatusComplete},\n-\t\t}},\n-\t}\n-\n-\tfor _, scenario := range scenarios {\n-\t\tdeployed = nil\n-\t\tconfig = deploytest.OkDeploymentConfig(scenario.version)\n-\t\tconfig.Triggers = []deployapi.DeploymentTriggerPolicy{}\n-\t\texistingDeployments = &kapi.ReplicationControllerList{}\n-\t\tfor _, e := range scenario.existing {\n-\t\t\td, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec)\n-\t\t\td.Spec.Replicas = e.replicas\n-\t\t\td.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status)\n-\t\t\texistingDeployments.Items = append(existingDeployments.Items, *d)\n-\t\t}\n-\n-\t\terr := controller.Handle(config)\n-\n-\t\tif deployed == nil {\n-\t\t\tt.Fatalf(\"expected a deployment\")\n-\t\t}\n-\n-\t\tif err != nil {\n-\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n+\tmkdeployment := func(d deployment) kapi.ReplicationController {\n+\t\tdeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(d.version), kapi.Codec)\n+\t\tdeployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(d.status)\n+\t\tif d.cancelled {\n+\t\t\tdeployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue\n+\t\t\tdeployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists\n \t\t}\n-\n-\t\tdesired, hasDesired := deployutil.DeploymentDesiredReplicas(deployed)\n-\t\tif !hasDesired {\n-\t\t\tt.Fatalf(\"expected desired replicas\")\n+\t\tif d.replicasA != nil {\n+\t\t\tdeployment.Annotations[deployapi.DeploymentReplicasAnnotation] = strconv.Itoa(*d.replicasA)\n+\t\t} else {\n+\t\t\tdelete(deployment.Annotations, deployapi.DeploymentReplicasAnnotation)\n \t\t}\n-\t\tif e, a := scenario.expectedReplicas, desired; e != a {\n-\t\t\tt.Errorf(\"expected desired replicas %d, got %d\", e, a)\n+\t\tif d.desiredA != nil {\n+\t\t\tdeployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(*d.desiredA)\n+\t\t} else {\n+\t\t\tdelete(deployment.Annotations, deployapi.DesiredReplicasAnnotation)\n \t\t}\n+\t\tdeployment.Spec.Replicas = d.replicas\n+\t\treturn *deployment\n \t}\n-}\n \n-// TestHandle_nonfatalLookupError ensures that an API failure to look up the\n-// existing deployment for an updated config results in a nonfatal error.\n-func TestHandle_nonfatalLookupError(t *testing.T) {\n-\tconfigController := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(config, api.Codec)\n+\ttests := []struct {\n+\t\tname string\n+\t\t// replicas is the config replicas prior to the update\n+\t\treplicas int\n+\t\t// newVersion is the version of the config at the time of the update\n+\t\tnewVersion int\n+\t\t// expectedReplicas is the expected config replica count after the update\n+\t\texpectedReplicas int\n+\t\t// before is the state of all deployments prior to the update\n+\t\tbefore []deployment\n+\t\t// after is the expected state of all deployments after the update\n+\t\tafter []deployment\n+\t\t// errExpected is whether the update should produce an error\n+\t\terrExpected bool\n+\t}{\n+\t\t{\n+\t\t\tname:             \"version is zero\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       0,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore:           []deployment{},\n+\t\t\tafter:            []deployment{},\n+\t\t\terrExpected:      false,\n \t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn nil, kerrors.NewInternalError(fmt.Errorf(\"fatal test error\"))\n+\t\t{\n+\t\t\tname:             \"first deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       1,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore:           []deployment{},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n+\t\t},\n+\t\t{\n+\t\t\tname:             \"initial deployment already in progress\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       1,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n \t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n \t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(),\n-\t}\n-\n-\terr := configController.Handle(deploytest.OkDeploymentConfig(1))\n-\tif err == nil {\n-\t\tt.Fatalf(\"expected error\")\n-\t}\n-\tif _, isFatal := err.(fatalError); isFatal {\n-\t\tt.Fatalf(\"expected a retryable error, got a fatal error: %v\", err)\n-\t}\n-}\n-\n-// TestHandle_configAlreadyDeployed ensures that an attempt to create a\n-// deployment for an updated config for which the deployment was already\n-// created results in a no-op.\n-func TestHandle_configAlreadyDeployed(t *testing.T) {\n-\tdeploymentConfig := deploytest.OkDeploymentConfig(0)\n-\n-\tcontroller := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(config, api.Codec)\n+\t\t{\n+\t\t\tname:             \"new version\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected call to to create deployment: %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\texistingDeployments := []kapi.ReplicationController{}\n-\t\t\t\tdeployment, _ := deployutil.MakeDeployment(deploymentConfig, kapi.Codec)\n-\t\t\t\texistingDeployments = append(existingDeployments, *deployment)\n-\t\t\t\treturn &kapi.ReplicationControllerList{Items: existingDeployments}, nil\n+\t\t{\n+\t\t\tname:             \"already in progress\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n+\t\t},\n+\t\t{\n+\t\t\tname:             \"already deployed\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       1,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n \t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n \t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(deploymentConfig),\n-\t}\n-\n-\terr := controller.Handle(deploymentConfig)\n-\n-\tif err != nil {\n-\t\tt.Fatalf(\"unexpected error: %v\", err)\n-\t}\n-}\n-\n-// TestHandle_nonfatalCreateError ensures that a failed API attempt to create\n-// a new deployment for an updated config results in a nonfatal error.\n-func TestHandle_nonfatalCreateError(t *testing.T) {\n-\tconfigController := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(config, api.Codec)\n+\t\t{\n+\t\t\tname:             \"awaiting cancellation of older deployments\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       3,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: true,\n \t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\treturn nil, kerrors.NewInternalError(fmt.Errorf(\"test error\"))\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn &kapi.ReplicationControllerList{}, nil\n+\t\t{\n+\t\t\tname:             \"awaiting cancellation of older deployments (already cancelled)\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n \t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n \t\t\t},\n+\t\t\terrExpected: true,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(),\n-\t}\n-\n-\terr := configController.Handle(deploytest.OkDeploymentConfig(1))\n-\tif err == nil {\n-\t\tt.Fatalf(\"expected error\")\n-\t}\n-\tif _, isFatal := err.(fatalError); isFatal {\n-\t\tt.Fatalf(\"expected a nonfatal error, got a fatal error: %v\", err)\n-\t}\n-}\n-\n-// TestHandle_fatalError ensures that in internal (not API) failure to make a\n-// deployment from an updated config results in a fatal error.\n-func TestHandle_fatalError(t *testing.T) {\n-\tconfigController := &DeploymentConfigController{\n-\t\tmakeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn nil, fmt.Errorf(\"couldn't make deployment\")\n+\t\t{\n+\t\t\tname:             \"steady state replica corrections (latest == active)\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       5,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected call to create\")\n-\t\t\t\treturn nil, kerrors.NewInternalError(fmt.Errorf(\"test error\"))\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn &kapi.ReplicationControllerList{}, nil\n-\t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tt.Fatalf(\"unexpected update call with deployment %v\", deployment)\n-\t\t\t\treturn nil, nil\n-\t\t\t},\n+\t\t{\n+\t\t\tname:             \"steady state replica corrections (latest != active)\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       5,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(deploytest.OkDeploymentConfig(1)),\n-\t}\n-\n-\terr := configController.Handle(deploytest.OkDeploymentConfig(1))\n-\tif err == nil {\n-\t\tt.Fatalf(\"expected error\")\n-\t}\n-\tif _, isFatal := err.(fatalError); !isFatal {\n-\t\tt.Fatalf(\"expected a fatal error, got: %v\", err)\n-\t}\n-}\n-\n-// TestHandle_existingDeployments ensures that an attempt to create a\n-// new deployment for a config that has existing deployments succeeds of fails\n-// depending upon the state of the existing deployments\n-func TestHandle_existingDeployments(t *testing.T) {\n-\tvar (\n-\t\tconfig              *deployapi.DeploymentConfig\n-\t\tdeployed            *kapi.ReplicationController\n-\t\texistingDeployments *kapi.ReplicationControllerList\n-\t\tupdatedDeployments  []kapi.ReplicationController\n-\t)\n-\n-\tcontroller := &DeploymentConfigController{\n-\t\tmakeDeployment: func(cfg *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) {\n-\t\t\treturn deployutil.MakeDeployment(cfg, api.Codec)\n+\t\t{\n+\t\t\tname:             \"already deployed, no active deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tcreateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tdeployed = deployment\n-\t\t\t\treturn deployment, nil\n-\t\t\t},\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn existingDeployments, nil\n-\t\t\t},\n-\t\t\tupdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) {\n-\t\t\t\tupdatedDeployments = append(updatedDeployments, *deployment)\n-\t\t\t\treturn deployment, nil\n-\t\t\t},\n+\t\t{\n+\t\t\tname:             \"scale up latest/active completed deployment\",\n+\t\t\treplicas:         5,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 5,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(),\n-\t}\n-\n-\ttype existing struct {\n-\t\tversion      int\n-\t\tstatus       deployapi.DeploymentStatus\n-\t\tshouldCancel bool\n-\t}\n-\n-\ttype scenario struct {\n-\t\tversion          int\n-\t\texisting         []existing\n-\t\terrorType        reflect.Type\n-\t\texpectDeployment bool\n-\t}\n-\n-\ttransientErrorType := reflect.TypeOf(transientError(\"\"))\n-\tscenarios := []scenario{\n-\t\t// No existing deployments\n-\t\t{1, []existing{}, nil, true},\n-\t\t// A single existing completed deployment\n-\t\t{2, []existing{{1, deployapi.DeploymentStatusComplete, false}}, nil, true},\n-\t\t// A single existing failed deployment\n-\t\t{2, []existing{{1, deployapi.DeploymentStatusFailed, false}}, nil, true},\n-\t\t// Multiple existing completed/failed deployments\n-\t\t{3, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, true},\n-\n-\t\t// A single existing deployment in the default state\n-\t\t{2, []existing{{1, \"\", false}}, transientErrorType, false},\n-\t\t// A single existing new deployment\n-\t\t{2, []existing{{1, deployapi.DeploymentStatusNew, false}}, transientErrorType, false},\n-\t\t// A single existing pending deployment\n-\t\t{2, []existing{{1, deployapi.DeploymentStatusPending, false}}, transientErrorType, false},\n-\t\t// A single existing running deployment\n-\t\t{2, []existing{{1, deployapi.DeploymentStatusRunning, false}}, transientErrorType, false},\n-\t\t// Multiple existing deployments with one in new/pending/running\n-\t\t{4, []existing{{3, deployapi.DeploymentStatusRunning, false}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false},\n-\n-\t\t// Latest deployment exists and has already failed/completed\n-\t\t{2, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false},\n-\t\t// Latest deployment exists and is in new/pending/running state\n-\t\t{2, []existing{{2, deployapi.DeploymentStatusRunning, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false},\n-\n-\t\t// Multiple existing deployments with more than one in new/pending/running\n-\t\t{4, []existing{{3, deployapi.DeploymentStatusNew, false}, {2, deployapi.DeploymentStatusRunning, true}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false},\n-\t\t// Multiple existing deployments with more than one in new/pending/running\n-\t\t// Latest deployment has already failed\n-\t\t{6, []existing{{5, deployapi.DeploymentStatusFailed, false}, {4, deployapi.DeploymentStatusRunning, false}, {3, deployapi.DeploymentStatusNew, true}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusNew, true}}, transientErrorType, false},\n-\t}\n-\n-\tfor _, scenario := range scenarios {\n-\t\tupdatedDeployments = []kapi.ReplicationController{}\n-\t\tdeployed = nil\n-\t\tconfig = deploytest.OkDeploymentConfig(scenario.version)\n-\t\texistingDeployments = &kapi.ReplicationControllerList{}\n-\t\tfor _, e := range scenario.existing {\n-\t\t\td, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec)\n-\t\t\tif e.status != \"\" {\n-\t\t\t\td.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status)\n-\t\t\t}\n-\t\t\texistingDeployments.Items = append(existingDeployments.Items, *d)\n-\t\t}\n-\t\tcontroller.osClient = testclient.NewSimpleFake(config)\n-\t\terr := controller.Handle(config)\n-\n-\t\tif scenario.expectDeployment && deployed == nil {\n-\t\t\tt.Fatalf(\"expected a deployment\")\n-\t\t}\n-\n-\t\tif scenario.errorType == nil {\n-\t\t\tif err != nil {\n-\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n-\t\t\t}\n-\t\t} else {\n-\t\t\tif err == nil {\n-\t\t\t\tt.Fatalf(\"expected error\")\n-\t\t\t}\n-\t\t\tif reflect.TypeOf(err) != scenario.errorType {\n-\t\t\t\tt.Fatalf(\"error expected: %s, got: %s\", scenario.errorType, reflect.TypeOf(err))\n-\t\t\t}\n-\t\t}\n-\n-\t\texpectedCancellations := []int{}\n-\t\tactualCancellations := []int{}\n-\t\tfor _, e := range scenario.existing {\n-\t\t\tif e.shouldCancel {\n-\t\t\t\texpectedCancellations = append(expectedCancellations, e.version)\n-\t\t\t}\n-\t\t}\n-\t\tfor _, d := range updatedDeployments {\n-\t\t\tactualCancellations = append(actualCancellations, deployutil.DeploymentVersionFor(&d))\n-\t\t}\n-\n-\t\tsort.Ints(actualCancellations)\n-\t\tsort.Ints(expectedCancellations)\n-\t\tif !reflect.DeepEqual(actualCancellations, expectedCancellations) {\n-\t\t\tt.Fatalf(\"expected cancellations: %v, actual: %v\", expectedCancellations, actualCancellations)\n-\t\t}\n-\t}\n-}\n-\n-func TestFindDetails(t *testing.T) {\n-\texistingDeployments := &kapi.ReplicationControllerList{}\n-\tcontroller := &DeploymentConfigController{\n-\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\treturn existingDeployments, nil\n-\t\t\t},\n+\t\t{\n+\t\t\tname:             \"scale up active (not latest) completed deployment\",\n+\t\t\treplicas:         5,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 5,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: false,\n+\t\t},\n+\t\t{\n+\t\t\tname:             \"scale down latest/active completed deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n+\t\t},\n+\t\t{\n+\t\t\tname:             \"scale down active (not latest) completed deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n-\t\tosClient: testclient.NewSimpleFake(),\n-\t}\n-\n-\ttype reaction struct {\n-\t\tverb, resource string\n-\t\tfn             ktestclient.ReactionFunc\n-\t}\n-\n-\ttests := []struct {\n-\t\tname              string\n-\t\tcontroller        *DeploymentConfigController\n-\t\tversion           int\n-\t\thasDeployments    bool\n-\t\thasNoImageChange  bool\n-\t\thasMultipleErrors bool\n-\t\tstatus            deployapi.DeploymentStatus\n-\t\treactions         []reaction\n-\t\texpectedDetails   string\n-\t\texpectedLatest    bool\n-\t\texpectedErr       bool\n-\t}{\n \t\t{\n-\t\t\tname: \"cannot get existing deployments\",\n-\t\t\tcontroller: &DeploymentConfigController{\n-\t\t\t\tdeploymentClient: &deploymentClientImpl{\n-\t\t\t\t\tlistDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {\n-\t\t\t\t\t\treturn nil, errors.New(\"cannot return these deployments\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\tversion:         0,\n-\t\t\thasDeployments:  false,\n-\t\t\texpectedDetails: \"\",\n-\t\t\texpectedLatest:  false,\n-\t\t\texpectedErr:     true,\n+\t\t\tname:             \"fallback to last completed deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: false,\n+\t\t},\n+\t\t// The cases below will exercise backwards compatibility for resources\n+\t\t// which predate the use of the replica annotation.\n+\t\t{\n+\t\t\tname:             \"(compat) initial deployment already in progress\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       1,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:            \"complete latest\",\n-\t\t\tcontroller:      controller,\n-\t\t\tversion:         1,\n-\t\t\thasDeployments:  true,\n-\t\t\tstatus:          deployapi.DeploymentStatusComplete,\n-\t\t\texpectedDetails: \"\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) new version\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:             \"no image change triggers\",\n-\t\t\tcontroller:       controller,\n-\t\t\tversion:          1,\n-\t\t\thasDeployments:   true,\n-\t\t\thasNoImageChange: true,\n-\t\t\tstatus:           deployapi.DeploymentStatusFailed,\n-\t\t\texpectedDetails:  \"\",\n-\t\t\texpectedLatest:   true,\n-\t\t\texpectedErr:      false,\n+\t\t\tname:             \"(compat) already in progress\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:           \"cannot retrieve istag\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, errors.New(\"unknown error while retrieving an istag\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) already deployed\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       1,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:           \"found istag\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, &imageapi.ImageStreamTag{}, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) awaiting cancellation of older deployments\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       3,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: true,\n \t\t},\n \t\t{\n-\t\t\tname:           \"not found istag\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:latest\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"list\",\n-\t\t\t\t\tresource: \"buildconfigs\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, &buildapi.BuildConfigList{}, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream tag \\\"test-image-stream:latest\\\" does not exist.\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) awaiting cancellation of older deployments (already cancelled)\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true},\n+\t\t\t},\n+\t\t\terrExpected: true,\n \t\t},\n \t\t{\n-\t\t\tname:           \"synthetic istag\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:\"+imageapi.DefaultImageTag)\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"list\",\n-\t\t\t\t\tresource: \"buildconfigs\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, mkBuildConfigList(), nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream tag \\\"test-image-stream:latest\\\" does not exist.\\n\\tIf image stream tag \\\"test-image-stream:latest\\\" is expected, check build config \\\"mybc\\\" which produces image stream tag \\\"test-image-stream:latest\\\".\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) steady state replica corrections (latest == active)\",\n+\t\t\treplicas:         5,\n+\t\t\tnewVersion:       5,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:           \"not found image stream\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:latest\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestream\", \"test-image-stream\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream \\\"test-image-stream\\\" does not exist.\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) steady state replica corrections (latest != active)\",\n+\t\t\treplicas:         5,\n+\t\t\tnewVersion:       5,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true},\n+\t\t\t\t{version: 4, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 5, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:       \"no deployments - not found istag\",\n-\t\t\tcontroller: controller,\n-\t\t\tversion:    0,\n-\t\t\tstatus:     deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:latest\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"list\",\n-\t\t\t\t\tresource: \"buildconfigs\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, &buildapi.BuildConfigList{}, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream tag \\\"test-image-stream:latest\\\" does not exist.\",\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) already deployed, no active deployment\",\n+\t\t\treplicas:         2,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 1,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:       \"no deployments - synthetic istag\",\n-\t\t\tcontroller: controller,\n-\t\t\tversion:    0,\n-\t\t\tstatus:     deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:\"+imageapi.DefaultImageTag)\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"list\",\n-\t\t\t\t\tresource: \"buildconfigs\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, mkBuildConfigList(), nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, nil\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream tag \\\"test-image-stream:latest\\\" does not exist.\\n\\tIf image stream tag \\\"test-image-stream:latest\\\" is expected, check build config \\\"mybc\\\" which produces image stream tag \\\"test-image-stream:latest\\\".\",\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) scale up latest/active completed deployment\",\n+\t\t\treplicas:         1,\n+\t\t\tnewVersion:       2,\n+\t\t\texpectedReplicas: 5,\n+\t\t\tbefore: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\tafter: []deployment{\n+\t\t\t\t{version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t\t{version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false},\n+\t\t\t},\n+\t\t\terrExpected: false,\n \t\t},\n \t\t{\n-\t\t\tname:           \"no deployments - not found image stream\",\n-\t\t\tcontroller:     controller,\n-\t\t\tversion:        1,\n-\t\t\thasDeployments: true,\n-\t\t\tstatus:         deployapi.DeploymentStatusFailed,\n-\t\t\treactions: []reaction{\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreamtags\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestreamtag\", \"test-image-stream:latest\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestream\", \"test-image-stream\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t\t{\n-\t\t\t\t\tverb:     \"get\",\n-\t\t\t\t\tresource: \"imagestreams\",\n-\t\t\t\t\tfn: func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {\n-\t\t\t\t\t\treturn true, nil, kerrors.NewNotFound(\"imagestream\", \"test-image-stream\")\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t\texpectedDetails: \"The image trigger for image stream tag \\\"test-image-stream:latest\\\" will have no effect because image stream \\\"test-image-stream\\\" does not exist.\",\n-\t\t\texpectedLatest:  true,\n-\t\t\texpectedErr:     false,\n+\t\t\tname:             \"(compat) scale up active (not latest) completed deployment\",","path":"pkg/deploy/controller/deploymentconfig/controller_test.go","position":1103,"original_position":1103,"commit_id":"2b769ddc126842e9c17f58881c62195520d0062a","original_commit_id":"2b769ddc126842e9c17f58881c62195520d0062a","user":{"login":"ironcladlou","id":298299,"avatar_url":"https://avatars.githubusercontent.com/u/298299?v=3","gravatar_id":"","url":"https://api.github.com/users/ironcladlou","html_url":"https://github.com/ironcladlou","followers_url":"https://api.github.com/users/ironcladlou/followers","following_url":"https://api.github.com/users/ironcladlou/following{/other_user}","gists_url":"https://api.github.com/users/ironcladlou/gists{/gist_id}","starred_url":"https://api.github.com/users/ironcladlou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ironcladlou/subscriptions","organizations_url":"https://api.github.com/users/ironcladlou/orgs","repos_url":"https://api.github.com/users/ironcladlou/repos","events_url":"https://api.github.com/users/ironcladlou/events{/privacy}","received_events_url":"https://api.github.com/users/ironcladlou/received_events","type":"User","site_admin":false},"body":"Correct. As you pointed out earlier, the RC targetting in the scaler code looks only for the \"latest\" (not the \"active\")...","created_at":"2015-11-23T17:58:29Z","updated_at":"2015-11-23T17:58:29Z","html_url":"https://github.com/openshift/origin/pull/5875#discussion_r45636448","pull_request_url":"https://api.github.com/repos/openshift/origin/pulls/5875","_links":{"self":{"href":"https://api.github.com/repos/openshift/origin/pulls/comments/45636448"},"html":{"href":"https://github.com/openshift/origin/pull/5875#discussion_r45636448"},"pull_request":{"href":"https://api.github.com/repos/openshift/origin/pulls/5875"}}},"pull_request":{"url":"https://api.github.com/repos/openshift/origin/pulls/5875","id":50521607,"html_url":"https://github.com/openshift/origin/pull/5875","diff_url":"https://github.com/openshift/origin/pull/5875.diff","patch_url":"https://github.com/openshift/origin/pull/5875.patch","issue_url":"https://api.github.com/repos/openshift/origin/issues/5875","number":5875,"state":"open","locked":false,"title":"Improve deployment scaling behavior","user":{"login":"ironcladlou","id":298299,"avatar_url":"https://avatars.githubusercontent.com/u/298299?v=3","gravatar_id":"","url":"https://api.github.com/users/ironcladlou","html_url":"https://github.com/ironcladlou","followers_url":"https://api.github.com/users/ironcladlou/followers","following_url":"https://api.github.com/users/ironcladlou/following{/other_user}","gists_url":"https://api.github.com/users/ironcladlou/gists{/gist_id}","starred_url":"https://api.github.com/users/ironcladlou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ironcladlou/subscriptions","organizations_url":"https://api.github.com/users/ironcladlou/orgs","repos_url":"https://api.github.com/users/ironcladlou/repos","events_url":"https://api.github.com/users/ironcladlou/events{/privacy}","received_events_url":"https://api.github.com/users/ironcladlou/received_events","type":"User","site_admin":false},"body":"This change makes the DeploymentConfigController solely responsible for scaling RCs owned by a deployment config which aren't currently being manipulated by a deployer process. RCs owned by the deployment config will be reconciled so that the active deployment matches the config replica count and all other RCs are scaled to 0.\r\n\r\nRationale:\r\n\r\n* Prevents races with HPA, users performing manual scaling, the deployer process, and the deployer pod controller (which was also performing scaling operations.)\r\n* Aligns replica count handling with upstream deployments; today, the replica count for deployments follows the last successful deployment and requires users to scale RCs in order to adjust subsequent deployment replica counts. Now, changes to the config replica count will drive new deployments, and scale existing deployments.\r\n* Allows all scaling operations to target the deploymentConfig only rather than RCs.\r\n\r\nThis also adds a variety of new events to provide insight into what the controller is doing.\r\n\r\nFixes #5597\r\n\r\n### TODO\r\n- [x] deployerPodController unit tests\r\n- [x] update DC scaling subresource to scale the DC directly\r\n- [x] update deployment registry to remove restriction on scaling during an in-progress deployment\r\n- [x] e2e or extended tests to exercise scaling interactions\r\n- [x] discussion of https://github.com/openshift/origin/pull/5875#issuecomment-156197377\r\n- [x] implement compatibility logic for old clients which will still scale RCs directly\r\n","created_at":"2015-11-12T15:07:36Z","updated_at":"2015-11-23T17:58:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"68fb1f78a1ad75847efa232481ab517200a0b8ee","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/openshift/origin/pulls/5875/commits","review_comments_url":"https://api.github.com/repos/openshift/origin/pulls/5875/comments","review_comment_url":"https://api.github.com/repos/openshift/origin/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openshift/origin/issues/5875/comments","statuses_url":"https://api.github.com/repos/openshift/origin/statuses/2b769ddc126842e9c17f58881c62195520d0062a","head":{"label":"ironcladlou:dc-scaling","ref":"dc-scaling","sha":"2b769ddc126842e9c17f58881c62195520d0062a","user":{"login":"ironcladlou","id":298299,"avatar_url":"https://avatars.githubusercontent.com/u/298299?v=3","gravatar_id":"","url":"https://api.github.com/users/ironcladlou","html_url":"https://github.com/ironcladlou","followers_url":"https://api.github.com/users/ironcladlou/followers","following_url":"https://api.github.com/users/ironcladlou/following{/other_user}","gists_url":"https://api.github.com/users/ironcladlou/gists{/gist_id}","starred_url":"https://api.github.com/users/ironcladlou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ironcladlou/subscriptions","organizations_url":"https://api.github.com/users/ironcladlou/orgs","repos_url":"https://api.github.com/users/ironcladlou/repos","events_url":"https://api.github.com/users/ironcladlou/events{/privacy}","received_events_url":"https://api.github.com/users/ironcladlou/received_events","type":"User","site_admin":false},"repo":{"id":23721352,"name":"origin","full_name":"ironcladlou/origin","owner":{"login":"ironcladlou","id":298299,"avatar_url":"https://avatars.githubusercontent.com/u/298299?v=3","gravatar_id":"","url":"https://api.github.com/users/ironcladlou","html_url":"https://github.com/ironcladlou","followers_url":"https://api.github.com/users/ironcladlou/followers","following_url":"https://api.github.com/users/ironcladlou/following{/other_user}","gists_url":"https://api.github.com/users/ironcladlou/gists{/gist_id}","starred_url":"https://api.github.com/users/ironcladlou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ironcladlou/subscriptions","organizations_url":"https://api.github.com/users/ironcladlou/orgs","repos_url":"https://api.github.com/users/ironcladlou/repos","events_url":"https://api.github.com/users/ironcladlou/events{/privacy}","received_events_url":"https://api.github.com/users/ironcladlou/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ironcladlou/origin","description":"OpenShift 3 - build, deploy, and manage your applications with Docker and Kubernetes","fork":true,"url":"https://api.github.com/repos/ironcladlou/origin","forks_url":"https://api.github.com/repos/ironcladlou/origin/forks","keys_url":"https://api.github.com/repos/ironcladlou/origin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ironcladlou/origin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ironcladlou/origin/teams","hooks_url":"https://api.github.com/repos/ironcladlou/origin/hooks","issue_events_url":"https://api.github.com/repos/ironcladlou/origin/issues/events{/number}","events_url":"https://api.github.com/repos/ironcladlou/origin/events","assignees_url":"https://api.github.com/repos/ironcladlou/origin/assignees{/user}","branches_url":"https://api.github.com/repos/ironcladlou/origin/branches{/branch}","tags_url":"https://api.github.com/repos/ironcladlou/origin/tags","blobs_url":"https://api.github.com/repos/ironcladlou/origin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ironcladlou/origin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ironcladlou/origin/git/refs{/sha}","trees_url":"https://api.github.com/repos/ironcladlou/origin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ironcladlou/origin/statuses/{sha}","languages_url":"https://api.github.com/repos/ironcladlou/origin/languages","stargazers_url":"https://api.github.com/repos/ironcladlou/origin/stargazers","contributors_url":"https://api.github.com/repos/ironcladlou/origin/contributors","subscribers_url":"https://api.github.com/repos/ironcladlou/origin/subscribers","subscription_url":"https://api.github.com/repos/ironcladlou/origin/subscription","commits_url":"https://api.github.com/repos/ironcladlou/origin/commits{/sha}","git_commits_url":"https://api.github.com/repos/ironcladlou/origin/git/commits{/sha}","comments_url":"https://api.github.com/repos/ironcladlou/origin/comments{/number}","issue_comment_url":"https://api.github.com/repos/ironcladlou/origin/issues/comments{/number}","contents_url":"https://api.github.com/repos/ironcladlou/origin/contents/{+path}","compare_url":"https://api.github.com/repos/ironcladlou/origin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ironcladlou/origin/merges","archive_url":"https://api.github.com/repos/ironcladlou/origin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ironcladlou/origin/downloads","issues_url":"https://api.github.com/repos/ironcladlou/origin/issues{/number}","pulls_url":"https://api.github.com/repos/ironcladlou/origin/pulls{/number}","milestones_url":"https://api.github.com/repos/ironcladlou/origin/milestones{/number}","notifications_url":"https://api.github.com/repos/ironcladlou/origin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ironcladlou/origin/labels{/name}","releases_url":"https://api.github.com/repos/ironcladlou/origin/releases{/id}","created_at":"2014-09-05T23:22:09Z","updated_at":"2015-04-29T15:08:27Z","pushed_at":"2015-11-23T16:46:17Z","git_url":"git://github.com/ironcladlou/origin.git","ssh_url":"git@github.com:ironcladlou/origin.git","clone_url":"https://github.com/ironcladlou/origin.git","svn_url":"https://github.com/ironcladlou/origin","homepage":"https://openshift.github.io","size":181165,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"openshift:master","ref":"master","sha":"de3b2dbe74140333a43c98e067c49f3e34b57b74","user":{"login":"openshift","id":792337,"avatar_url":"https://avatars.githubusercontent.com/u/792337?v=3","gravatar_id":"","url":"https://api.github.com/users/openshift","html_url":"https://github.com/openshift","followers_url":"https://api.github.com/users/openshift/followers","following_url":"https://api.github.com/users/openshift/following{/other_user}","gists_url":"https://api.github.com/users/openshift/gists{/gist_id}","starred_url":"https://api.github.com/users/openshift/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openshift/subscriptions","organizations_url":"https://api.github.com/users/openshift/orgs","repos_url":"https://api.github.com/users/openshift/repos","events_url":"https://api.github.com/users/openshift/events{/privacy}","received_events_url":"https://api.github.com/users/openshift/received_events","type":"Organization","site_admin":false},"repo":{"id":22442668,"name":"origin","full_name":"openshift/origin","owner":{"login":"openshift","id":792337,"avatar_url":"https://avatars.githubusercontent.com/u/792337?v=3","gravatar_id":"","url":"https://api.github.com/users/openshift","html_url":"https://github.com/openshift","followers_url":"https://api.github.com/users/openshift/followers","following_url":"https://api.github.com/users/openshift/following{/other_user}","gists_url":"https://api.github.com/users/openshift/gists{/gist_id}","starred_url":"https://api.github.com/users/openshift/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openshift/subscriptions","organizations_url":"https://api.github.com/users/openshift/orgs","repos_url":"https://api.github.com/users/openshift/repos","events_url":"https://api.github.com/users/openshift/events{/privacy}","received_events_url":"https://api.github.com/users/openshift/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openshift/origin","description":"OpenShift 3 - build, deploy, and manage your applications with Docker and Kubernetes","fork":false,"url":"https://api.github.com/repos/openshift/origin","forks_url":"https://api.github.com/repos/openshift/origin/forks","keys_url":"https://api.github.com/repos/openshift/origin/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openshift/origin/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openshift/origin/teams","hooks_url":"https://api.github.com/repos/openshift/origin/hooks","issue_events_url":"https://api.github.com/repos/openshift/origin/issues/events{/number}","events_url":"https://api.github.com/repos/openshift/origin/events","assignees_url":"https://api.github.com/repos/openshift/origin/assignees{/user}","branches_url":"https://api.github.com/repos/openshift/origin/branches{/branch}","tags_url":"https://api.github.com/repos/openshift/origin/tags","blobs_url":"https://api.github.com/repos/openshift/origin/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openshift/origin/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openshift/origin/git/refs{/sha}","trees_url":"https://api.github.com/repos/openshift/origin/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openshift/origin/statuses/{sha}","languages_url":"https://api.github.com/repos/openshift/origin/languages","stargazers_url":"https://api.github.com/repos/openshift/origin/stargazers","contributors_url":"https://api.github.com/repos/openshift/origin/contributors","subscribers_url":"https://api.github.com/repos/openshift/origin/subscribers","subscription_url":"https://api.github.com/repos/openshift/origin/subscription","commits_url":"https://api.github.com/repos/openshift/origin/commits{/sha}","git_commits_url":"https://api.github.com/repos/openshift/origin/git/commits{/sha}","comments_url":"https://api.github.com/repos/openshift/origin/comments{/number}","issue_comment_url":"https://api.github.com/repos/openshift/origin/issues/comments{/number}","contents_url":"https://api.github.com/repos/openshift/origin/contents/{+path}","compare_url":"https://api.github.com/repos/openshift/origin/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openshift/origin/merges","archive_url":"https://api.github.com/repos/openshift/origin/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openshift/origin/downloads","issues_url":"https://api.github.com/repos/openshift/origin/issues{/number}","pulls_url":"https://api.github.com/repos/openshift/origin/pulls{/number}","milestones_url":"https://api.github.com/repos/openshift/origin/milestones{/number}","notifications_url":"https://api.github.com/repos/openshift/origin/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openshift/origin/labels{/name}","releases_url":"https://api.github.com/repos/openshift/origin/releases{/id}","created_at":"2014-07-30T20:04:05Z","updated_at":"2015-11-23T17:54:40Z","pushed_at":"2015-11-23T17:51:41Z","git_url":"git://github.com/openshift/origin.git","ssh_url":"git@github.com:openshift/origin.git","clone_url":"https://github.com/openshift/origin.git","svn_url":"https://github.com/openshift/origin","homepage":"http://www.openshift.org","size":226735,"stargazers_count":824,"watchers_count":824,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":519,"mirror_url":null,"open_issues_count":683,"forks":519,"open_issues":683,"watchers":824,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openshift/origin/pulls/5875"},"html":{"href":"https://github.com/openshift/origin/pull/5875"},"issue":{"href":"https://api.github.com/repos/openshift/origin/issues/5875"},"comments":{"href":"https://api.github.com/repos/openshift/origin/issues/5875/comments"},"review_comments":{"href":"https://api.github.com/repos/openshift/origin/pulls/5875/comments"},"review_comment":{"href":"https://api.github.com/repos/openshift/origin/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openshift/origin/pulls/5875/commits"},"statuses":{"href":"https://api.github.com/repos/openshift/origin/statuses/2b769ddc126842e9c17f58881c62195520d0062a"}}}}