package reaper import ( "fmt" "reflect" "strings" "testing" "time" kapi "k8s.io/kubernetes/pkg/api" kerrors "k8s.io/kubernetes/pkg/api/errors" ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" ktypes "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/validation" buildapi "github.com/openshift/origin/pkg/build/api" _ "github.com/openshift/origin/pkg/build/api/install" buildutil "github.com/openshift/origin/pkg/build/util" "github.com/openshift/origin/pkg/client/testclient" ) var ( configName = strings.Repeat("a", validation.DNS1123LabelMaxLength) longConfigNameA = strings.Repeat("0", 250) + "a" longConfigNameB = strings.Repeat("0", 250) + "b" ) func makeBuildConfig(configName string, version int64, deleting bool) *buildapi.BuildConfig { ret := &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{ Name: configName, Namespace: "default", Annotations: make(map[string]string), }, Spec: buildapi.BuildConfigSpec{}, Status: buildapi.BuildConfigStatus{ LastVersion: version, }, } if deleting { ret.Annotations[buildapi.BuildConfigPausedAnnotation] = "true" } return ret } func makeBuild(configName string, version int) buildapi.Build { return buildapi.Build{ ObjectMeta: kapi.ObjectMeta{ Name: fmt.Sprintf("build-%d", version), UID: ktypes.UID(fmt.Sprintf("build-%d", version)), Namespace: "default", Labels: map[string]string{buildapi.BuildConfigLabel: buildapi.LabelValue(configName)}, Annotations: map[string]string{buildapi.BuildConfigAnnotation: configName}, }, } } func makeDeprecatedBuild(configName string, version int) buildapi.Build { return buildapi.Build{ ObjectMeta: kapi.ObjectMeta{ Name: fmt.Sprintf("build-%d", version), UID: ktypes.UID(fmt.Sprintf("build-%d", version)), Namespace: "default", Labels: map[string]string{buildapi.BuildConfigLabelDeprecated: buildapi.LabelValue(configName)}, Annotations: map[string]string{buildapi.BuildConfigAnnotation: configName}, }, } } func makeBuildList(configName string, version int) *buildapi.BuildList { if version%2 != 0 { panic("version needs be even") } list := &buildapi.BuildList{} for i := 1; i <= version; i += 2 { list.Items = append(list.Items, makeBuild(configName, i)) list.Items = append(list.Items, makeDeprecatedBuild(configName, i+1)) } return list } func newBuildListFake(objects ...runtime.Object) *testclient.Fake { fake := testclient.NewSimpleFake(objects...) fake.PrependReactor("list", "builds", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { selector := action.(ktestclient.ListAction).GetListRestrictions().Labels retList := &buildapi.BuildList{} for _, obj := range objects { list, ok := obj.(*buildapi.BuildList) if !ok { continue } for _, build := range list.Items { if selector.Matches(labels.Set(build.Labels)) { retList.Items = append(retList.Items, build) } } } return true, retList, nil }) return fake } func actionsAreEqual(a, b ktestclient.Action) bool { if reflect.DeepEqual(a, b) { return true } // If it's an update action, we will take a better look at the object if a.GetVerb() == "update" && b.GetVerb() == "update" && a.GetNamespace() == b.GetNamespace() && a.GetResource() == b.GetResource() && a.GetSubresource() == b.GetSubresource() { ret := reflect.DeepEqual(a.(ktestclient.UpdateAction).GetObject(), b.(ktestclient.UpdateAction).GetObject()) return ret } return false } 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]) } } } }