func TestDeploy_reenableTriggers(t *testing.T) { mktrigger := func() deployapi.DeploymentTriggerPolicy { t := deploytest.OkImageChangeTrigger() t.ImageChangeParams.Automatic = false return t } var updated *deployapi.DeploymentConfig osClient := &tc.Fake{} osClient.ReactFn = func(action ktc.Action) (runtime.Object, error) { switch a := action.(type) { case ktc.UpdateActionImpl: updated = a.GetObject().(*deployapi.DeploymentConfig) return updated, nil } t.Fatalf("unexpected action: %+v", action) return nil, nil } config := deploytest.OkDeploymentConfig(1) config.Triggers = []deployapi.DeploymentTriggerPolicy{} count := 3 for i := 0; i < count; i++ { config.Triggers = append(config.Triggers, mktrigger()) } o := &DeployOptions{osClient: osClient} err := o.reenableTriggers(config, ioutil.Discard) if err != nil { t.Fatalf("unexpected error: %v", err) } if updated == nil { t.Fatalf("expected an updated config") } if e, a := count, len(config.Triggers); e != a { t.Fatalf("expected %d triggers, got %d", e, a) } for _, trigger := range config.Triggers { if !trigger.ImageChangeParams.Automatic { t.Errorf("expected trigger to be enabled: %#v", trigger.ImageChangeParams) } } }
func TestHandle_nonAutomaticImageUpdates(t *testing.T) { var updated *deployapi.DeploymentConfig controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { generated := deployapitest.OkDeploymentConfig(1) // The generator doesn't change automatic so it's ok to fake it here. generated.Spec.Triggers[0].ImageChangeParams.Automatic = false generated.Status.Details = deployapitest.OkImageChangeDetails() updated = generated return generated, nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated.Status.Details = deployapitest.OkConfigChangeDetails() return updated, nil }, }, } config := deployapitest.OkDeploymentConfig(0) ict := deployapitest.OkImageChangeTrigger() ict.ImageChangeParams.Automatic = false config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger(), ict} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := 1, updated.Status.LatestVersion; e != a { t.Fatalf("expected update to latestversion=%d, got %d", e, a) } if updated.Status.Details == nil { t.Fatalf("expected config change details to be set") } else if updated.Status.Details.Causes == nil { t.Fatalf("expected config change causes to be set") } else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange { t.Fatalf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type) } }
func TestDeploy_reenableTriggers(t *testing.T) { mktrigger := func() deployapi.DeploymentTriggerPolicy { t := deploytest.OkImageChangeTrigger() t.ImageChangeParams.Automatic = false return t } var updated *deployapi.DeploymentConfig osClient := &tc.Fake{} osClient.AddReactor("update", "deploymentconfigs", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { updated = action.(ktc.UpdateAction).GetObject().(*deployapi.DeploymentConfig) return true, updated, nil }) config := deploytest.OkDeploymentConfig(1) config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{} count := 3 for i := 0; i < count; i++ { config.Spec.Triggers = append(config.Spec.Triggers, mktrigger()) } o := &DeployOptions{osClient: osClient, out: ioutil.Discard} err := o.reenableTriggers(config) if err != nil { t.Fatalf("unexpected error: %v", err) } if updated == nil { t.Fatalf("expected an updated config") } if e, a := count, len(config.Spec.Triggers); e != a { t.Fatalf("expected %d triggers, got %d", e, a) } for _, trigger := range config.Spec.Triggers { if !trigger.ImageChangeParams.Automatic { t.Errorf("expected trigger to be enabled: %#v", trigger.ImageChangeParams) } } }
// TestHandle_imageChangeTrigger ensures that a config with an image change // trigger will be reconciled. func TestHandle_imageChangeTrigger(t *testing.T) { updated := false fake := &testclient.Fake{} fake.AddReactor("update", "deploymentconfigs/instantiate", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updated = true return true, nil, nil }) controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, codec) config := testapi.OkDeploymentConfig(0) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkImageChangeTrigger()} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } if !updated { t.Fatalf("expected config to be instantiated") } }
func TestDeploy_triggerEnable(t *testing.T) { var updated *deployapi.DeploymentConfig triggerEnabler := &triggerEnabler{ updateConfig: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated = config return config, nil }, } mktrigger := func() deployapi.DeploymentTriggerPolicy { t := deploytest.OkImageChangeTrigger() t.ImageChangeParams.Automatic = false return t } count := 3 config := deploytest.OkDeploymentConfig(1) config.Triggers = []deployapi.DeploymentTriggerPolicy{} for i := 0; i < count; i++ { config.Triggers = append(config.Triggers, mktrigger()) } err := triggerEnabler.enableTriggers(config, ioutil.Discard) if err != nil { t.Fatalf("unexpected error: %v", err) } if updated == nil { t.Fatalf("expected an updated config") } if e, a := count, len(config.Triggers); e != a { t.Fatalf("expected %d triggers, got %d", e, a) } for _, trigger := range config.Triggers { if !trigger.ImageChangeParams.Automatic { t.Errorf("expected trigger to be enabled: %#v", trigger.ImageChangeParams) } } }
func TestValidateDeploymentConfigICTMissingImage(t *testing.T) { dc := &api.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "foo", Namespace: "bar"}, Spec: api.DeploymentConfigSpec{ Replicas: 1, Triggers: []api.DeploymentTriggerPolicy{test.OkImageChangeTrigger()}, Selector: test.OkSelector(), Strategy: test.OkStrategy(), Template: test.OkPodTemplateMissingImage("container1"), }, } errs := ValidateDeploymentConfig(dc) if len(errs) > 0 { t.Errorf("Unexpected non-empty error list: %+v", errs) } for _, c := range dc.Spec.Template.Spec.Containers { if c.Image == "unset" { t.Errorf("%s image field still has validation fake out value of %s", c.Name, c.Image) } } }
// TestHandle_waitForImageController tests an initial deployment with unresolved image. The config // change controller should never increment latestVersion, thus trigger a deployment for this config. func TestHandle_waitForImageController(t *testing.T) { fake := &testclient.Fake{} kFake := &ktestclient.Fake{} fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { t.Fatalf("an update should never run before the template image is resolved") return true, nil, nil }) controller := NewDeploymentTriggerController(fake, kFake, codec) config := testapi.OkDeploymentConfig(0) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger()} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } }
// TestTriggers_imageChange ensures that a deployment config with an ImageChange trigger // will start a new deployment when an image change happens. func TestTriggers_imageChange(t *testing.T) { testutil.RequireEtcd(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("error starting master: %v", err) } openshiftClusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client: %v", err) } openshiftClusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client config: %v", err) } openshiftProjectAdminClient, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob") if err != nil { t.Fatalf("error creating project: %v", err) } imageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: deploytest.ImageStreamName}} config := deploytest.OkDeploymentConfig(0) config.Namespace = testutil.Namespace() config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deploytest.OkImageChangeTrigger()} configWatch, err := openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to deploymentconfigs %v", err) } defer configWatch.Stop() if imageStream, err = openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil { t.Fatalf("Couldn't create imagestream: %v", err) } imageWatch, err := openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to imagestreams: %v", err) } defer imageWatch.Stop() updatedImage := fmt.Sprintf("sha256:%s", deploytest.ImageID) updatedPullSpec := fmt.Sprintf("registry:8080/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, updatedImage) // Make a function which can create a new tag event for the image stream and // then wait for the stream status to be asynchronously updated. createTagEvent := func() { mapping := &imageapi.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{Name: imageStream.Name}, Tag: imageapi.DefaultImageTag, Image: imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: updatedImage, }, DockerImageReference: updatedPullSpec, }, } if err := openshiftProjectAdminClient.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil { t.Fatalf("unexpected error: %v", err) } t.Log("Waiting for image stream mapping to be reflected in the image stream status...") statusLoop: for { select { case event := <-imageWatch.ResultChan(): stream := event.Object.(*imageapi.ImageStream) if _, ok := stream.Status.Tags[imageapi.DefaultImageTag]; ok { t.Logf("imagestream %q now has status with tags: %#v", stream.Name, stream.Status.Tags) break statusLoop } t.Logf("Still waiting for latest tag status on imagestream %q", stream.Name) } } } if config, err = openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil { t.Fatalf("Couldn't create deploymentconfig: %v", err) } createTagEvent() var newConfig *deployapi.DeploymentConfig t.Log("Waiting for a new deployment config in response to imagestream update") waitForNewConfig: for { select { case event := <-configWatch.ResultChan(): if event.Type == watchapi.Modified { newConfig = event.Object.(*deployapi.DeploymentConfig) // Multiple updates to the config can be expected (e.g. status // updates), so wait for a significant update (e.g. version). if newConfig.Status.LatestVersion > 0 { if e, a := updatedPullSpec, newConfig.Spec.Template.Spec.Containers[0].Image; e != a { t.Fatalf("unexpected image for pod template container 0; expected %q, got %q", e, a) } break waitForNewConfig } t.Log("Still waiting for a new deployment config in response to imagestream update") } } } }
func TestCanTrigger(t *testing.T) { tests := []struct { name string config *deployapi.DeploymentConfig decoded *deployapi.DeploymentConfig force bool expected bool expectedCauses []deployapi.DeploymentCause expectedErr bool }{ { name: "no trigger [w/ podtemplate change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Triggers: []deployapi.DeploymentTriggerPolicy{}, Template: deploytest.OkPodTemplateChanged(), }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Triggers: []deployapi.DeploymentTriggerPolicy{}, Template: deploytest.OkPodTemplate(), }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, }, { name: "forced updated", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: true, expected: true, expectedCauses: []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerManual}}, }, { name: "config change trigger only [w/ podtemplate change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: true, expectedCauses: deploytest.OkConfigChangeDetails().Causes, }, { name: "config change trigger only [no change][initial]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, force: false, expected: true, expectedCauses: deploytest.OkConfigChangeDetails().Causes, }, { name: "config change trigger only [no change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, }, { name: "image change trigger only [automatic=false][w/ podtemplate change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), // Irrelevant change Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkNonAutomaticICT(), // Image still to be resolved but it's false anyway }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkNonAutomaticICT(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, expectedErr: true, }, { name: "image change trigger only [automatic=false][w/ image change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), // Image has been updated in the template but automatic=false Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkTriggeredNonAutomatic(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkNonAutomaticICT(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, }, { name: "image change trigger only [automatic=true][w/ image change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkTriggeredImageChange(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkImageChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: true, expectedCauses: deploytest.OkImageChangeDetails().Causes, }, { name: "image change trigger only [automatic=true][no change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkTriggeredImageChange(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkTriggeredImageChange(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, }, { name: "config change and image change trigger [automatic=false][initial][w/ image change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkTriggeredNonAutomatic(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkNonAutomaticICT(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, force: false, expected: true, expectedCauses: deploytest.OkConfigChangeDetails().Causes, }, { name: "config change and image change trigger [automatic=false][initial][no change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkNonAutomaticICT(), // Image is not resolved yet }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkNonAutomaticICT(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, force: false, expected: false, expectedCauses: nil, expectedErr: true, }, { name: "config change and image change trigger [automatic=true][initial][w/ podtemplate change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), // Pod template has changed but the image in the template is yet to be updated Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkImageChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkImageChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, force: false, expected: false, expectedCauses: nil, expectedErr: true, }, { name: "config change and image change trigger [automatic=true][initial][w/ image change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkTriggeredImageChange(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkImageChangeTrigger(), }, }, Status: deploytest.OkDeploymentConfigStatus(0), }, force: false, expected: true, expectedCauses: deploytest.OkImageChangeDetails().Causes, }, { name: "config change and image change trigger [automatic=true][no change]", config: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Name: "config"}, Spec: deployapi.DeploymentConfigSpec{ Template: deploytest.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkConfigChangeTrigger(), deploytest.OkTriggeredImageChange(), }, }, Status: deploytest.OkDeploymentConfigStatus(1), }, force: false, expected: false, expectedCauses: nil, }, } for _, test := range tests { t.Logf("running scenario %q", test.name) fake := &ktestclient.Fake{} fake.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { config := test.decoded if config == nil { config = test.config } config = deploytest.RoundTripConfig(t, config) deployment, _ := deployutil.MakeDeployment(config, codec) return true, deployment, nil }) test.config = deploytest.RoundTripConfig(t, test.config) got, gotCauses, err := canTrigger(test.config, fake, codec, test.force) if err != nil && !test.expectedErr { t.Errorf("unexpected error: %v", err) continue } if err == nil && test.expectedErr { t.Errorf("expected an error") continue } if test.expected != got { t.Errorf("expected to trigger: %t, got: %t", test.expected, got) } if !kapi.Semantic.DeepEqual(test.expectedCauses, gotCauses) { t.Errorf("expected causes:\n%#v\ngot:\n%#v", test.expectedCauses, gotCauses) } } }
func TestExport(t *testing.T) { exporter := &defaultExporter{} baseSA := &kapi.ServiceAccount{} baseSA.Name = "my-sa" tests := []struct { name string object runtime.Object exact bool expectedObj runtime.Object expectedErr error }{ { name: "export deploymentConfig", object: deploytest.OkDeploymentConfig(1), expectedObj: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{ Name: "config", }, LatestVersion: 0, Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkImageChangeTrigger(), }, Template: deploytest.OkDeploymentTemplate(), }, expectedErr: nil, }, { name: "export imageStream", object: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "other", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ DockerImageRepository: "foo/bar", Tags: map[string]imageapi.TagEventList{ "v1": { Items: []imageapi.TagEvent{{Image: "the image"}}, }, }, }, }, expectedObj: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "foo/bar:v1", }, Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{}, }, }, expectedErr: nil, }, { name: "remove unexportable SA secrets", object: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, expectedObj: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: "another-mountable-secret"}, }, }, expectedErr: nil, }, { name: "do not remove unexportable SA secrets with exact", object: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, expectedObj: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, exact: true, expectedErr: nil, }, } for _, test := range tests { if err := exporter.Export(test.object, test.exact); err != test.expectedErr { t.Errorf("error mismatch: expected %v, got %v", test.expectedErr, err) } if !reflect.DeepEqual(test.object, test.expectedObj) { t.Errorf("object mismatch: expected \n%v\ngot \n%v\n", test.expectedObj, test.object) } } }
func TestHandle_raceWithTheImageController(t *testing.T) { var updated *deployapi.DeploymentConfig controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { generated := deployapitest.OkDeploymentConfig(1) generated.Status.Details = deployapitest.OkImageChangeDetails() updated = generated return generated, nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { t.Errorf("an update should never run in the presence of races") updated.Status.Details = deployapitest.OkConfigChangeDetails() return updated, nil }, }, } config := deployapitest.OkDeploymentConfig(0) config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger(), deployapitest.OkImageChangeTrigger()} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := 1, updated.Status.LatestVersion; e != a { t.Fatalf("expected update to latestversion=%d, got %d", e, a) } if updated.Status.Details == nil { t.Fatalf("expected config change details to be set") } else if updated.Status.Details.Causes == nil { t.Fatalf("expected config change causes to be set") } else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnImageChange { t.Fatalf("expected config change cause to be set to image change trigger, got %s", updated.Status.Details.Causes[0].Type) } }
// TestTriggers_imageChange_nonAutomatic ensures that a deployment config with a non-automatic // trigger will have its image updated when a deployment is started manually. func TestTriggers_imageChange_nonAutomatic(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("error starting master: %v", err) } openshiftClusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client: %v", err) } openshiftClusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client config: %v", err) } oc, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob") if err != nil { t.Fatalf("error creating project: %v", err) } imageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: deploytest.ImageStreamName}} if imageStream, err = oc.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil { t.Fatalf("Couldn't create imagestream: %v", err) } imageWatch, err := oc.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to imagestreams: %v", err) } defer imageWatch.Stop() image := fmt.Sprintf("sha256:%s", deploytest.ImageID) pullSpec := fmt.Sprintf("registry:5000/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, image) // Make a function which can create a new tag event for the image stream and // then wait for the stream status to be asynchronously updated. mapping := &imageapi.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{Name: imageStream.Name}, Tag: imageapi.DefaultImageTag, Image: imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: image, }, DockerImageReference: pullSpec, }, } createTagEvent := func(mapping *imageapi.ImageStreamMapping) { if err := oc.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil { t.Fatalf("unexpected error: %v", err) } t.Log("Waiting for image stream mapping to be reflected in the image stream status...") timeout := time.After(time.Minute) for { select { case event := <-imageWatch.ResultChan(): stream := event.Object.(*imageapi.ImageStream) tagEventList, ok := stream.Status.Tags[imageapi.DefaultImageTag] if ok && len(tagEventList.Items) > 0 && tagEventList.Items[0].DockerImageReference == mapping.Image.DockerImageReference { t.Logf("imagestream %q now has status with tags: %#v", stream.Name, stream.Status.Tags) return } if len(tagEventList.Items) > 0 { t.Logf("want: %s, got: %s", mapping.Image.DockerImageReference, tagEventList.Items[0].DockerImageReference) } t.Logf("Still waiting for latest tag status update on imagestream %q with tags: %#v", stream.Name, tagEventList) case <-timeout: t.Fatalf("timed out waiting for image stream %q to be updated", imageStream.Name) } } } configWatch, err := oc.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to deploymentconfigs: %v", err) } defer configWatch.Stop() config := deploytest.OkDeploymentConfig(0) config.Namespace = testutil.Namespace() config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deploytest.OkImageChangeTrigger()} config.Spec.Triggers[0].ImageChangeParams.Automatic = false if config, err = oc.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil { t.Fatalf("Couldn't create deploymentconfig: %v", err) } createTagEvent(mapping) var newConfig *deployapi.DeploymentConfig t.Log("Waiting for the first imagestream update - no deployment should run") timeout := time.After(20 * time.Second) // Deployment config with automatic=false in its ICT - no deployment should trigger. // We don't really care about the initial update since it's not going to be deployed // anyway. out: for { select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig = event.Object.(*deployapi.DeploymentConfig) if newConfig.Status.LatestVersion > 0 { t.Fatalf("unexpected latestVersion update - the config has no config change trigger") } case <-timeout: break out } } t.Log("Waiting for the second imagestream update - no deployment should run") // Subsequent updates to the image shouldn't update the pod template image mapping.Image.Name = "sha256:thisupdatedimageshouldneverlandinthepodtemplate" mapping.Image.DockerImageReference = fmt.Sprintf("registry:8080/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, mapping.Image.Name) createTagEvent(mapping) timeout = time.After(20 * time.Second) loop: for { select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig = event.Object.(*deployapi.DeploymentConfig) if newConfig.Status.LatestVersion > 0 { t.Fatalf("unexpected latestVersion update - the config has no config change trigger") } case <-timeout: break loop } } t.Log("Instantiate the deployment config - the latest image should be picked up and a new deployment should run") request := &deployapi.DeploymentRequest{ Name: config.Name, Latest: true, Force: true, } if _, err = oc.DeploymentConfigs(config.Namespace).Instantiate(request); err != nil { t.Fatalf("Couldn't instantiate deployment config %q: %v", config.Name, err) } config, err = oc.DeploymentConfigs(config.Namespace).Get(config.Name) if err != nil { t.Fatalf("Unexpected error: %v", err) } if exp, got := mapping.Image.DockerImageReference, config.Spec.Template.Spec.Containers[0].Image; exp != got { t.Fatalf("Expected image %q instead of %q to be updated in deployment config %q", exp, got, config.Name) } if exp, got := int64(1), config.Status.LatestVersion; exp != got { t.Fatalf("Expected latestVersion for deployment config %q to be %d, got %d", config.Name, exp, got) } if config.Status.Details == nil || len(config.Status.Details.Causes) == 0 { t.Fatalf("Expected a cause of deployment for deployment config %q", config.Name) } if gotType, expectedType := config.Status.Details.Causes[0].Type, deployapi.DeploymentTriggerManual; gotType != expectedType { t.Fatalf("Instantiated deployment config should have a %q cause of deployment instead of %q", expectedType, gotType) } }
func TestExport(t *testing.T) { exporter := &defaultExporter{} tests := []struct { name string object runtime.Object exact bool expectedObj runtime.Object expectedErr error }{ { name: "export deploymentConfig", object: deploytest.OkDeploymentConfig(1), expectedObj: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{ Name: "config", }, LatestVersion: 0, Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkImageChangeTrigger(), }, Template: deploytest.OkDeploymentTemplate(), }, expectedErr: nil, }, { name: "export imageStream", object: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "other", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ DockerImageRepository: "foo/bar", Tags: map[string]imageapi.TagEventList{ "v1": { Items: []imageapi.TagEvent{{Image: "the image"}}, }, }, }, }, expectedObj: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "foo/bar:v1", }, Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{}, }, }, expectedErr: nil, }, } for _, test := range tests { if err := exporter.Export(test.object, test.exact); err != test.expectedErr { t.Errorf("error mismatch: expected %v, got %v", test.expectedErr, err) } if !reflect.DeepEqual(test.object, test.expectedObj) { t.Errorf("object mismatch: expected \n%v\ngot \n%v\n", test.expectedObj, test.object) } } }
// TestHandle_automaticImageUpdates tests automatic and non-automatic updates // from image change triggers. func TestHandle_automaticImageUpdates(t *testing.T) { tests := []struct { name string auto bool canTrigger bool version int64 expectedUpdate bool }{ { name: "initial deployment with unresolved image (auto: true)", auto: true, canTrigger: false, version: 0, expectedUpdate: false, }, { name: "initial deployment with unresolved image (auto: false)", auto: false, canTrigger: false, version: 0, expectedUpdate: false, }, { name: "initial deployment with resolved image (auto: true)", auto: true, canTrigger: true, version: 0, expectedUpdate: true, }, { name: "initial deployment with resolved image (auto: false)", auto: false, canTrigger: true, version: 0, expectedUpdate: true, }, } for _, test := range tests { updated := false fake := &testclient.Fake{} kFake := &ktestclient.Fake{} fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updated = true return true, nil, nil }) kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { // This will always return no template difference. We test template differences in TestHandle_changeWithTemplateDiff config := testapi.OkDeploymentConfig(0) deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) return true, deployment, nil }) controller := NewDeploymentTriggerController(fake, kFake, codec) config := testapi.OkDeploymentConfig(test.version) config.Namespace = kapi.NamespaceDefault ict := testapi.OkImageChangeTrigger() ict.ImageChangeParams.Automatic = test.auto if test.canTrigger { ict.ImageChangeParams.LastTriggeredImage = testapi.DockerImageReference } config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), ict} if err := controller.Handle(config); err != nil { t.Errorf("%s: unexpected error: %v", test.name, err) continue } if test.expectedUpdate != updated { t.Errorf("%s: expected update: %t, got update: %t", test.name, test.expectedUpdate, updated) } } }
// TestTriggers_MultipleICTs ensures that a deployment config with more than one ImageChange trigger // will start a new deployment iff all images are resolved. func TestTriggers_MultipleICTs(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("error starting master: %v", err) } openshiftClusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client: %v", err) } openshiftClusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client config: %v", err) } openshiftProjectAdminClient, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob") if err != nil { t.Fatalf("error creating project: %v", err) } imageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: deploytest.ImageStreamName}} secondImageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: "sample"}} config := deploytest.OkDeploymentConfig(0) config.Namespace = testutil.Namespace() firstTrigger := deploytest.OkImageChangeTrigger() secondTrigger := deploytest.OkImageChangeTrigger() secondTrigger.ImageChangeParams.ContainerNames = []string{"container2"} secondTrigger.ImageChangeParams.From.Name = imageapi.JoinImageStreamTag("sample", imageapi.DefaultImageTag) config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{firstTrigger, secondTrigger} configWatch, err := openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to deploymentconfigs %v", err) } defer configWatch.Stop() if imageStream, err = openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil { t.Fatalf("Couldn't create imagestream %q: %v", imageStream.Name, err) } if secondImageStream, err = openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Create(secondImageStream); err != nil { t.Fatalf("Couldn't create imagestream %q: %v", secondImageStream.Name, err) } imageWatch, err := openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to imagestreams: %v", err) } defer imageWatch.Stop() updatedImage := fmt.Sprintf("sha256:%s", deploytest.ImageID) updatedPullSpec := fmt.Sprintf("registry:8080/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, updatedImage) // Make a function which can create a new tag event for the image stream and // then wait for the stream status to be asynchronously updated. createTagEvent := func(name, tag, image, pullSpec string) { mapping := &imageapi.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{Name: name}, Tag: tag, Image: imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: image, }, DockerImageReference: pullSpec, }, } if err := openshiftProjectAdminClient.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil { t.Fatalf("unexpected error: %v", err) } t.Log("Waiting for image stream mapping to be reflected in the image stream status...") statusLoop: for { select { case event := <-imageWatch.ResultChan(): stream := event.Object.(*imageapi.ImageStream) if stream.Name != name { continue } if _, ok := stream.Status.Tags[tag]; ok { t.Logf("imagestream %q now has status with tags: %#v", stream.Name, stream.Status.Tags) break statusLoop } t.Logf("Still waiting for latest tag status on imagestream %q", stream.Name) } } } if config, err = openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil { t.Fatalf("Couldn't create deploymentconfig: %v", err) } timeout := time.After(30 * time.Second) t.Log("Should not trigger a new deployment in response to the first imagestream update") createTagEvent(imageStream.Name, imageapi.DefaultImageTag, updatedImage, updatedPullSpec) out: for { select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig := event.Object.(*deployapi.DeploymentConfig) if newConfig.Status.LatestVersion > 0 { t.Fatalf("unexpected latestVersion update: %#v", newConfig) } container := newConfig.Spec.Template.Spec.Containers[0] if e, a := updatedPullSpec, container.Image; e == a { break out } case <-timeout: t.Fatalf("timed out waiting for the first image update to happen") } } t.Log("Should trigger a new deployment in response to the second imagestream update") updatedImage = "sampleImage" updatedPullSpec = "samplePullSpec" createTagEvent(secondImageStream.Name, imageapi.DefaultImageTag, updatedImage, updatedPullSpec) for { inner: select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig := event.Object.(*deployapi.DeploymentConfig) switch { case newConfig.Status.LatestVersion == 0: t.Logf("Wating for latestVersion to update to 1") break inner case newConfig.Status.LatestVersion > 1: t.Fatalf("unexpected latestVersion %d for %#v", newConfig.Status.LatestVersion, newConfig) } container := newConfig.Spec.Template.Spec.Containers[1] if e, a := updatedPullSpec, container.Image; e != a { t.Fatalf("unexpected image for pod template container %q; expected %q, got %q", container.Name, e, a) } return case <-timeout: t.Fatalf("timed out waiting for the second image update to happen") } } }
func TestCanTrigger(t *testing.T) { tests := []struct { name string config *deployapi.DeploymentConfig decoded *deployapi.DeploymentConfig expected bool expectedCauses []deployapi.DeploymentCause }{ { name: "nil decoded config", config: testapi.OkDeploymentConfig(1), decoded: nil, expected: false, expectedCauses: nil, }, { name: "no trigger", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), }, Status: testapi.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), }, Status: testapi.OkDeploymentConfigStatus(1), }, expected: false, expectedCauses: nil, }, { name: "config change trigger only", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, expected: true, expectedCauses: testapi.OkConfigChangeDetails().Causes, }, { name: "config change trigger only [no change][initial]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, expected: true, expectedCauses: testapi.OkConfigChangeDetails().Causes, }, { name: "config change trigger only [no change]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, expected: false, expectedCauses: nil, }, { name: "image change trigger only [automatic=false]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), // Irrelevant change Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkNonAutomaticICT(), // Image still to be resolved but it's false anyway }, }, }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkNonAutomaticICT(), }, }, }, expected: false, expectedCauses: nil, }, { name: "image change trigger only [automatic=false][image triggered]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), // Image has been updated in the template but automatic=false Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkTriggeredNonAutomatic(), }, }, }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkNonAutomaticICT(), }, }, }, expected: false, expectedCauses: nil, }, { name: "image change trigger only [automatic=true]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkTriggeredImageChange(), }, }, }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkImageChangeTrigger(), }, }, }, expected: true, expectedCauses: testapi.OkImageChangeDetails().Causes, }, { name: "image change trigger only [automatic=true][no change]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkImageChangeTrigger(), }, }, }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkImageChangeTrigger(), }, }, }, expected: false, expectedCauses: nil, }, { name: "config change and image change trigger [automatic=false][initial][image resolved]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkTriggeredNonAutomatic(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkNonAutomaticICT(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, expected: true, expectedCauses: testapi.OkConfigChangeDetails().Causes, }, { name: "config change and image change trigger [automatic=false][initial]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkNonAutomaticICT(), // Image is not resolved yet }, }, Status: testapi.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkNonAutomaticICT(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, expected: false, expectedCauses: nil, }, { name: "config change and image change trigger [automatic=true][initial]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), // Pod template has changed but the image in the template is yet to be updated Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, expected: false, expectedCauses: nil, }, { name: "config change and image change trigger [automatic=true][initial][image triggered]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplateChanged(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkTriggeredImageChange(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(0), }, expected: true, expectedCauses: testapi.OkImageChangeDetails().Causes, }, { name: "config change and image change trigger [automatic=true][no change]", config: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, decoded: &deployapi.DeploymentConfig{ Spec: deployapi.DeploymentConfigSpec{ Template: testapi.OkPodTemplate(), Triggers: []deployapi.DeploymentTriggerPolicy{ testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger(), }, }, Status: testapi.OkDeploymentConfigStatus(1), }, expected: false, expectedCauses: nil, }, } for _, test := range tests { got, gotCauses := canTrigger(test.config, test.decoded) if test.expected != got { t.Errorf("%s: expected to trigger: %t, got: %t", test.name, test.expected, got) continue } if !kapi.Semantic.DeepEqual(test.expectedCauses, gotCauses) { t.Errorf("%s: expected causes:\n%#v\ngot:\n%#v", test.name, test.expectedCauses, gotCauses) } } }
// TestTriggers_imageChange_nonAutomatic ensures that a deployment config with a non-automatic // trigger will have its image updated without starting a new deployment. func TestTriggers_imageChange_nonAutomatic(t *testing.T) { testutil.RequireEtcd(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("error starting master: %v", err) } openshiftClusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client: %v", err) } openshiftClusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("error getting cluster admin client config: %v", err) } openshiftProjectAdminClient, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob") if err != nil { t.Fatalf("error creating project: %v", err) } imageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: deploytest.ImageStreamName}} if imageStream, err = openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil { t.Fatalf("Couldn't create imagestream: %v", err) } imageWatch, err := openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to imagestreams: %v", err) } defer imageWatch.Stop() image := fmt.Sprintf("sha256:%s", deploytest.ImageID) pullSpec := fmt.Sprintf("registry:5000/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, image) // Make a function which can create a new tag event for the image stream and // then wait for the stream status to be asynchronously updated. mapping := &imageapi.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{Name: imageStream.Name}, Tag: imageapi.DefaultImageTag, Image: imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: image, }, DockerImageReference: pullSpec, }, } updated := "" createTagEvent := func(mapping *imageapi.ImageStreamMapping) { if err := openshiftProjectAdminClient.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil { t.Fatalf("unexpected error: %v", err) } t.Log("Waiting for image stream mapping to be reflected in the image stream status...") for { select { case event := <-imageWatch.ResultChan(): stream := event.Object.(*imageapi.ImageStream) tagEventList, ok := stream.Status.Tags[imageapi.DefaultImageTag] if ok { if updated != tagEventList.Items[0].DockerImageReference { updated = tagEventList.Items[0].DockerImageReference return } } t.Logf("Still waiting for latest tag status update on imagestream %q", stream.Name) } } } configWatch, err := openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{}) if err != nil { t.Fatalf("Couldn't subscribe to deploymentconfigs: %v", err) } defer configWatch.Stop() config := deploytest.OkDeploymentConfig(0) config.Namespace = testutil.Namespace() config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deploytest.OkImageChangeTrigger()} config.Spec.Triggers[0].ImageChangeParams.Automatic = false if config, err = openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil { t.Fatalf("Couldn't create deploymentconfig: %v", err) } createTagEvent(mapping) var newConfig *deployapi.DeploymentConfig t.Log("Waiting for the initial deploymentconfig update in response to the imagestream update") timeout := time.After(30 * time.Second) // This is the initial deployment with automatic=false in its ICT - it should be updated to pullSpec out: for { select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig = event.Object.(*deployapi.DeploymentConfig) if newConfig.Status.LatestVersion > 0 { t.Fatalf("unexpected latestVersion update - the config has no config change trigger") } if e, a := updated, newConfig.Spec.Template.Spec.Containers[0].Image; e == a { break out } case <-timeout: t.Fatalf("timed out waiting for the image update to happen") } } t.Log("Waiting for the second imagestream update - it shouldn't update the deploymentconfig") // Subsequent updates to the image shouldn't update the pod template image mapping.Image.Name = "sha256:thisupdatedimageshouldneverlandinthepodtemplate" mapping.Image.DockerImageReference = fmt.Sprintf("registry:8080/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, mapping.Image.Name) createTagEvent(mapping) for { select { case event := <-configWatch.ResultChan(): if event.Type != watchapi.Modified { continue } newConfig = event.Object.(*deployapi.DeploymentConfig) if newConfig.Status.LatestVersion > 0 { t.Fatalf("unexpected latestVersion update - the config has no config change trigger") } if e, a := updated, newConfig.Spec.Template.Spec.Containers[0].Image; e == a { t.Fatalf("unexpected image update, expected initial image to be the same") } case <-timeout: return } } }
func TestGeneration(t *testing.T) { from := deploytest.OkDeploymentConfig(2) from.Spec.Strategy = deployapi.DeploymentStrategy{ Type: deployapi.DeploymentStrategyTypeCustom, } from.Spec.Triggers = append(from.Spec.Triggers, deployapi.DeploymentTriggerPolicy{Type: deployapi.DeploymentTriggerOnConfigChange}) from.Spec.Triggers = append(from.Spec.Triggers, deploytest.OkImageChangeTrigger()) from.Spec.Template.Spec.Containers[0].Name = "changed" from.Spec.Replicas = 5 from.Spec.Selector = map[string]string{ "new1": "new2", "new2": "new2", } to := deploytest.OkDeploymentConfig(1) // Generate a rollback for every combination of flag (using 1 bit per flag). rollbackSpecs := []*deployapi.DeploymentConfigRollbackSpec{} for i := 0; i < 15; i++ { spec := &deployapi.DeploymentConfigRollbackSpec{ From: kapi.ObjectReference{ Name: "deployment", Namespace: kapi.NamespaceDefault, }, IncludeTriggers: i&(1<<0) > 0, IncludeTemplate: i&(1<<1) > 0, IncludeReplicationMeta: i&(1<<2) > 0, IncludeStrategy: i&(1<<3) > 0, } rollbackSpecs = append(rollbackSpecs, spec) } generator := &RollbackGenerator{} // Test every combination. for _, spec := range rollbackSpecs { t.Logf("testing spec %#v", spec) if rollback, err := generator.GenerateRollback(from, to, spec); err != nil { t.Fatalf("Unexpected error: %v", err) } else { if hasStrategyDiff(from, rollback) && !spec.IncludeStrategy { t.Fatalf("unexpected strategy diff: from=%v, rollback=%v", from, rollback) } if hasTriggerDiff(from, rollback) && !spec.IncludeTriggers { t.Fatalf("unexpected trigger diff: from=%v, rollback=%v", from, rollback) } if hasPodTemplateDiff(from, rollback) && !spec.IncludeTemplate { t.Fatalf("unexpected template diff: from=%v, rollback=%v", from, rollback) } if hasReplicationMetaDiff(from, rollback) && !spec.IncludeReplicationMeta { t.Fatalf("unexpected replication meta diff: from=%v, rollback=%v", from, rollback) } for i, trigger := range rollback.Spec.Triggers { if trigger.Type == deployapi.DeploymentTriggerOnImageChange && trigger.ImageChangeParams.Automatic { t.Errorf("image change trigger %d should be disabled", i) } } } } }
// TestHandle_waitForImageController tests an initial deployment with unresolved image. The config // change controller should never increment latestVersion, thus trigger a deployment for this config. func TestHandle_waitForImageController(t *testing.T) { fake := &testclient.Fake{} kFake := &ktestclient.Fake{} fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { t.Fatalf("an update should never run before the template image is resolved") return true, nil, nil }) controller := &DeploymentConfigChangeController{ client: fake, kClient: kFake, decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, } config := testapi.OkDeploymentConfig(0) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger()} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } }