func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { printer := spew.ConfigState{DisableMethods: true} original := item copied, err := api.Scheme.DeepCopy(item) if err != nil { panic(fmt.Sprintf("unable to copy: %v", err)) } item = copied.(runtime.Object) name := reflect.TypeOf(item).Elem().Name() data, err := runtime.Encode(codec, item) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v: not registered: %v (%s)", name, err, printer.Sprintf("%#v", item)) } else { t.Errorf("%v: %v (%s)", name, err, printer.Sprintf("%#v", item)) } return } if !api.Semantic.DeepEqual(original, item) { t.Errorf("0: %v: encode altered the object, diff: %v", name, diff.ObjectReflectDiff(original, item)) return } obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("0: %v: %v\nCodec: %#v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item)) panic("failed") } if !api.Semantic.DeepEqual(original, obj2) { t.Errorf("\n1: %v: diff: %v\nCodec: %#v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectReflectDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2)) return } obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("2: %v: %v", name, err) return } if !api.Semantic.DeepEqual(item, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %#v", name, diff.ObjectReflectDiff(item, obj3), codec) return } }
func roundTrip(t *testing.T, codec runtime.Codec, originalItem runtime.Object) { // Make a copy of the originalItem to give to conversion functions // This lets us know if conversion messed with the input object deepCopy, err := kapi.Scheme.DeepCopy(originalItem) if err != nil { t.Errorf("Could not copy object: %v", err) return } item := deepCopy.(runtime.Object) name := reflect.TypeOf(item).Elem().Name() data, err := runtime.Encode(codec, item) if err != nil { if runtime.IsNotRegisteredError(err) { t.Logf("%v skipped: not registered: %v", name, err) return } t.Errorf("%v: %v (%#v)", name, err, item) return } obj2, err := runtime.Decode(codec, data) if err != nil { t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), originalItem) return } if reflect.TypeOf(item) != reflect.TypeOf(obj2) { obj2conv := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := kapi.Scheme.Convert(obj2, obj2conv, nil); err != nil { t.Errorf("0X: no conversion from %v to %v: %v", reflect.TypeOf(item), reflect.TypeOf(obj2), err) return } obj2 = obj2conv } if !kapi.Semantic.DeepEqual(originalItem, obj2) { t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s", name, diff.ObjectReflectDiff(originalItem, obj2), codec, dataToString(data)) return } obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Errorf("2: %v: %v", name, err) return } if !kapi.Semantic.DeepEqual(originalItem, obj3) { t.Errorf("3: %v: diff: %v\nCodec: %v", name, diff.ObjectReflectDiff(originalItem, obj3), codec) return } }
func TestDoRequestFailed(t *testing.T) { status := &unversioned.Status{ Code: http.StatusNotFound, Status: unversioned.StatusFailure, Reason: unversioned.StatusReasonNotFound, Message: " \"\" not found", Details: &unversioned.StatusDetails{}, } expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) fakeHandler := utiltesting.FakeHandler{ StatusCode: 404, ResponseBody: string(expectedBody), T: t, } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() c, err := restClient(testServer) if err != nil { t.Fatalf("unexpected error: %v", err) } err = c.Get().Do().Error() if err == nil { t.Errorf("unexpected non-error") } ss, ok := err.(errors.APIStatus) if !ok { t.Errorf("unexpected error type %v", err) } actual := ss.Status() if !reflect.DeepEqual(status, &actual) { t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) } }
func TestAPIServerDefaults(t *testing.T) { defaults := apiserveroptions.NewServerRunOptions() // This is a snapshot of the default config // If the default changes (new fields are added, or default values change), we want to know // Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults expectedDefaults := &apiserveroptions.ServerRunOptions{ GenericServerRunOptions: &genericapiserveroptions.ServerRunOptions{ AnonymousAuth: true, BindAddress: net.ParseIP("0.0.0.0"), CertDirectory: "/var/run/kubernetes", InsecureBindAddress: net.ParseIP("127.0.0.1"), InsecurePort: 8080, LongRunningRequestRE: "(/|^)((watch|proxy)(/|$)|(logs?|portforward|exec|attach)/?$)", MaxRequestsInFlight: 400, SecurePort: 6443, EnableProfiling: true, EnableGarbageCollection: true, EnableWatchCache: true, MinRequestTimeout: 1800, ServiceNodePortRange: genericapiserveroptions.DefaultServiceNodePortRange, RuntimeConfig: utilconfig.ConfigurationMap{}, StorageVersions: registered.AllPreferredGroupVersions(), MasterCount: 1, DefaultStorageVersions: registered.AllPreferredGroupVersions(), StorageConfig: storagebackend.Config{ ServerList: nil, Prefix: "/registry", DeserializationCacheSize: 0, }, DefaultStorageMediaType: "application/json", AdmissionControl: "AlwaysAdmit", AuthorizationMode: "AlwaysAllow", DeleteCollectionWorkers: 1, MasterServiceNamespace: "default", AuthorizationWebhookCacheAuthorizedTTL: 5 * time.Minute, AuthorizationWebhookCacheUnauthorizedTTL: 30 * time.Second, }, EventTTL: 1 * time.Hour, KubeletConfig: kubeletclient.KubeletClientConfig{ Port: 10250, PreferredAddressTypes: []string{ string(apiv1.NodeHostName), string(apiv1.NodeInternalIP), string(apiv1.NodeExternalIP), string(apiv1.NodeLegacyHostIP), }, EnableHttps: true, HTTPTimeout: time.Duration(5) * time.Second, }, WebhookTokenAuthnCacheTTL: 2 * time.Minute, } if !reflect.DeepEqual(defaults, expectedDefaults) { t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults)) t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults") } }
func doDeepCopyTest(t *testing.T, kind unversioned.GroupVersionKind, f *fuzz.Fuzzer) { item, err := api.Scheme.New(kind) if err != nil { t.Fatalf("Could not create a %v: %s", kind, err) } f.Fuzz(item) itemCopy, err := api.Scheme.DeepCopy(item) if err != nil { t.Errorf("Could not deep copy a %v: %s", kind, err) return } if !reflect.DeepEqual(item, itemCopy) { t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy)) } }
func TestSetKubernetesDefaults(t *testing.T) { testCases := []struct { Config restclient.Config After restclient.Config Err bool }{ { restclient.Config{}, restclient.Config{ APIPath: "/api", ContentConfig: restclient.ContentConfig{ GroupVersion: testapi.Default.GroupVersion(), NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), }, QPS: 5, Burst: 10, }, false, }, // Add this test back when we fixed config and SetKubernetesDefaults // { // restclient.Config{ // GroupVersion: &unversioned.GroupVersion{Group: "not.a.group", Version: "not_an_api"}, // }, // restclient.Config{}, // true, // }, } for _, testCase := range testCases { val := &testCase.Config err := SetKubernetesDefaults(val) val.UserAgent = "" switch { case err == nil && testCase.Err: t.Errorf("expected error but was nil") continue case err != nil && !testCase.Err: t.Errorf("unexpected error %v", err) continue case err != nil: continue } if !reflect.DeepEqual(*val, testCase.After) { t.Errorf("unexpected result object: %s", diff.ObjectReflectDiff(*val, testCase.After)) } } }
func TestUnconditionalUpdate(t *testing.T) { storage := makeLocalTestStorage() ctx := kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "unittest"), &user.DefaultInfo{Name: "system:admin"}) realizedRoleObj, err := storage.Create(ctx, &authorizationapi.Role{ ObjectMeta: kapi.ObjectMeta{Name: "my-role"}, Rules: []authorizationapi.PolicyRule{ {Verbs: sets.NewString(authorizationapi.VerbAll)}, }, }) if err != nil { t.Fatalf("unexpected error: %v", err) } realizedRole := realizedRoleObj.(*authorizationapi.Role) role := &authorizationapi.Role{ ObjectMeta: realizedRole.ObjectMeta, Rules: []authorizationapi.PolicyRule{ {Verbs: sets.NewString("list", "update")}, }, } role.ResourceVersion = "" obj, created, err := storage.Update(ctx, role.Name, rest.DefaultUpdatedObjectInfo(role, kapi.Scheme)) if err != nil || created { t.Errorf("Unexpected error %v", err) } switch actual := obj.(type) { case *unversioned.Status: t.Errorf("Unexpected operation error: %v", obj) case *authorizationapi.Role: if realizedRole.ResourceVersion == actual.ResourceVersion { t.Errorf("Expected change to role binding. Expected: %s, Got: %s", realizedRole.ResourceVersion, actual.ResourceVersion) } role.ResourceVersion = actual.ResourceVersion if !reflect.DeepEqual(role, obj) { t.Errorf("Updated role does not match input role. %s", diff.ObjectReflectDiff(role, obj)) } default: t.Errorf("Unexpected result type: %v", obj) } }
func TestUnconditionalUpdate(t *testing.T) { ctx := kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "unittest"), &user.DefaultInfo{Name: "system:admin"}) storage := makeTestStorage() obj, err := storage.Create(ctx, &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{Name: "my-roleBinding"}, RoleRef: kapi.ObjectReference{Name: "admin"}, }) if err != nil { t.Errorf("unexpected error: %v", err) return } original := obj.(*authorizationapi.RoleBinding) roleBinding := &authorizationapi.RoleBinding{ ObjectMeta: original.ObjectMeta, RoleRef: kapi.ObjectReference{Name: "admin"}, Subjects: []kapi.ObjectReference{{Name: "bob", Kind: "User"}}, } roleBinding.ResourceVersion = "" obj, created, err := storage.Update(ctx, roleBinding.Name, rest.DefaultUpdatedObjectInfo(roleBinding, kapi.Scheme)) if err != nil || created { t.Errorf("Unexpected error %v", err) } switch actual := obj.(type) { case *unversioned.Status: t.Errorf("Unexpected operation error: %v", obj) case *authorizationapi.RoleBinding: if original.ResourceVersion == actual.ResourceVersion { t.Errorf("Expected change to role binding. Expected: %s, Got: %s", original.ResourceVersion, actual.ResourceVersion) } roleBinding.ResourceVersion = actual.ResourceVersion if !reflect.DeepEqual(roleBinding, obj) { t.Errorf("Updated roleBinding does not match input roleBinding. %s", diff.ObjectReflectDiff(roleBinding, obj)) } default: t.Errorf("Unexpected result type: %v", obj) } }
func TestDoRawRequestFailed(t *testing.T) { status := &unversioned.Status{ Code: http.StatusNotFound, Status: unversioned.StatusFailure, Reason: unversioned.StatusReasonNotFound, Message: "the server could not find the requested resource", Details: &unversioned.StatusDetails{ Causes: []unversioned.StatusCause{ {Type: unversioned.CauseTypeUnexpectedServerResponse, Message: "unknown"}, }, }, } expectedBody, _ := runtime.Encode(testapi.Default.Codec(), status) fakeHandler := utiltesting.FakeHandler{ StatusCode: 404, ResponseBody: string(expectedBody), T: t, } testServer := httptest.NewServer(&fakeHandler) defer testServer.Close() c, err := restClient(testServer) if err != nil { t.Fatalf("unexpected error: %v", err) } body, err := c.Get().Do().Raw() if err == nil || body == nil { t.Errorf("unexpected non-error: %#v", body) } ss, ok := err.(errors.APIStatus) if !ok { t.Errorf("unexpected error type %v", err) } actual := ss.Status() if !reflect.DeepEqual(status, &actual) { t.Errorf("Unexpected mis-match: %s", diff.ObjectReflectDiff(status, &actual)) } }
func TestSchedulerServerDefaults(t *testing.T) { defaults := scheduleroptions.NewSchedulerServer() // This is a snapshot of the default config // If the default changes (new fields are added, or default values change), we want to know // Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults expectedDefaults := &scheduleroptions.SchedulerServer{ KubeSchedulerConfiguration: componentconfig.KubeSchedulerConfiguration{ Port: 10251, // disabled Address: "0.0.0.0", AlgorithmProvider: "DefaultProvider", ContentType: "application/vnd.kubernetes.protobuf", KubeAPIQPS: 50, KubeAPIBurst: 100, SchedulerName: "default-scheduler", HardPodAffinitySymmetricWeight: 1, FailureDomains: "kubernetes.io/hostname,failure-domain.beta.kubernetes.io/zone,failure-domain.beta.kubernetes.io/region", LeaderElection: componentconfig.LeaderElectionConfiguration{ LeaderElect: true, LeaseDuration: unversioned.Duration{ Duration: 15 * time.Second, }, RenewDeadline: unversioned.Duration{ Duration: 10 * time.Second, }, RetryPeriod: unversioned.Duration{ Duration: 2 * time.Second, }, }, }, } if !reflect.DeepEqual(defaults, expectedDefaults) { t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults)) t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults") } }
func doDeepCopyTest(t *testing.T, kind unversioned.GroupVersionKind, f *fuzz.Fuzzer) { item, err := api.Scheme.New(kind) if err != nil { t.Fatalf("Could not create a %v: %s", kind, err) } f.Fuzz(item) itemCopy, err := api.Scheme.DeepCopy(item) if err != nil { t.Errorf("Could not deep copy a %v: %s", kind, err) return } if !reflect.DeepEqual(item, itemCopy) { t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, diff.ObjectReflectDiff(item, itemCopy)) } prefuzzData := &bytes.Buffer{} if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, prefuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } // Refuzz the copy, which should have no effect on the original f.Fuzz(itemCopy) postfuzzData := &bytes.Buffer{} if err := api.Codecs.LegacyCodec(kind.GroupVersion()).Encode(item, postfuzzData); err != nil { t.Errorf("Could not encode a %v: %s", kind, err) return } if bytes.Compare(prefuzzData.Bytes(), postfuzzData.Bytes()) != 0 { t.Log(diff.StringDiff(prefuzzData.String(), postfuzzData.String())) t.Errorf("Fuzzing copy modified original of %#v", kind) return } }
func TestProxyConfig(t *testing.T) { // This is a snapshot of the default config // If the default changes (new fields are added, or default values change), we want to know // Once we've reacted to the changes appropriately in buildKubeProxyConfig(), update this expected default to match the new upstream defaults oomScoreAdj := int32(-999) ipTablesMasqueratebit := int32(14) expectedDefaultConfig := &proxyoptions.ProxyServerConfig{ KubeProxyConfiguration: componentconfig.KubeProxyConfiguration{ BindAddress: "0.0.0.0", ClusterCIDR: "", HealthzPort: 10249, // disabled HealthzBindAddress: "127.0.0.1", // disabled OOMScoreAdj: &oomScoreAdj, // disabled ResourceContainer: "/kube-proxy", // disabled IPTablesSyncPeriod: unversioned.Duration{Duration: 30 * time.Second}, // from k8s.io/kubernetes/cmd/kube-proxy/app/options/options.go // defaults to 14. IPTablesMasqueradeBit: &ipTablesMasqueratebit, UDPIdleTimeout: unversioned.Duration{Duration: 250 * time.Millisecond}, ConntrackMaxPerCore: 32 * 1024, ConntrackTCPEstablishedTimeout: unversioned.Duration{Duration: 86400 * time.Second}, // 1 day (1/5 default) }, ConfigSyncPeriod: 15 * time.Minute, KubeAPIQPS: 5.0, KubeAPIBurst: 10, ContentType: "application/vnd.kubernetes.protobuf", } actualDefaultConfig := proxyoptions.NewProxyConfig() if !reflect.DeepEqual(expectedDefaultConfig, actualDefaultConfig) { t.Errorf("Default kube proxy config has changed. Adjust buildKubeProxyConfig() as needed to disable or make use of additions.") t.Logf("Difference %s", diff.ObjectReflectDiff(expectedDefaultConfig, actualDefaultConfig)) } }
func TestSpecificRoundTrips(t *testing.T) { boolFalse := false testCases := []struct { mediaType string in, out runtime.Object to, from unversioned.GroupVersion }{ { in: &configapi.MasterConfig{ AdmissionConfig: configapi.AdmissionConfig{ PluginConfig: map[string]configapi.AdmissionPluginConfig{ "test1": {Configuration: &configapi.LDAPSyncConfig{BindDN: "first"}}, "test2": {Configuration: &runtime.Unknown{Raw: []byte(`{"kind":"LDAPSyncConfig","apiVersion":"v1","bindDN":"second"}`)}}, "test3": {Configuration: &runtime.Unknown{Raw: []byte(`{"kind":"Unknown","apiVersion":"some/version"}`)}}, "test4": {Configuration: nil}, }, }, }, to: configapiv1.SchemeGroupVersion, out: &configapiv1.MasterConfig{ TypeMeta: unversioned.TypeMeta{Kind: "MasterConfig", APIVersion: "v1"}, AdmissionConfig: configapiv1.AdmissionConfig{ PluginConfig: map[string]configapiv1.AdmissionPluginConfig{ "test1": {Configuration: runtime.RawExtension{ Object: &configapiv1.LDAPSyncConfig{BindDN: "first"}, Raw: []byte(`{"kind":"LDAPSyncConfig","apiVersion":"v1","url":"","bindDN":"first","bindPassword":"","insecure":false,"ca":"","groupUIDNameMapping":null}`), }}, "test2": {Configuration: runtime.RawExtension{ Object: &configapiv1.LDAPSyncConfig{BindDN: "second"}, Raw: []byte(`{"kind":"LDAPSyncConfig","apiVersion":"v1","bindDN":"second"}`), }}, "test3": {Configuration: runtime.RawExtension{ Object: &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Unknown", APIVersion: "some/version"}, ContentType: "application/json", Raw: []byte(`{"kind":"Unknown","apiVersion":"some/version"}`)}, Raw: []byte(`{"kind":"Unknown","apiVersion":"some/version"}`), }}, "test4": {}, }, }, VolumeConfig: configapiv1.MasterVolumeConfig{DynamicProvisioningEnabled: &boolFalse}, }, from: configapiv1.SchemeGroupVersion, }, } f := serializer.NewCodecFactory(configapi.Scheme) for i, test := range testCases { var s runtime.Serializer if len(test.mediaType) != 0 { info, _ := f.SerializerForMediaType(test.mediaType, nil) s = info.Serializer } else { info, _ := f.SerializerForMediaType(f.SupportedMediaTypes()[0], nil) s = info.Serializer } data, err := runtime.Encode(f.LegacyCodec(test.to), test.in) if err != nil { t.Errorf("%d: unable to encode: %v", i, err) continue } result, err := runtime.Decode(f.DecoderToVersion(s, test.from), data) if err != nil { t.Errorf("%d: unable to decode: %v", i, err) continue } if !reflect.DeepEqual(test.out, result) { t.Errorf("%d: result did not match: %s", i, diff.ObjectReflectDiff(test.out, result)) continue } } }
func TestLimitVerifier(t *testing.T) { makeISForbiddenError := func(isName string, exceeded []kapi.ResourceName) error { if len(exceeded) == 0 { return nil } exceededStrings := []string{} for _, r := range exceeded { exceededStrings = append(exceededStrings, string(r)) } sort.Strings(exceededStrings) err := fmt.Errorf("exceeded %s", strings.Join(exceededStrings, ",")) return kapierrors.NewForbidden(api.Resource("ImageStream"), isName, err) } makeISEvaluator := func(maxImages, maxImageTags int64) func(string, *api.ImageStream) error { return func(ns string, is *api.ImageStream) error { limit := kapi.ResourceList{ api.ResourceImageStreamImages: *resource.NewQuantity(maxImages, resource.DecimalSI), api.ResourceImageStreamTags: *resource.NewQuantity(maxImageTags, resource.DecimalSI), } usage := admission.GetImageStreamUsage(is) if less, exceeded := kquota.LessThanOrEqual(usage, limit); !less { return makeISForbiddenError(is.Name, exceeded) } return nil } } tests := []struct { name string isEvaluator func(string, *api.ImageStream) error is api.ImageStream expected error }{ { name: "no limit", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, }, { name: "below limit", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(1, 0), }, { name: "exceed images", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, "oldest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "is", testutil.BaseImageWith2LayersDigest), Image: testutil.BaseImageWith2LayersDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(1, 0), expected: makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamImages}), }, { name: "exceed tags", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Spec: api.ImageStreamSpec{ Tags: map[string]api.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "DockerImage", Name: testutil.MakeDockerImageReference("test", "is", testutil.ChildImageWith2LayersDigest), }, }, }, }, }, isEvaluator: makeISEvaluator(0, 0), expected: makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamTags}), }, { name: "exceed images and tags", is: api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, Spec: api.ImageStreamSpec{ Tags: map[string]api.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "DockerImage", Name: testutil.MakeDockerImageReference("test", "other", testutil.BaseImageWith1LayerDigest), }, }, }, }, Status: api.ImageStreamStatus{ Tags: map[string]api.TagEventList{ "latest": { Items: []api.TagEvent{ { DockerImageReference: testutil.MakeDockerImageReference("test", "other", testutil.BaseImageWith1LayerDigest), Image: testutil.BaseImageWith1LayerDigest, }, }, }, }, }, }, isEvaluator: makeISEvaluator(0, 0), expected: makeISForbiddenError("is", []kapi.ResourceName{api.ResourceImageStreamImages, api.ResourceImageStreamTags}), }, } for _, tc := range tests { sar := &fakeSubjectAccessReviewRegistry{ allow: true, } tagVerifier := &TagVerifier{sar} s := &Strategy{ tagVerifier: tagVerifier, limitVerifier: &testutil.FakeImageStreamLimitVerifier{ ImageStreamEvaluator: tc.isEvaluator, }, defaultRegistry: &fakeDefaultRegistry{}, } ctx := kapi.WithUser(kapi.NewDefaultContext(), &fakeUser{}) err := s.BeforeCreate(ctx, &tc.is) if e, a := tc.expected, err; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected validation errors: %s", tc.name, diff.ObjectReflectDiff(e, a)) } // Update must fail the exact same way err = s.BeforeUpdate(ctx, &tc.is, &api.ImageStream{}) if e, a := tc.expected, err; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected validation errors: %s", tc.name, diff.ObjectReflectDiff(e, a)) } } }
func TestAdmissionResolveImages(t *testing.T) { image1 := &imageapi.Image{ ObjectMeta: kapi.ObjectMeta{Name: "sha256:0000000000000000000000000000000000000000000000000000000000000001"}, DockerImageReference: "integrated.registry/image1/image1:latest", } testCases := []struct { client *testclient.Fake attrs admission.Attributes admit bool expect runtime.Object }{ // fails resolution { client: testclient.NewSimpleFake(), attrs: admission.NewAttributesRecord( &kapi.Pod{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ {Image: "integrated.registry/test/mysql@sha256:good"}, }, InitContainers: []kapi.Container{ {Image: "myregistry.com/mysql/mysql:latest"}, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Pod"}, "default", "pod1", unversioned.GroupVersionResource{Version: "v1", Resource: "pods"}, "", admission.Create, nil, ), }, // resolves images in the integrated registry without altering their ref (avoids looking up the tag) { client: testclient.NewSimpleFake( image1, ), attrs: admission.NewAttributesRecord( &kapi.Pod{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ {Image: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Pod"}, "default", "pod1", unversioned.GroupVersionResource{Version: "v1", Resource: "pods"}, "", admission.Create, nil, ), admit: true, expect: &kapi.Pod{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ {Image: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, // resolves images in the integrated registry without altering their ref (avoids looking up the tag) { client: testclient.NewSimpleFake( image1, ), attrs: admission.NewAttributesRecord( &kapi.Pod{ Spec: kapi.PodSpec{ InitContainers: []kapi.Container{ {Image: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Pod"}, "default", "pod1", unversioned.GroupVersionResource{Version: "v1", Resource: "pods"}, "", admission.Create, nil, ), admit: true, expect: &kapi.Pod{ Spec: kapi.PodSpec{ InitContainers: []kapi.Container{ {Image: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, // resolves images in the integrated registry on builds without altering their ref (avoids looking up the tag) { client: testclient.NewSimpleFake( image1, ), attrs: admission.NewAttributesRecord( &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ SourceStrategy: &buildapi.SourceBuildStrategy{ From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Build"}, "default", "build1", unversioned.GroupVersionResource{Version: "v1", Resource: "builds"}, "", admission.Create, nil, ), admit: true, expect: &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ SourceStrategy: &buildapi.SourceBuildStrategy{ From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, }, }, // resolves builds with image stream tags, uses the image DockerImageReference with SHA set. { client: testclient.NewSimpleFake( &imageapi.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{Name: "test:other", Namespace: "default"}, Image: *image1, }, ), attrs: admission.NewAttributesRecord( &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ CustomStrategy: &buildapi.CustomBuildStrategy{ From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, }, }, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Build"}, "default", "build1", unversioned.GroupVersionResource{Version: "v1", Resource: "builds"}, "", admission.Create, nil, ), admit: true, expect: &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ CustomStrategy: &buildapi.CustomBuildStrategy{ From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/image1/image1@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, }, }, // resolves builds with image stream images { client: testclient.NewSimpleFake( &imageapi.ImageStreamImage{ ObjectMeta: kapi.ObjectMeta{Name: "test@sha256:0000000000000000000000000000000000000000000000000000000000000001", Namespace: "default"}, Image: *image1, }, ), attrs: admission.NewAttributesRecord( &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ DockerStrategy: &buildapi.DockerBuildStrategy{ From: &kapi.ObjectReference{Kind: "ImageStreamImage", Name: "test@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, }, }, }, }, nil, unversioned.GroupVersionKind{Version: "v1", Kind: "Build"}, "default", "build1", unversioned.GroupVersionResource{Version: "v1", Resource: "builds"}, "", admission.Create, nil, ), admit: true, expect: &buildapi.Build{ Spec: buildapi.BuildSpec{ CommonSpec: buildapi.CommonSpec{ Strategy: buildapi.BuildStrategy{ DockerStrategy: &buildapi.DockerBuildStrategy{ From: &kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/image1/image1:latest"}, }, }, }, }, }, }, } for i, test := range testCases { onResources := []unversioned.GroupResource{{Resource: "builds"}, {Resource: "pods"}} p, err := newImagePolicyPlugin(nil, &api.ImagePolicyConfig{ ResolveImages: api.RequiredRewrite, ExecutionRules: []api.ImageExecutionPolicyRule{ {ImageCondition: api.ImageCondition{OnResources: onResources}}, }, }) if err != nil { t.Fatal(err) } setDefaultCache(p) p.SetOpenshiftClient(test.client) p.SetDefaultRegistryFunc(func() (string, bool) { return "integrated.registry", true }) if err := p.Validate(); err != nil { t.Fatal(err) } if err := p.Admit(test.attrs); err != nil { if test.admit { t.Errorf("%d: should admit: %v", i, err) } continue } if !test.admit { t.Errorf("%d: should not admit", i) continue } if !reflect.DeepEqual(test.expect, test.attrs.GetObject()) { t.Errorf("%d: unequal: %s", i, diff.ObjectReflectDiff(test.expect, test.attrs.GetObject())) } } }
func TestTransformUnstructuredError(t *testing.T) { testCases := []struct { Req *http.Request Res *http.Response Resource string Name string ErrFn func(error) bool Transformed error }{ { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "POST", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsAlreadyExists, }, { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "PUT", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsConflict, }, { Resource: "foo", Name: "bar", Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsNotFound, }, { Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsBadRequest, }, { // status in response overrides transformed result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: unversioned.Status{Status: unversioned.StatusFailure, Code: http.StatusNotFound}, }, }, { // successful status is ignored Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, { // empty object does not change result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))}, ErrFn: apierrors.IsBadRequest, }, { // we default apiVersion for backwards compatibility with old clients // TODO: potentially remove in 1.7 Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: unversioned.Status{Status: unversioned.StatusFailure, Code: http.StatusNotFound}, }, }, { // we do not default kind Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, } for i, testCase := range testCases { r := &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), resourceName: testCase.Name, resource: testCase.Resource, } result := r.transformResponse(testCase.Res, testCase.Req) err := result.err if !testCase.ErrFn(err) { t.Errorf("unexpected error: %v", err) continue } if !apierrors.IsUnexpectedServerError(err) { t.Errorf("%d: unexpected error type: %v", i, err) } if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) { t.Errorf("unexpected error string: %s", err) } if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) { t.Errorf("unexpected error string: %s", err) } // verify Error() properly transforms the error transformed := result.Error() expect := testCase.Transformed if expect == nil { expect = err } if !reflect.DeepEqual(expect, transformed) { t.Errorf("%d: unexpected Error(): %s", i, diff.ObjectReflectDiff(expect, transformed)) } // verify result.Get properly transforms the error if _, err := result.Get(); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Get(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Into properly handles the error if err := result.Into(&api.Pod{}); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Into(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Raw leaves the error in the untransformed state if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) { t.Errorf("%d: unexpected error on Raw(): %s", i, diff.ObjectReflectDiff(expect, err)) } } }
func TestKubeletDefaults(t *testing.T) { defaults := kubeletoptions.NewKubeletServer() // This is a snapshot of the default config // If the default changes (new fields are added, or default values change), we want to know // Once we've reacted to the changes appropriately in BuildKubernetesNodeConfig(), update this expected default to match the new upstream defaults expectedDefaults := &kubeletoptions.KubeletServer{ AuthPath: util.NewStringFlag(""), KubeConfig: util.NewStringFlag("/var/lib/kubelet/kubeconfig"), KubeletConfiguration: componentconfig.KubeletConfiguration{ Address: "0.0.0.0", // overridden AllowPrivileged: false, // overridden CAdvisorPort: 4194, // disabled VolumeStatsAggPeriod: unversioned.Duration{Duration: time.Minute}, CertDirectory: "/var/run/kubernetes", CgroupRoot: "", ClusterDNS: "", // overridden ClusterDomain: "", // overridden ConfigureCBR0: false, ContainerRuntime: "docker", Containerized: false, // overridden based on OPENSHIFT_CONTAINERIZED CPUCFSQuota: true, // forced to true DockerExecHandlerName: "native", DockerEndpoint: "unix:///var/run/docker.sock", EventBurst: 10, EventRecordQPS: 5.0, EnableCustomMetrics: false, EnableDebuggingHandlers: true, EnableServer: true, EvictionHard: "memory.available<100Mi", FileCheckFrequency: unversioned.Duration{Duration: 20 * time.Second}, // overridden HealthzBindAddress: "127.0.0.1", // disabled HealthzPort: 10248, // disabled HostNetworkSources: []string{"*"}, // overridden HostPIDSources: []string{"*"}, // overridden HostIPCSources: []string{"*"}, // overridden HTTPCheckFrequency: unversioned.Duration{Duration: 20 * time.Second}, // disabled ImageMinimumGCAge: unversioned.Duration{Duration: 120 * time.Second}, ImageGCHighThresholdPercent: 90, ImageGCLowThresholdPercent: 80, IPTablesMasqueradeBit: 14, IPTablesDropBit: 15, LowDiskSpaceThresholdMB: 256, MakeIPTablesUtilChains: true, MasterServiceNamespace: "default", MaxContainerCount: -1, MaxPerPodContainerCount: 1, MaxOpenFiles: 1000000, MaxPods: 110, // overridden MinimumGCAge: unversioned.Duration{}, NetworkPluginDir: "", NetworkPluginName: "", // overridden NonMasqueradeCIDR: "10.0.0.0/8", VolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", NodeStatusUpdateFrequency: unversioned.Duration{Duration: 10 * time.Second}, NodeLabels: nil, OOMScoreAdj: -999, LockFilePath: "", PodInfraContainerImage: "gcr.io/google_containers/pause-amd64:3.0", // overridden Port: 10250, // overridden ReadOnlyPort: 10255, // disabled RegisterNode: true, RegisterSchedulable: true, RegistryBurst: 10, RegistryPullQPS: 5.0, ResolverConfig: kubetypes.ResolvConfDefault, KubeletCgroups: "", RktAPIEndpoint: rkt.DefaultRktAPIServiceEndpoint, RktPath: "", RktStage1Image: "", RootDirectory: "/var/lib/kubelet", // overridden RuntimeCgroups: "", SerializeImagePulls: true, StreamingConnectionIdleTimeout: unversioned.Duration{Duration: 4 * time.Hour}, SyncFrequency: unversioned.Duration{Duration: 1 * time.Minute}, SystemCgroups: "", TLSCertFile: "", // overridden to prevent cert generation TLSPrivateKeyFile: "", // overridden to prevent cert generation ReconcileCIDR: true, KubeAPIQPS: 5.0, KubeAPIBurst: 10, ExperimentalFlannelOverlay: false, OutOfDiskTransitionFrequency: unversioned.Duration{Duration: 5 * time.Minute}, HairpinMode: "promiscuous-bridge", BabysitDaemons: false, SeccompProfileRoot: "", CloudProvider: "auto-detect", RuntimeRequestTimeout: unversioned.Duration{Duration: 2 * time.Minute}, ContentType: "application/vnd.kubernetes.protobuf", EnableControllerAttachDetach: true, EvictionPressureTransitionPeriod: unversioned.Duration{Duration: 5 * time.Minute}, SystemReserved: utilconfig.ConfigurationMap{}, KubeReserved: utilconfig.ConfigurationMap{}, }, } if !reflect.DeepEqual(defaults, expectedDefaults) { t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults)) t.Errorf("Got different defaults than expected, adjust in BuildKubernetesNodeConfig and update expectedDefaults") } }
func TestCMServerDefaults(t *testing.T) { defaults := cmapp.NewCMServer() // This is a snapshot of the default config // If the default changes (new fields are added, or default values change), we want to know // Once we've reacted to the changes appropriately in BuildKubernetesMasterConfig(), update this expected default to match the new upstream defaults expectedDefaults := &cmapp.CMServer{ KubeControllerManagerConfiguration: componentconfig.KubeControllerManagerConfiguration{ Port: 10252, // disabled Address: "0.0.0.0", ConcurrentEndpointSyncs: 5, ConcurrentRCSyncs: 5, ConcurrentRSSyncs: 5, ConcurrentDaemonSetSyncs: 2, ConcurrentJobSyncs: 5, ConcurrentResourceQuotaSyncs: 5, ConcurrentDeploymentSyncs: 5, ConcurrentNamespaceSyncs: 2, ConcurrentSATokenSyncs: 5, LookupCacheSizeForRC: 4096, LookupCacheSizeForRS: 4096, LookupCacheSizeForDaemonSet: 1024, ConfigureCloudRoutes: true, NodeCIDRMaskSize: 24, ServiceSyncPeriod: unversioned.Duration{Duration: 5 * time.Minute}, NodeSyncPeriod: unversioned.Duration{Duration: 10 * time.Second}, ResourceQuotaSyncPeriod: unversioned.Duration{Duration: 5 * time.Minute}, NamespaceSyncPeriod: unversioned.Duration{Duration: 5 * time.Minute}, PVClaimBinderSyncPeriod: unversioned.Duration{Duration: 15 * time.Second}, HorizontalPodAutoscalerSyncPeriod: unversioned.Duration{Duration: 30 * time.Second}, DeploymentControllerSyncPeriod: unversioned.Duration{Duration: 30 * time.Second}, MinResyncPeriod: unversioned.Duration{Duration: 12 * time.Hour}, RegisterRetryCount: 10, PodEvictionTimeout: unversioned.Duration{Duration: 5 * time.Minute}, NodeMonitorGracePeriod: unversioned.Duration{Duration: 40 * time.Second}, NodeStartupGracePeriod: unversioned.Duration{Duration: 60 * time.Second}, NodeMonitorPeriod: unversioned.Duration{Duration: 5 * time.Second}, ClusterName: "kubernetes", TerminatedPodGCThreshold: 12500, VolumeConfiguration: componentconfig.VolumeConfiguration{ EnableDynamicProvisioning: true, EnableHostPathProvisioning: false, FlexVolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", PersistentVolumeRecyclerConfiguration: componentconfig.PersistentVolumeRecyclerConfiguration{ MaximumRetry: 3, MinimumTimeoutNFS: 300, IncrementTimeoutNFS: 30, MinimumTimeoutHostPath: 60, IncrementTimeoutHostPath: 30, }, }, ContentType: "application/vnd.kubernetes.protobuf", KubeAPIQPS: 20.0, KubeAPIBurst: 30, LeaderElection: componentconfig.LeaderElectionConfiguration{ LeaderElect: false, LeaseDuration: unversioned.Duration{Duration: 15 * time.Second}, RenewDeadline: unversioned.Duration{Duration: 10 * time.Second}, RetryPeriod: unversioned.Duration{Duration: 2 * time.Second}, }, }, } if !reflect.DeepEqual(defaults, expectedDefaults) { t.Logf("expected defaults, actual defaults: \n%s", diff.ObjectReflectDiff(expectedDefaults, defaults)) t.Errorf("Got different defaults than expected, adjust in BuildKubernetesMasterConfig and update expectedDefaults") } }
// TestHandle_changeWithTemplateDiff ensures that a pod template change to a // config with a config change trigger results in a version bump and cause // update. func TestHandle_changeWithTemplateDiff(t *testing.T) { scenarios := []struct { name string modify func(*deployapi.DeploymentConfig) changeExpected bool }{ { name: "container name change", changeExpected: true, modify: func(config *deployapi.DeploymentConfig) { config.Spec.Template.Spec.Containers[1].Name = "modified" }, }, { name: "template label change", changeExpected: true, modify: func(config *deployapi.DeploymentConfig) { config.Spec.Template.Labels["newkey"] = "value" }, }, { name: "no diff", changeExpected: false, modify: func(config *deployapi.DeploymentConfig) {}, }, } for _, s := range scenarios { t.Logf("running scenario: %s", s.name) fake := &testclient.Fake{} kFake := &ktestclient.Fake{} config := testapi.OkDeploymentConfig(1) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger()} versioned, err := kapi.Scheme.ConvertToVersion(config, deployv1.SchemeGroupVersion) if err != nil { t.Errorf("unexpected conversion error: %v", err) continue } defaulted, err := kapi.Scheme.ConvertToVersion(versioned, deployapi.SchemeGroupVersion) if err != nil { t.Errorf("unexpected conversion error: %v", err) continue } config = defaulted.(*deployapi.DeploymentConfig) deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion)) var updated *deployapi.DeploymentConfig fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updated = action.(ktestclient.UpdateAction).GetObject().(*deployapi.DeploymentConfig) return true, updated, nil }) kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { return true, deployment, nil }) controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec) s.modify(config) if err := controller.Handle(config); err != nil { t.Errorf("unexpected error: %v", err) continue } if !s.changeExpected { if updated != nil { t.Errorf("unexpected update to version %d: %s", updated.Status.LatestVersion, diff.ObjectReflectDiff(config, updated)) } continue } // changeExpected == true if updated == nil { t.Errorf("expected config to be updated") continue } if e, a := int64(2), updated.Status.LatestVersion; e != a { t.Errorf("expected update to latestversion=%d, got %d", e, a) continue } if updated.Status.Details == nil { t.Errorf("expected config change details to be set") } else if updated.Status.Details.Causes == nil { t.Errorf("expected config change causes to be set") } else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange { t.Errorf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type) } } }
func TestConvertToVersion(t *testing.T) { testCases := []struct { scheme *runtime.Scheme in runtime.Object gv runtime.GroupVersioner same bool out runtime.Object errFn func(error) bool }{ // errors if the type is not registered in the scheme { scheme: GetTestScheme(), in: &UnknownType{}, errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, }, // errors if the group versioner returns no target { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{}, errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // converts to internal { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersion{Version: "__internal"}, out: &TestType1{A: "test"}, }, // prefers the best match { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "__internal"}, {Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // unversioned type returned as-is { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // unversioned type returned when not included in the target types { scheme: GetTestScheme(), in: &UnversionedType{A: "test"}, gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, same: true, out: &UnversionedType{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, A: "test", }, }, // detected as already being in the target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // detected as already being in the first target version { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: schema.GroupVersions{{Version: "v1"}, {Version: "__internal"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test", }, }, // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), errFn: func(err error) bool { return err != nil && strings.Contains(err.Error(), "is not suitable for converting") }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy { scheme: GetTestScheme(), in: &ExternalTestType1{A: "test"}, gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), same: true, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, A: "test", }, }, // group versioner can choose a particular target kind for a given input when kind is the same across group versions { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, A: "test", }, }, // group versioner can choose a different kind { scheme: GetTestScheme(), in: &TestType1{A: "test"}, gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, out: &ExternalTestType1{ MyWeirdCustomEmbeddedVersionKindField: MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, A: "test", }, }, } for i, test := range testCases { original, _ := test.scheme.DeepCopy(test.in) out, err := test.scheme.ConvertToVersion(test.in, test.gv) switch { case test.errFn != nil: if !test.errFn(err) { t.Errorf("%d: unexpected error: %v", i, err) } continue case err != nil: t.Errorf("%d: unexpected error: %v", i, err) continue } if out == test.in { t.Errorf("%d: ConvertToVersion should always copy out: %#v", i, out) continue } if test.same { if !reflect.DeepEqual(original, test.in) { t.Errorf("%d: unexpected mutation of input: %s", i, diff.ObjectReflectDiff(original, test.in)) continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } if !reflect.DeepEqual(unsafe, test.out) { t.Errorf("%d: unexpected unsafe: %s", i, diff.ObjectReflectDiff(unsafe, test.out)) continue } if unsafe != test.in { t.Errorf("%d: UnsafeConvertToVersion should return same object: %#v", i, unsafe) continue } continue } if !reflect.DeepEqual(out, test.out) { t.Errorf("%d: unexpected out: %s", i, diff.ObjectReflectDiff(out, test.out)) continue } } }
func TestValidateImageStream(t *testing.T) { namespace63Char := strings.Repeat("a", 63) name191Char := strings.Repeat("b", 191) name192Char := "x" + name191Char missingNameErr := field.Required(field.NewPath("metadata", "name"), "") missingNameErr.Detail = "name or generateName is required" tests := map[string]struct { namespace string name string dockerImageRepository string specTags map[string]api.TagReference statusTags map[string]api.TagEventList expected field.ErrorList }{ "missing name": { namespace: "foo", name: "", expected: field.ErrorList{missingNameErr}, }, "no slash in Name": { namespace: "foo", name: "foo/bar", expected: field.ErrorList{ field.Invalid(field.NewPath("metadata", "name"), "foo/bar", `may not contain '/'`), }, }, "no percent in Name": { namespace: "foo", name: "foo%%bar", expected: field.ErrorList{ field.Invalid(field.NewPath("metadata", "name"), "foo%%bar", `may not contain '%'`), }, }, "other invalid name": { namespace: "foo", name: "foo bar", expected: field.ErrorList{ field.Invalid(field.NewPath("metadata", "name"), "foo bar", `must match "[a-z0-9]+(?:[._-][a-z0-9]+)*"`), }, }, "missing namespace": { namespace: "", name: "foo", expected: field.ErrorList{ field.Required(field.NewPath("metadata", "namespace"), ""), }, }, "invalid namespace": { namespace: "!$", name: "foo", expected: field.ErrorList{ field.Invalid(field.NewPath("metadata", "namespace"), "!$", `must match the regex [a-z0-9]([-a-z0-9]*[a-z0-9])? (e.g. 'my-name' or '123-abc')`), }, }, "invalid dockerImageRepository": { namespace: "namespace", name: "foo", dockerImageRepository: "a-|///bbb", expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "dockerImageRepository"), "a-|///bbb", "invalid reference format"), }, }, "invalid dockerImageRepository with tag": { namespace: "namespace", name: "foo", dockerImageRepository: "a/b:tag", expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "dockerImageRepository"), "a/b:tag", "the repository name may not contain a tag"), }, }, "invalid dockerImageRepository with ID": { namespace: "namespace", name: "foo", dockerImageRepository: "a/b@sha256:something", expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "dockerImageRepository"), "a/b@sha256:something", "invalid reference format"), }, }, "status tag missing dockerImageReference": { namespace: "namespace", name: "foo", statusTags: map[string]api.TagEventList{ "tag": { Items: []api.TagEvent{ {DockerImageReference: ""}, {DockerImageReference: "foo/bar:latest"}, {DockerImageReference: ""}, }, }, }, expected: field.ErrorList{ field.Required(field.NewPath("status", "tags").Key("tag").Child("items").Index(0).Child("dockerImageReference"), ""), field.Required(field.NewPath("status", "tags").Key("tag").Child("items").Index(2).Child("dockerImageReference"), ""), }, }, "referencePolicy.type must be valid": { namespace: "namespace", name: "foo", specTags: map[string]api.TagReference{ "tag": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "abc", }, ReferencePolicy: api.TagReferencePolicy{Type: api.TagReferencePolicyType("Other")}, }, }, expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "tags").Key("tag").Child("referencePolicy", "type"), api.TagReferencePolicyType("Other"), "valid values are \"Source\", \"Local\""), }, }, "ImageStreamTags can't be scheduled": { namespace: "namespace", name: "foo", specTags: map[string]api.TagReference{ "tag": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "abc", }, ImportPolicy: api.TagImportPolicy{Scheduled: true}, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, "other": { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "other:latest", }, ImportPolicy: api.TagImportPolicy{Scheduled: true}, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, }, expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "tags").Key("other").Child("importPolicy", "scheduled"), true, "only tags pointing to Docker repositories may be scheduled for background import"), }, }, "image IDs can't be scheduled": { namespace: "namespace", name: "foo", specTags: map[string]api.TagReference{ "badid": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "abc@badid", }, ImportPolicy: api.TagImportPolicy{Scheduled: true}, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, }, expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "tags").Key("badid").Child("from", "name"), "abc@badid", "invalid reference format"), }, }, "ImageStreamImages can't be scheduled": { namespace: "namespace", name: "foo", specTags: map[string]api.TagReference{ "otherimage": { From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Name: "other@latest", }, ImportPolicy: api.TagImportPolicy{Scheduled: true}, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, }, expected: field.ErrorList{ field.Invalid(field.NewPath("spec", "tags").Key("otherimage").Child("importPolicy", "scheduled"), true, "only tags pointing to Docker repositories may be scheduled for background import"), }, }, "valid": { namespace: "namespace", name: "foo", specTags: map[string]api.TagReference{ "tag": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "abc", }, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, "other": { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "other:latest", }, ReferencePolicy: api.TagReferencePolicy{Type: api.SourceTagReferencePolicy}, }, }, statusTags: map[string]api.TagEventList{ "tag": { Items: []api.TagEvent{ {DockerImageReference: "foo/bar:latest"}, }, }, }, expected: field.ErrorList{}, }, "shortest name components": { namespace: "f", name: "g", expected: field.ErrorList{}, }, "all possible characters used": { namespace: "abcdefghijklmnopqrstuvwxyz-1234567890", name: "abcdefghijklmnopqrstuvwxyz-1234567890.dot_underscore-dash", expected: field.ErrorList{}, }, "max name and namespace length met": { namespace: namespace63Char, name: name191Char, expected: field.ErrorList{}, }, "max name and namespace length exceeded": { namespace: namespace63Char, name: name192Char, expected: field.ErrorList{ field.Invalid(field.NewPath("metadata", "name"), name192Char, "'namespace/name' cannot be longer than 255 characters"), }, }, } for name, test := range tests { stream := api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: test.namespace, Name: test.name, }, Spec: api.ImageStreamSpec{ DockerImageRepository: test.dockerImageRepository, Tags: test.specTags, }, Status: api.ImageStreamStatus{ Tags: test.statusTags, }, } errs := ValidateImageStream(&stream) if e, a := test.expected, errs; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected errors: %s", name, diff.ObjectReflectDiff(e, a)) } } }
// TODO: add a reflexive test that verifies that all SetDefaults functions are registered func TestDefaulting(t *testing.T) { // these are the known types with defaulters - you must add to this list if you add a top level defaulter typesWithDefaulting := map[schema.GroupVersionKind]struct{}{ {Group: "", Version: "v1", Kind: "ConfigMap"}: {}, {Group: "", Version: "v1", Kind: "ConfigMapList"}: {}, {Group: "", Version: "v1", Kind: "Endpoints"}: {}, {Group: "", Version: "v1", Kind: "EndpointsList"}: {}, {Group: "", Version: "v1", Kind: "Namespace"}: {}, {Group: "", Version: "v1", Kind: "NamespaceList"}: {}, {Group: "", Version: "v1", Kind: "Node"}: {}, {Group: "", Version: "v1", Kind: "NodeList"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolume"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeList"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaim"}: {}, {Group: "", Version: "v1", Kind: "PersistentVolumeClaimList"}: {}, {Group: "", Version: "v1", Kind: "PodAttachOptions"}: {}, {Group: "", Version: "v1", Kind: "PodExecOptions"}: {}, {Group: "", Version: "v1", Kind: "Pod"}: {}, {Group: "", Version: "v1", Kind: "PodList"}: {}, {Group: "", Version: "v1", Kind: "PodTemplate"}: {}, {Group: "", Version: "v1", Kind: "PodTemplateList"}: {}, {Group: "", Version: "v1", Kind: "ReplicationController"}: {}, {Group: "", Version: "v1", Kind: "ReplicationControllerList"}: {}, {Group: "", Version: "v1", Kind: "Secret"}: {}, {Group: "", Version: "v1", Kind: "SecretList"}: {}, {Group: "", Version: "v1", Kind: "Service"}: {}, {Group: "", Version: "v1", Kind: "ServiceList"}: {}, {Group: "apps", Version: "v1beta1", Kind: "StatefulSet"}: {}, {Group: "apps", Version: "v1beta1", Kind: "StatefulSetList"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "batch", Version: "v1", Kind: "Job"}: {}, {Group: "batch", Version: "v1", Kind: "JobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "CronJob"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "CronJobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "Job"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "JobList"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "JobTemplate"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}: {}, {Group: "batch", Version: "v2alpha1", Kind: "ScheduledJobList"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeProxyConfiguration"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeSchedulerConfiguration"}: {}, {Group: "componentconfig", Version: "v1alpha1", Kind: "KubeletConfiguration"}: {}, {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "MasterConfiguration"}: {}, // This object contains only int fields which currently breaks the defaulting test because // it's pretty stupid. Once we add non integer fields, we should uncomment this. // {Group: "kubeadm.k8s.io", Version: "v1alpha1", Kind: "NodeConfiguration"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DaemonSetList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "Deployment"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "DeploymentList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "HorizontalPodAutoscaler"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "HorizontalPodAutoscalerList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "Job"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "JobList"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSet"}: {}, {Group: "extensions", Version: "v1beta1", Kind: "ReplicaSetList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "ClusterRoleBindingList"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBinding"}: {}, {Group: "rbac.authorization.k8s.io", Version: "v1alpha1", Kind: "RoleBindingList"}: {}, } f := fuzz.New().NilChance(.5).NumElements(1, 1).RandSource(rand.NewSource(1)) f.Funcs( func(s *runtime.RawExtension, c fuzz.Continue) {}, func(s *metav1.LabelSelector, c fuzz.Continue) { c.FuzzNoCustom(s) s.MatchExpressions = nil // need to fuzz this specially }, func(s *apiv1.ListOptions, c fuzz.Continue) { c.FuzzNoCustom(s) s.LabelSelector = "" // need to fuzz requirement strings specially s.FieldSelector = "" // need to fuzz requirement strings specially }, func(s *extensionsv1beta1.ScaleStatus, c fuzz.Continue) { c.FuzzNoCustom(s) s.TargetSelector = "" // need to fuzz requirement strings specially }, ) scheme := api.Scheme var testTypes orderedGroupVersionKinds for gvk := range scheme.AllKnownTypes() { if gvk.Version == runtime.APIVersionInternal { continue } testTypes = append(testTypes, gvk) } sort.Sort(testTypes) for _, gvk := range testTypes { _, expectedChanged := typesWithDefaulting[gvk] iter := 0 changedOnce := false for { if iter > *fuzzIters { if !expectedChanged || changedOnce { break } if iter > 200 { t.Errorf("expected %s to trigger defaulting due to fuzzing", gvk) break } // if we expected defaulting, continue looping until the fuzzer gives us one // at worst, we will timeout } iter++ src, err := scheme.New(gvk) if err != nil { t.Fatal(err) } f.Fuzz(src) src.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) original, err := scheme.DeepCopy(src) if err != nil { t.Fatal(err) } // get internal withDefaults, _ := scheme.DeepCopy(src) scheme.Default(withDefaults.(runtime.Object)) if !reflect.DeepEqual(original, withDefaults) { changedOnce = true if !expectedChanged { t.Errorf("{Group: \"%s\", Version: \"%s\", Kind: \"%s\"} did not expect defaults to be set - update expected or check defaulter registering: %s", gvk.Group, gvk.Version, gvk.Kind, diff.ObjectReflectDiff(original, withDefaults)) } } } } }
func TestStop(t *testing.T) { notfound := func() runtime.Object { return &(kerrors.NewNotFound(kapi.Resource("DeploymentConfig"), "config").ErrStatus) } pause := func(d *deployapi.DeploymentConfig) *deployapi.DeploymentConfig { d.Spec.Paused = true return d } fakeDC := map[string]*deployapi.DeploymentConfig{ "simple-stop": deploytest.OkDeploymentConfig(1), "legacy-simple-stop": deploytest.OkDeploymentConfig(1), "multi-stop": deploytest.OkDeploymentConfig(5), "legacy-multi-stop": deploytest.OkDeploymentConfig(5), "no-deployments": deploytest.OkDeploymentConfig(5), "legacy-no-deployments": deploytest.OkDeploymentConfig(5), } tests := []struct { testName string namespace string name string oc *testclient.Fake kc *ktestclient.Fake expected []ktestclient.Action kexpected []ktestclient.Action err bool }{ { testName: "simple stop", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["simple-stop"]), kc: ktestclient.NewSimpleFake(mkdeploymentlist(1)), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", pause(fakeDC["simple-stop"])), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{"openshift.io/deployment-config.name": "config"})}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-1"), }, err: false, }, { testName: "legacy simple stop", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["legacy-simple-stop"]), kc: ktestclient.NewSimpleFake(mkdeploymentlist(1)), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", nil), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{"openshift.io/deployment-config.name": "config"})}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-1"), }, err: false, }, { testName: "stop multiple controllers", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["multi-stop"]), kc: ktestclient.NewSimpleFake(mkdeploymentlist(1, 2, 3, 4, 5)), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", pause(fakeDC["multi-stop"])), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{"openshift.io/deployment-config.name": "config"})}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-1"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-2"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-3"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-4"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-5"), }, err: false, }, { testName: "legacy stop multiple controllers", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["legacy-multi-stop"]), kc: ktestclient.NewSimpleFake(mkdeploymentlist(1, 2, 3, 4, 5)), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", nil), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{"openshift.io/deployment-config.name": "config"})}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-1"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-2"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-2"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-3"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-3"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-4"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-4"), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-5"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-5"), }, err: false, }, { testName: "no config, some deployments", namespace: "default", name: "config", oc: testclient.NewSimpleFake(notfound()), kc: ktestclient.NewSimpleFake(mkdeploymentlist(1)), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{"openshift.io/deployment-config.name": "config"})}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewUpdateAction("replicationcontrollers", "default", nil), ktestclient.NewGetAction("replicationcontrollers", "default", "config-1"), ktestclient.NewDeleteAction("replicationcontrollers", "default", "config-1"), }, err: false, }, { testName: "no config, no deployments", namespace: "default", name: "config", oc: testclient.NewSimpleFake(notfound()), kc: ktestclient.NewSimpleFake(&kapi.ReplicationControllerList{}), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), }, err: true, }, { testName: "config, no deployments", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["no-deployments"]), kc: ktestclient.NewSimpleFake(&kapi.ReplicationControllerList{}), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", pause(fakeDC["no-deployments"])), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), }, err: false, }, { testName: "legacy config, no deployments", namespace: "default", name: "config", oc: testclient.NewSimpleFake(fakeDC["legacy-no-deployments"]), kc: ktestclient.NewSimpleFake(&kapi.ReplicationControllerList{}), expected: []ktestclient.Action{ ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewUpdateAction("deploymentconfigs", "default", nil), ktestclient.NewGetAction("deploymentconfigs", "default", "config"), ktestclient.NewDeleteAction("deploymentconfigs", "default", "config"), }, kexpected: []ktestclient.Action{ ktestclient.NewListAction("replicationcontrollers", "default", kapi.ListOptions{}), }, err: false, }, } for _, test := range tests { reaper := &DeploymentConfigReaper{oc: test.oc, kc: test.kc, pollInterval: time.Millisecond, timeout: time.Millisecond} err := reaper.Stop(test.namespace, test.name, 1*time.Second, nil) if !test.err && err != nil { t.Errorf("%s: unexpected error: %v", test.testName, err) } if test.err && err == nil { t.Errorf("%s: expected an error", test.testName) } if len(test.oc.Actions()) != len(test.expected) { t.Errorf("%s: unexpected actions: %s", test.testName, diff.ObjectReflectDiff(test.oc.Actions(), test.expected)) continue } for j, actualAction := range test.oc.Actions() { e, a := test.expected[j], actualAction switch a.(type) { case ktestclient.UpdateAction: if e.GetVerb() != a.GetVerb() || e.GetNamespace() != a.GetNamespace() || e.GetResource() != a.GetResource() || e.GetSubresource() != a.GetSubresource() { t.Errorf("%s: unexpected action[%d]: %s, expected %s", test.testName, j, a, e) } default: if !reflect.DeepEqual(actualAction, test.expected[j]) { t.Errorf("%s: unexpected action: %s", test.testName, diff.ObjectReflectDiff(actualAction, test.expected[j])) } } } if len(test.kc.Actions()) != len(test.kexpected) { t.Errorf("%s: unexpected actions: %s", test.testName, diff.ObjectReflectDiff(test.kc.Actions(), test.kexpected)) continue } for j, actualAction := range test.kc.Actions() { e, a := test.kexpected[j], actualAction if e.GetVerb() != a.GetVerb() || e.GetNamespace() != a.GetNamespace() || e.GetResource() != a.GetResource() || e.GetSubresource() != a.GetSubresource() { t.Errorf("%s: unexpected action[%d]: %s, expected %s", test.testName, j, a, e) } switch a.(type) { case ktestclient.GetAction, ktestclient.DeleteAction: if !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected action[%d]: %s, expected %s", test.testName, j, a, e) } } } } }