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
	}
}
예제 #2
0
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
	}
}
예제 #3
0
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))
	}
}
예제 #4
0
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")
	}
}
예제 #5
0
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))
	}
}
예제 #6
0
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)
	}
}
예제 #8
0
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)
	}
}
예제 #9
0
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))
	}
}
예제 #10
0
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")
	}
}
예제 #11
0
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
	}
}
예제 #12
0
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))
	}

}
예제 #13
0
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
		}
	}
}
예제 #14
0
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))
		}
	}
}
예제 #15
0
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()))
		}
	}
}
예제 #16
0
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))
		}
	}
}
예제 #17
0
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")
	}
}
예제 #18
0
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")
	}
}
예제 #19
0
// 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)
		}
	}
}
예제 #20
0
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
		}
	}
}
예제 #21
0
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))
		}
	}
}
예제 #22
0
// 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))
				}
			}
		}
	}
}
예제 #23
0
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)
				}
			}
		}
	}
}