// TestGetNodeAddresses verifies that proper results are returned // when requesting node addresses. func TestGetNodeAddresses(t *testing.T) { master, etcdserver, _, assert := setUp(t) defer etcdserver.Terminate(t) // Fail case (no addresses associated with nodes) nodes, _ := master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) addrs, err := master.getNodeAddresses() assert.Error(err, "getNodeAddresses should have caused an error as there are no addresses.") assert.Equal([]string(nil), addrs) // Pass case with External type IP nodes, _ = master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) for index := range nodes.Items { nodes.Items[index].Status.Addresses = []api.NodeAddress{{Type: api.NodeExternalIP, Address: "127.0.0.1"}} } addrs, err = master.getNodeAddresses() assert.NoError(err, "getNodeAddresses should not have returned an error.") assert.Equal([]string{"127.0.0.1", "127.0.0.1"}, addrs) // Pass case with LegacyHost type IP nodes, _ = master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) for index := range nodes.Items { nodes.Items[index].Status.Addresses = []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "127.0.0.2"}} } addrs, err = master.getNodeAddresses() assert.NoError(err, "getNodeAddresses failback should not have returned an error.") assert.Equal([]string{"127.0.0.2", "127.0.0.2"}, addrs) }
func TestNoOpUpdates(t *testing.T) { server, registry := NewTestGenericStoreRegistry(t) defer server.Terminate(t) newPod := func() *api.Pod { return &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: api.NamespaceDefault, Name: "foo", Labels: map[string]string{"prepare_create": "true"}, }, Spec: api.PodSpec{NodeName: "machine"}, } } var err error var createResult runtime.Object if createResult, err = registry.Create(api.NewDefaultContext(), newPod()); err != nil { t.Fatalf("Unexpected error: %v", err) } createdPod, err := registry.Get(api.NewDefaultContext(), "foo") if err != nil { t.Fatalf("Unexpected error: %v", err) } var updateResult runtime.Object p := newPod() if updateResult, _, err = registry.Update(api.NewDefaultContext(), p.Name, rest.DefaultUpdatedObjectInfo(p, api.Scheme)); err != nil { t.Fatalf("Unexpected error: %v", err) } // Check whether we do not return empty result on no-op update. if !reflect.DeepEqual(createResult, updateResult) { t.Errorf("no-op update should return a correct value, got: %#v", updateResult) } updatedPod, err := registry.Get(api.NewDefaultContext(), "foo") if err != nil { t.Fatalf("Unexpected error: %v", err) } createdMeta, err := meta.Accessor(createdPod) if err != nil { t.Fatalf("Unexpected error: %v", err) } updatedMeta, err := meta.Accessor(updatedPod) if err != nil { t.Fatalf("Unexpected error: %v", err) } if createdMeta.GetResourceVersion() != updatedMeta.GetResourceVersion() { t.Errorf("no-op update should be ignored and not written to etcd") } }
// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update func TestValidNamespace(t *testing.T) { ctx := api.NewDefaultContext() namespace, _ := api.NamespaceFrom(ctx) resource := api.ReplicationController{} if !api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("expected success") } if namespace != resource.Namespace { t.Errorf("expected resource to have the default namespace assigned during validation") } resource = api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: "other"}} if api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("Expected error that resource and context errors do not match because resource has different namespace") } ctx = api.NewContext() if api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("Expected error that resource and context errors do not match since context has no namespace") } ctx = api.NewContext() ns := api.NamespaceValue(ctx) if ns != "" { t.Errorf("Expected the empty string") } }
func TestNamespaceStatusStrategy(t *testing.T) { ctx := api.NewDefaultContext() if StatusStrategy.NamespaceScoped() { t.Errorf("Namespaces should not be namespace scoped") } if StatusStrategy.AllowCreateOnUpdate() { t.Errorf("Namespaces should not allow create on update") } now := unversioned.Now() oldNamespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10", DeletionTimestamp: &now}, Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"kubernetes"}}, Status: api.NamespaceStatus{Phase: api.NamespaceActive}, } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "9", DeletionTimestamp: &now}, Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, } StatusStrategy.PrepareForUpdate(namespace, oldNamespace) if namespace.Status.Phase != api.NamespaceTerminating { t.Errorf("Namespace status updates should allow change of phase: %v", namespace.Status.Phase) } if len(namespace.Spec.Finalizers) != 1 || namespace.Spec.Finalizers[0] != api.FinalizerKubernetes { t.Errorf("PrepareForUpdate should have preserved old finalizers") } errs := StatusStrategy.ValidateUpdate(ctx, namespace, oldNamespace) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } if namespace.ResourceVersion != "9" { t.Errorf("Incoming resource version on update should not be mutated") } }
func TestNamespaceFinalizeStrategy(t *testing.T) { ctx := api.NewDefaultContext() if FinalizeStrategy.NamespaceScoped() { t.Errorf("Namespaces should not be namespace scoped") } if FinalizeStrategy.AllowCreateOnUpdate() { t.Errorf("Namespaces should not allow create on update") } oldNamespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"kubernetes", "example.com/org"}}, Status: api.NamespaceStatus{Phase: api.NamespaceActive}, } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "9"}, Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"example.com/foo"}}, Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, } FinalizeStrategy.PrepareForUpdate(namespace, oldNamespace) if namespace.Status.Phase != api.NamespaceActive { t.Errorf("finalize updates should not allow change of phase: %v", namespace.Status.Phase) } if len(namespace.Spec.Finalizers) != 1 || string(namespace.Spec.Finalizers[0]) != "example.com/foo" { t.Errorf("PrepareForUpdate should have modified finalizers") } errs := StatusStrategy.ValidateUpdate(ctx, namespace, oldNamespace) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } if namespace.ResourceVersion != "9" { t.Errorf("Incoming resource version on update should not be mutated") } }
func TestServiceStatusStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !StatusStrategy.NamespaceScoped() { t.Errorf("Service must be namespace scoped") } oldService := makeValidService() newService := makeValidService() oldService.ResourceVersion = "4" newService.ResourceVersion = "4" newService.Spec.SessionAffinity = "ClientIP" newService.Status = api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ Ingress: []api.LoadBalancerIngress{ {IP: "127.0.0.2"}, }, }, } StatusStrategy.PrepareForUpdate(&newService, &oldService) if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" { t.Errorf("Service status updates should allow change of status fields") } if newService.Spec.SessionAffinity != "None" { t.Errorf("PrepareForUpdate should have preserved old spec") } errs := StatusStrategy.ValidateUpdate(ctx, &newService, &oldService) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } }
func TestReplicaSetStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !Strategy.NamespaceScoped() { t.Errorf("ReplicaSet must be namespace scoped") } if Strategy.AllowCreateOnUpdate() { t.Errorf("ReplicaSet should not allow create on update") } validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, }, } rs := &extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 1, ObservedGeneration: int64(10), }, } Strategy.PrepareForCreate(rs) if rs.Status.Replicas != 0 { t.Error("ReplicaSet should not allow setting status.replicas on create") } if rs.Status.ObservedGeneration != int64(0) { t.Error("ReplicaSet should not allow setting status.observedGeneration on create") } errs := Strategy.Validate(ctx, rs) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } invalidRc := &extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, } Strategy.PrepareForUpdate(invalidRc, rs) errs = Strategy.ValidateUpdate(ctx, invalidRc, rs) if len(errs) == 0 { t.Errorf("Expected a validation error") } if invalidRc.ResourceVersion != "4" { t.Errorf("Incoming resource version on update should not be mutated") } }
func TestReplicaSetStatusStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !StatusStrategy.NamespaceScoped() { t.Errorf("ReplicaSet must be namespace scoped") } if StatusStrategy.AllowCreateOnUpdate() { t.Errorf("ReplicaSet should not allow create on update") } validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, }, } oldRS := &extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"}, Spec: extensions.ReplicaSetSpec{ Replicas: 3, Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 1, ObservedGeneration: int64(10), }, } newRS := &extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"}, Spec: extensions.ReplicaSetSpec{ Replicas: 1, Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 3, ObservedGeneration: int64(11), }, } StatusStrategy.PrepareForUpdate(newRS, oldRS) if newRS.Status.Replicas != 3 { t.Errorf("ReplicaSet status updates should allow change of replicas: %v", newRS.Status.Replicas) } if newRS.Spec.Replicas != 3 { t.Errorf("PrepareForUpdate should have preferred spec") } errs := StatusStrategy.ValidateUpdate(ctx, newRS, oldRS) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } }
func TestCreateSetsFields(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) ctx := api.NewDefaultContext() resourcequota := validNewResourceQuota() _, err := storage.Create(api.NewDefaultContext(), resourcequota) if err != nil { t.Fatalf("unexpected error: %v", err) } object, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } actual := object.(*api.ResourceQuota) if actual.Name != resourcequota.Name { t.Errorf("unexpected resourcequota: %#v", actual) } if len(actual.UID) == 0 { t.Errorf("expected resourcequota UID to be set: %#v", actual) } }
func TestGenerationNumber(t *testing.T) { storage, server := newStorage(t) defer server.Terminate(t) modifiedSno := *validNewReplicaSet() modifiedSno.Generation = 100 modifiedSno.Status.ObservedGeneration = 10 ctx := api.NewDefaultContext() rs, err := createReplicaSet(storage.ReplicaSet, modifiedSno, t) etcdRS, err := storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ := etcdRS.(*extensions.ReplicaSet) // Generation initialization if storedRS.Generation != 1 && storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation number %v, status generation %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } // Updates to spec should increment the generation number storedRS.Spec.Replicas += 1 storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS, api.Scheme)) if err != nil { t.Errorf("unexpected error: %v", err) } etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ = etcdRS.(*extensions.ReplicaSet) if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } // Updates to status should not increment either spec or status generation numbers storedRS.Status.Replicas += 1 storage.ReplicaSet.Update(ctx, storedRS.Name, rest.DefaultUpdatedObjectInfo(storedRS, api.Scheme)) if err != nil { t.Errorf("unexpected error: %v", err) } etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) if err != nil { t.Errorf("unexpected error: %v", err) } storedRS, _ = etcdRS.(*extensions.ReplicaSet) if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { t.Fatalf("Unexpected generation number, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) } }
func TestUpdateStatus(t *testing.T) { storage, status, server := newStorage(t) defer server.Terminate(t) ctx := api.NewDefaultContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) resourcequotaStart := validNewResourceQuota() err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0) if err != nil { t.Fatalf("Unexpected error: %v", err) } resourcequotaIn := &api.ResourceQuota{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: api.NamespaceDefault, }, Status: api.ResourceQuotaStatus{ Used: api.ResourceList{ api.ResourceCPU: resource.MustParse("1"), api.ResourceMemory: resource.MustParse("1Gi"), api.ResourcePods: resource.MustParse("1"), api.ResourceServices: resource.MustParse("1"), api.ResourceReplicationControllers: resource.MustParse("1"), api.ResourceQuotas: resource.MustParse("1"), }, Hard: api.ResourceList{ api.ResourceCPU: resource.MustParse("100"), api.ResourceMemory: resource.MustParse("4Gi"), api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("1"), }, }, } _, _, err = status.Update(ctx, resourcequotaIn.Name, rest.DefaultUpdatedObjectInfo(resourcequotaIn, api.Scheme)) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") rqOut := obj.(*api.ResourceQuota) // only compare the meaningful update b/c we can't compare due to metadata if !api.Semantic.DeepEqual(resourcequotaIn.Status, rqOut.Status) { t.Errorf("unexpected object: %s", diff.ObjectDiff(resourcequotaIn, rqOut)) } }
func TestJobStrategyWithGeneration(t *testing.T) { ctx := api.NewDefaultContext() theUID := types.UID("1a2b3c4d5e6f7g8h9i0k") validPodTemplateSpec := api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob2", Namespace: api.NamespaceDefault, UID: theUID, }, Spec: batch.JobSpec{ Selector: nil, Template: validPodTemplateSpec, }, } Strategy.PrepareForCreate(job) errs := Strategy.Validate(ctx, job) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } // Validate the stuff that validation should have validated. if job.Spec.Selector == nil { t.Errorf("Selector not generated") } expectedLabels := make(map[string]string) expectedLabels["controller-uid"] = string(theUID) if !reflect.DeepEqual(job.Spec.Selector.MatchLabels, expectedLabels) { t.Errorf("Expected label selector not generated") } if job.Spec.Template.ObjectMeta.Labels == nil { t.Errorf("Expected template labels not generated") } if v, ok := job.Spec.Template.ObjectMeta.Labels["job-name"]; !ok || v != "myjob2" { t.Errorf("Expected template labels not present") } if v, ok := job.Spec.Template.ObjectMeta.Labels["controller-uid"]; !ok || v != string(theUID) { t.Errorf("Expected template labels not present: ok: %v, v: %v", ok, v) } }
// TestNamespaceContext validates that a namespace can be get/set on a context object func TestNamespaceContext(t *testing.T) { ctx := api.NewDefaultContext() result, ok := api.NamespaceFrom(ctx) if !ok { t.Errorf("Error getting namespace") } if api.NamespaceDefault != result { t.Errorf("Expected: %v, Actual: %v", api.NamespaceDefault, result) } ctx = api.NewContext() result, ok = api.NamespaceFrom(ctx) if ok { t.Errorf("Should not be ok because there is no namespace on the context") } }
func TestUpdateStatus(t *testing.T) { storage, statusStorage, server := newStorage(t) defer server.Terminate(t) ctx := api.NewDefaultContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) pvcStart := validNewPersistentVolumeClaim("foo", api.NamespaceDefault) err := storage.Storage.Create(ctx, key, pvcStart, nil, 0) pvc := &api.PersistentVolumeClaim{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: api.NamespaceDefault, }, Spec: api.PersistentVolumeClaimSpec{ AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Resources: api.ResourceRequirements{ Requests: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), }, }, }, Status: api.PersistentVolumeClaimStatus{ Phase: api.ClaimBound, }, } _, _, err = statusStorage.Update(ctx, pvc.Name, rest.DefaultUpdatedObjectInfo(pvc, api.Scheme)) if err != nil { t.Fatalf("Unexpected error: %v", err) } obj, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } pvcOut := obj.(*api.PersistentVolumeClaim) // only compare relevant changes b/c of difference in metadata if !api.Semantic.DeepEqual(pvc.Status, pvcOut.Status) { t.Errorf("unexpected object: %s", diff.ObjectDiff(pvc.Status, pvcOut.Status)) } }
func TestPodLogValidates(t *testing.T) { etcdStorage, _ := registrytest.NewEtcdStorage(t, "") store := ®istry.Store{ Storage: etcdStorage, } logRest := &LogREST{Store: store, KubeletConn: nil} negativeOne := int64(-1) testCases := []*api.PodLogOptions{ {SinceSeconds: &negativeOne}, {TailLines: &negativeOne}, } for _, tc := range testCases { _, err := logRest.Get(api.NewDefaultContext(), "test", tc) if !errors.IsInvalid(err) { t.Fatalf("unexpected error: %v", err) } } }
func TestNamespaceStrategy(t *testing.T) { ctx := api.NewDefaultContext() if Strategy.NamespaceScoped() { t.Errorf("Namespaces should not be namespace scoped") } if Strategy.AllowCreateOnUpdate() { t.Errorf("Namespaces should not allow create on update") } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, } Strategy.PrepareForCreate(namespace) if namespace.Status.Phase != api.NamespaceActive { t.Errorf("Namespaces do not allow setting phase on create") } if len(namespace.Spec.Finalizers) != 1 || namespace.Spec.Finalizers[0] != api.FinalizerKubernetes { t.Errorf("Prepare For Create should have added kubernetes finalizer") } errs := Strategy.Validate(ctx, namespace) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } invalidNamespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, } // ensure we copy spec.finalizers from old to new Strategy.PrepareForUpdate(invalidNamespace, namespace) if len(invalidNamespace.Spec.Finalizers) != 1 || invalidNamespace.Spec.Finalizers[0] != api.FinalizerKubernetes { t.Errorf("PrepareForUpdate should have preserved old.spec.finalizers") } errs = Strategy.ValidateUpdate(ctx, invalidNamespace, namespace) if len(errs) == 0 { t.Errorf("Expected a validation error") } if invalidNamespace.ResourceVersion != "4" { t.Errorf("Incoming resource version on update should not be mutated") } }
func TestNetworkPolicyStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !Strategy.NamespaceScoped() { t.Errorf("NetworkPolicy must be namespace scoped") } if Strategy.AllowCreateOnUpdate() { t.Errorf("NetworkPolicy should not allow create on update") } validMatchLabels := map[string]string{"a": "b"} np := &extensions.NetworkPolicy{ ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.NetworkPolicySpec{ PodSelector: unversioned.LabelSelector{MatchLabels: validMatchLabels}, Ingress: []extensions.NetworkPolicyIngressRule{}, }, } Strategy.PrepareForCreate(np) errs := Strategy.Validate(ctx, np) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } invalidNp := &extensions.NetworkPolicy{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, } Strategy.PrepareForUpdate(invalidNp, np) errs = Strategy.ValidateUpdate(ctx, invalidNp, np) if len(errs) == 0 { t.Errorf("Expected a validation error") } if invalidNp.ResourceVersion != "4" { t.Errorf("Incoming resource version on update should not be mutated") } }
func TestJobStatusStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !StatusStrategy.NamespaceScoped() { t.Errorf("Job must be namespace scoped") } if StatusStrategy.AllowCreateOnUpdate() { t.Errorf("Job should not allow create on update") } validSelector := &unversioned.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } oldParallelism := int32(10) newParallelism := int32(11) oldJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, ResourceVersion: "10", }, Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Parallelism: &oldParallelism, }, Status: batch.JobStatus{ Active: 11, }, } newJob := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, ResourceVersion: "9", }, Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, Parallelism: &newParallelism, }, Status: batch.JobStatus{ Active: 12, }, } StatusStrategy.PrepareForUpdate(newJob, oldJob) if newJob.Status.Active != 12 { t.Errorf("Job status updates must allow changes to job status") } if *newJob.Spec.Parallelism != 10 { t.Errorf("Job status updates must now allow changes to job spec") } errs := StatusStrategy.ValidateUpdate(ctx, newJob, oldJob) if len(errs) != 0 { t.Errorf("Unexpected error %v", errs) } if newJob.ResourceVersion != "9" { t.Errorf("Incoming resource version on update should not be mutated") } }
func (tc *patchTestCase) Run(t *testing.T) { t.Logf("Starting test %s", tc.name) namespace := tc.startingPod.Namespace name := tc.startingPod.Name codec := testapi.Default.Codec() admit := tc.admit if admit == nil { admit = func(updatedObject runtime.Object, currentObject runtime.Object) error { return nil } } testPatcher := &testPatcher{} testPatcher.t = t testPatcher.startingPod = tc.startingPod testPatcher.updatePod = tc.updatePod ctx := api.NewDefaultContext() ctx = api.WithNamespace(ctx, namespace) namer := &testNamer{namespace, name} copier := runtime.ObjectCopier(api.Scheme) resource := unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} versionedObj, err := api.Scheme.ConvertToVersion(&api.Pod{}, unversioned.GroupVersion{Version: "v1"}) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } for _, patchType := range []api.PatchType{api.JSONPatchType, api.MergePatchType, api.StrategicMergePatchType} { // TODO SUPPORT THIS! if patchType == api.JSONPatchType { continue } t.Logf("Working with patchType %v", patchType) originalObjJS, err := runtime.Encode(codec, tc.startingPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } changedJS, err := runtime.Encode(codec, tc.changedPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } patch := []byte{} switch patchType { case api.JSONPatchType: continue case api.StrategicMergePatchType: patch, err = strategicpatch.CreateStrategicMergePatch(originalObjJS, changedJS, versionedObj) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } case api.MergePatchType: patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } } resultObj, err := patchResource(ctx, admit, 1*time.Second, versionedObj, testPatcher, name, patchType, patch, namer, copier, resource, codec) if len(tc.expectedError) != 0 { if err == nil || err.Error() != tc.expectedError { t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err) return } } else { if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } } if tc.expectedPod == nil { if resultObj != nil { t.Errorf("%s: unexpected result: %v", tc.name, resultObj) } return } resultPod := resultObj.(*api.Pod) // roundtrip to get defaulting expectedJS, err := runtime.Encode(codec, tc.expectedPod) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } expectedObj, err := runtime.Decode(codec, expectedJS) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) return } reallyExpectedPod := expectedObj.(*api.Pod) if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) { t.Errorf("%s mismatch: %v\n", tc.name, diff.ObjectGoPrintDiff(reallyExpectedPod, resultPod)) return } } }
func TestJobStrategy(t *testing.T) { ctx := api.NewDefaultContext() if !Strategy.NamespaceScoped() { t.Errorf("Job must be namespace scoped") } if Strategy.AllowCreateOnUpdate() { t.Errorf("Job should not allow create on update") } validSelector := &unversioned.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } validPodTemplateSpec := api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } job := &batch.Job{ ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, }, Spec: batch.JobSpec{ Selector: validSelector, Template: validPodTemplateSpec, ManualSelector: newBool(true), }, Status: batch.JobStatus{ Active: 11, }, } Strategy.PrepareForCreate(job) if job.Status.Active != 0 { t.Errorf("Job does not allow setting status on create") } errs := Strategy.Validate(ctx, job) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } parallelism := int32(10) updatedJob := &batch.Job{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, Spec: batch.JobSpec{ Parallelism: ¶llelism, }, Status: batch.JobStatus{ Active: 11, }, } // ensure we do not change status job.Status.Active = 10 Strategy.PrepareForUpdate(updatedJob, job) if updatedJob.Status.Active != 10 { t.Errorf("PrepareForUpdate should have preserved prior version status") } errs = Strategy.ValidateUpdate(ctx, updatedJob, job) if len(errs) == 0 { t.Errorf("Expected a validation error") } }
// TODO: This should be done on types that are not part of our API func TestBeforeUpdate(t *testing.T) { testCases := []struct { name string tweakSvc func(oldSvc, newSvc *api.Service) // given basic valid services, each test case can customize them expectErr bool }{ { name: "no change", tweakSvc: func(oldSvc, newSvc *api.Service) { // nothing }, expectErr: false, }, { name: "change port", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Spec.Ports[0].Port++ }, expectErr: false, }, { name: "bad namespace", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Namespace = "#$%%invalid" }, expectErr: true, }, { name: "change name", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Name += "2" }, expectErr: true, }, { name: "change ClusterIP", tweakSvc: func(oldSvc, newSvc *api.Service) { oldSvc.Spec.ClusterIP = "1.2.3.4" newSvc.Spec.ClusterIP = "4.3.2.1" }, expectErr: true, }, { name: "change selectpor", tweakSvc: func(oldSvc, newSvc *api.Service) { newSvc.Spec.Selector = map[string]string{"newkey": "newvalue"} }, expectErr: false, }, } for _, tc := range testCases { oldSvc := makeValidService() newSvc := makeValidService() tc.tweakSvc(&oldSvc, &newSvc) ctx := api.NewDefaultContext() err := rest.BeforeUpdate(Strategy, ctx, runtime.Object(&oldSvc), runtime.Object(&newSvc)) if tc.expectErr && err == nil { t.Errorf("unexpected non-error for %q", tc.name) } if !tc.expectErr && err != nil { t.Errorf("unexpected error for %q: %v", tc.name, err) } } }