o.Expect(err).NotTo(o.HaveOccurred()) // Create all fixtures oc.Run("create").Args("-f", exutil.FixturePath("testdata", "run_policy")).Execute() }) g.Describe("build configuration with Parallel build run policy", func() { g.It("runs the builds in parallel", func() { g.By("starting multiple builds") var ( startedBuilds []string counter int ) bcName := "sample-parallel-build" buildWatch, err := oc.Client().Builds(oc.Namespace()).Watch(kapi.ListOptions{ LabelSelector: buildutil.BuildConfigSelector(bcName), }) defer buildWatch.Stop() // Start first build stdout, _, err := exutil.StartBuild(oc, bcName, "-o=name") o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(strings.TrimSpace(stdout)).ShouldNot(o.HaveLen(0)) // extract build name from "build/buildName" resource id startedBuilds = append(startedBuilds, strings.TrimSpace(strings.Split(stdout, "/")[1])) // Wait for it to become running for { event := <-buildWatch.ResultChan() build := event.Object.(*buildapi.Build) o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse())
// Stop deletes the build configuration and all of the associated builds. func (reaper *BuildConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error { noBcFound := false noBuildFound := true // Add deletion pending annotation to the build config err := unversioned.RetryOnConflict(unversioned.DefaultRetry, func() error { bc, err := reaper.oc.BuildConfigs(namespace).Get(name) if kerrors.IsNotFound(err) { noBcFound = true return nil } if err != nil { return err } // Ignore if the annotation already exists if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" { return nil } // Set the annotation and update if err := util.AddObjectAnnotations(bc, map[string]string{buildapi.BuildConfigPausedAnnotation: "true"}); err != nil { return err } _, err = reaper.oc.BuildConfigs(namespace).Update(bc) return err }) if err != nil { return err } // Warn the user if the BuildConfig won't get deleted after this point. bcDeleted := false defer func() { if !bcDeleted { glog.Warningf("BuildConfig %s/%s will not be deleted because not all associated builds could be deleted. You can try re-running the command or removing them manually", namespace, name) } }() // Collect builds related to the config. builds, err := reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(name)}) if err != nil { return err } errList := []error{} for _, build := range builds.Items { noBuildFound = false if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil { glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err) if !kerrors.IsNotFound(err) { errList = append(errList, err) } } } // Collect deprecated builds related to the config. // TODO: Delete this block after BuildConfigLabelDeprecated is removed. builds, err = reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(name)}) if err != nil { return err } for _, build := range builds.Items { noBuildFound = false if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil { glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err) if !kerrors.IsNotFound(err) { errList = append(errList, err) } } } // Aggregate all errors if len(errList) > 0 { return kutilerrors.NewAggregate(errList) } // Finally we can delete the BuildConfig if !noBcFound { if err := reaper.oc.BuildConfigs(namespace).Delete(name); err != nil { return err } } bcDeleted = true if noBcFound && noBuildFound { return kerrors.NewNotFound("BuildConfig", name) } return nil }
func TestStop(t *testing.T) { notFound := func() runtime.Object { return &(kerrors.NewNotFound(buildapi.Resource("BuildConfig"), configName).(*kerrors.StatusError).ErrStatus) } tests := map[string]struct { oc *testclient.Fake expected []ktestclient.Action err bool }{ "simple stop": { oc: newBuildListFake(makeBuildConfig(0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(0, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "multiple builds": { oc: newBuildListFake(makeBuildConfig(4, false), makeBuildList(4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(4, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "no config, some builds": { oc: newBuildListFake(makeBuildList(2)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("builds", "default", "build-2"), }, err: false, }, "no config, no builds": { oc: testclient.NewSimpleFake(notFound()), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), }, err: true, }, "config, no builds": { oc: testclient.NewSimpleFake(makeBuildConfig(0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(0, true)), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, } for testName, test := range tests { reaper := &BuildConfigReaper{oc: test.oc, pollInterval: time.Millisecond, timeout: time.Millisecond} err := reaper.Stop("default", configName, 1*time.Second, nil) if !test.err && err != nil { t.Errorf("%s: unexpected error: %v", testName, err) } if test.err && err == nil { t.Errorf("%s: expected an error", testName) } if len(test.oc.Actions()) != len(test.expected) { t.Fatalf("%s: unexpected actions: %v, expected %v", testName, test.oc.Actions(), test.expected) } for j, actualAction := range test.oc.Actions() { if !actionsAreEqual(actualAction, test.expected[j]) { t.Errorf("%s: unexpected action: %v, expected %v", testName, actualAction, test.expected[j]) } } } }
// Stop deletes the build configuration and all of the associated builds. func (reaper *BuildConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error { _, err := reaper.oc.BuildConfigs(namespace).Get(name) if err != nil { return err } var bcPotentialBuilds []buildapi.Build // Collect builds related to the config. builds, err := reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(name)}) if err != nil { return err } bcPotentialBuilds = append(bcPotentialBuilds, builds.Items...) // Collect deprecated builds related to the config. // TODO: Delete this block after BuildConfigLabelDeprecated is removed. builds, err = reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(name)}) if err != nil { return err } bcPotentialBuilds = append(bcPotentialBuilds, builds.Items...) // A map of builds associated with this build configuration bcBuilds := make(map[ktypes.UID]buildapi.Build) // Because of name length limits in the BuildConfigSelector, annotations are used to ensure // reliable selection of associated builds. for _, build := range bcPotentialBuilds { if build.Annotations != nil { if bcName, ok := build.Annotations[buildapi.BuildConfigAnnotation]; ok { // The annotation, if present, has the full build config name. if bcName != name { // If the name does not match exactly, the build is not truly associated with the build configuration continue } } } // Note that if there is no annotation, this is a deprecated build spec // and we choose to include it in the deletion having matched only the BuildConfigSelectorDeprecated // Use a map to union the lists returned by the contemporary & deprecated build queries // (there will be overlap between the lists, and we only want to try to delete each build once) bcBuilds[build.UID] = build } // If there are builds associated with this build configuration, pause it before attempting the deletion if len(bcBuilds) > 0 { // Add paused annotation to the build config pending the deletion err = kclient.RetryOnConflict(kclient.DefaultRetry, func() error { bc, err := reaper.oc.BuildConfigs(namespace).Get(name) if err != nil { return err } // Ignore if the annotation already exists if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" { return nil } // Set the annotation and update if err := util.AddObjectAnnotations(bc, map[string]string{buildapi.BuildConfigPausedAnnotation: "true"}); err != nil { return err } _, err = reaper.oc.BuildConfigs(namespace).Update(bc) return err }) if err != nil { return err } } // Warn the user if the BuildConfig won't get deleted after this point. bcDeleted := false defer func() { if !bcDeleted { glog.Warningf("BuildConfig %s/%s will not be deleted because not all associated builds could be deleted. You can try re-running the command or removing them manually", namespace, name) } }() // For the benefit of test cases, sort the UIDs so that the deletion order is deterministic buildUIDs := make([]string, 0, len(bcBuilds)) for buildUID := range bcBuilds { buildUIDs = append(buildUIDs, string(buildUID)) } sort.Strings(buildUIDs) errList := []error{} for _, buildUID := range buildUIDs { build := bcBuilds[ktypes.UID(buildUID)] if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil { glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err) if !kerrors.IsNotFound(err) { errList = append(errList, err) } } } // Aggregate all errors if len(errList) > 0 { return kutilerrors.NewAggregate(errList) } if err := reaper.oc.BuildConfigs(namespace).Delete(name); err != nil { return err } bcDeleted = true return nil }
func TestStop(t *testing.T) { notFound := func() runtime.Object { return &(kerrors.NewNotFound(buildapi.Resource("BuildConfig"), configName).ErrStatus) } tests := map[string]struct { targetBC string oc *testclient.Fake expected []ktestclient.Action err bool }{ "simple stop": { targetBC: configName, oc: newBuildListFake(makeBuildConfig(configName, 0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), // Since there are no builds associated with this build config, do not expect an update ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "multiple builds": { targetBC: configName, oc: newBuildListFake(makeBuildConfig(configName, 4, false), makeBuildList(configName, 4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewGetAction("buildconfigs", "default", configName), // Second GET to enable conflict retry logic ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(configName, 4, true)), // Because this bc has builds, it is paused ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, "long name builds": { targetBC: longConfigNameA, oc: newBuildListFake(makeBuildConfig(longConfigNameA, 4, false), makeBuildList(longConfigNameA, 4), makeBuildList(longConfigNameB, 4)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", longConfigNameA), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(longConfigNameA)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(longConfigNameA)}), ktestclient.NewGetAction("buildconfigs", "default", longConfigNameA), // Second GET to enable conflict retry logic ktestclient.NewUpdateAction("buildconfigs", "default", makeBuildConfig(longConfigNameA, 4, true)), // Because this bc has builds, it is paused ktestclient.NewDeleteAction("builds", "default", "build-1"), ktestclient.NewDeleteAction("builds", "default", "build-2"), ktestclient.NewDeleteAction("builds", "default", "build-3"), ktestclient.NewDeleteAction("builds", "default", "build-4"), ktestclient.NewDeleteAction("buildconfigs", "default", longConfigNameA), }, err: false, }, "no config, no or some builds": { targetBC: configName, oc: testclient.NewSimpleFake(notFound(), makeBuildList(configName, 2)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), }, err: true, }, "config, no builds": { targetBC: configName, oc: testclient.NewSimpleFake(makeBuildConfig(configName, 0, false)), expected: []ktestclient.Action{ ktestclient.NewGetAction("buildconfigs", "default", configName), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(configName)}), ktestclient.NewListAction("builds", "default", kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(configName)}), ktestclient.NewDeleteAction("buildconfigs", "default", configName), }, err: false, }, } for testName, test := range tests { reaper := &BuildConfigReaper{oc: test.oc, pollInterval: time.Millisecond, timeout: time.Millisecond} err := reaper.Stop("default", test.targetBC, 1*time.Second, nil) if !test.err && err != nil { t.Errorf("%s: unexpected error: %v", testName, err) } if test.err && err == nil { t.Errorf("%s: expected an error", testName) } if len(test.oc.Actions()) != len(test.expected) { t.Fatalf("%s: unexpected actions: %v, expected %v", testName, test.oc.Actions(), test.expected) } for j, actualAction := range test.oc.Actions() { if !actionsAreEqual(actualAction, test.expected[j]) { t.Errorf("%s: unexpected action: %v, expected %v", testName, actualAction, test.expected[j]) } } } }