diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index e8c902eb27e..7a7ab6bf860 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -319,6 +319,8 @@ func runSelfLinkTest(c *client.Client) { Selector: map[string]string{ "foo": "bar", }, + Protocol: "TCP", + SessionAffinity: "None", }, }, ).Do().Into(&svc) @@ -381,6 +383,8 @@ func runAtomicPutTest(c *client.Client) { Selector: map[string]string{ "foo": "bar", }, + Protocol: "TCP", + SessionAffinity: "None", }, }, ).Do().Into(&svc) @@ -521,8 +525,11 @@ func runServiceTest(client *client.Client) { Ports: []api.Port{ {ContainerPort: 1234}, }, + ImagePullPolicy: "PullIfNotPresent", }, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, Status: api.PodStatus{ PodIP: "1.2.3.4", @@ -541,7 +548,9 @@ func runServiceTest(client *client.Client) { Selector: map[string]string{ "name": "thisisalonglabel", }, - Port: 8080, + Port: 8080, + Protocol: "TCP", + SessionAffinity: "None", }, } _, err = client.Services(api.NamespaceDefault).Create(&svc1) @@ -558,7 +567,9 @@ func runServiceTest(client *client.Client) { Selector: map[string]string{ "name": "thisisalonglabel", }, - Port: 8080, + Port: 8080, + Protocol: "TCP", + SessionAffinity: "None", }, } _, err = client.Services(api.NamespaceDefault).Create(&svc2) diff --git a/pkg/api/conversion.go b/pkg/api/conversion.go index 2537718d1df..372adb76099 100644 --- a/pkg/api/conversion.go +++ b/pkg/api/conversion.go @@ -47,14 +47,6 @@ func init() { out.Spec.DNSPolicy = in.DNSPolicy out.Name = in.ID out.UID = in.UUID - // TODO(dchen1107): Move this conversion to pkg/api/v1beta[123]/conversion.go - // along with fixing #1502 - for i := range out.Spec.Containers { - ctr := &out.Spec.Containers[i] - if len(ctr.TerminationMessagePath) == 0 { - ctr.TerminationMessagePath = TerminationMessagePathDefault - } - } return nil }, func(in *BoundPod, out *ContainerManifest, s conversion.Scope) error { @@ -65,12 +57,6 @@ func init() { out.Version = "v1beta2" out.ID = in.Name out.UUID = in.UID - for i := range out.Containers { - ctr := &out.Containers[i] - if len(ctr.TerminationMessagePath) == 0 { - ctr.TerminationMessagePath = TerminationMessagePathDefault - } - } return nil }, diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 656dc0e5435..25caa9a5928 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -158,6 +158,10 @@ func TestEncode_Ptr(t *testing.T) { ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"name": "foo"}, }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } obj := runtime.Object(pod) data, err := latest.Codec.Encode(obj) @@ -169,7 +173,7 @@ func TestEncode_Ptr(t *testing.T) { t.Fatalf("Got wrong type") } if !api.Semantic.DeepEqual(obj2, pod) { - t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2) + t.Errorf("Expected:\n %#v,\n Got:\n %#v", pod, obj2) } } diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index a64b2a36ab5..deb75534ae9 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -32,6 +32,14 @@ import ( "speter.net/go/exp/math/dec/inf" ) +func fuzzOneOf(c fuzz.Continue, objs ...interface{}) { + // Use a new fuzzer which cannot populate nil to ensure one obj will be set. + // FIXME: would be nicer to use FuzzOnePtr() and reflect. + f := fuzz.New().NilChance(0).NumElements(1, 1) + i := c.RandUint64() % uint64(len(objs)) + f.Fuzz(objs[i]) +} + // FuzzerFor can randomly populate api objects that are destined for version. func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { f := fuzz.New().NilChance(.5).NumElements(1, 1) @@ -84,15 +92,18 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown} *j = statuses[c.Rand.Intn(len(statuses))] }, + func(j *api.PodTemplateSpec, c fuzz.Continue) { + // TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2 + // conversion compare converted object to nil via DeepEqual + j.ObjectMeta = api.ObjectMeta{} + c.Fuzz(&j.ObjectMeta) + j.ObjectMeta = api.ObjectMeta{Labels: j.ObjectMeta.Labels} + j.Spec = api.PodSpec{} + c.Fuzz(&j.Spec) + }, func(j *api.ReplicationControllerSpec, c fuzz.Continue) { - // TemplateRef must be nil for round trip + // TemplateRef is set to nil by omission; this is required for round trip c.Fuzz(&j.Template) - if j.Template == nil { - // TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2 - // conversion compare converted object to nil via DeepEqual - j.Template = &api.PodTemplateSpec{} - } - j.Template.ObjectMeta = api.ObjectMeta{Labels: j.Template.ObjectMeta.Labels} c.Fuzz(&j.Selector) j.Replicas = int(c.RandUint64()) }, @@ -160,6 +171,46 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent} *p = policies[c.Rand.Intn(len(policies))] }, + func(rp *api.RestartPolicy, c fuzz.Continue) { + // Exactly one of the fields should be set. + fuzzOneOf(c, &rp.Always, &rp.OnFailure, &rp.Never) + }, + func(vs *api.VolumeSource, c fuzz.Continue) { + // Exactly one of the fields should be set. + //FIXME: the fuzz can still end up nil. What if fuzz allowed me to say that? + fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo) + }, + func(d *api.DNSPolicy, c fuzz.Continue) { + policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault} + *d = policies[c.Rand.Intn(len(policies))] + }, + func(p *api.Protocol, c fuzz.Continue) { + protocols := []api.Protocol{api.ProtocolTCP, api.ProtocolUDP} + *p = protocols[c.Rand.Intn(len(protocols))] + }, + func(p *api.AffinityType, c fuzz.Continue) { + types := []api.AffinityType{api.AffinityTypeClientIP, api.AffinityTypeNone} + *p = types[c.Rand.Intn(len(types))] + }, + func(ct *api.Container, c fuzz.Continue) { + // This function exists soley to set TerminationMessagePath to a + // non-empty string. TODO: consider making TerminationMessagePath a + // new type to simplify fuzzing. + ct.TerminationMessagePath = api.TerminationMessagePathDefault + // Let fuzzer handle the rest of the fileds. + c.Fuzz(&ct.Name) + c.Fuzz(&ct.Image) + c.Fuzz(&ct.Command) + c.Fuzz(&ct.Ports) + c.Fuzz(&ct.WorkingDir) + c.Fuzz(&ct.Env) + c.Fuzz(&ct.VolumeMounts) + c.Fuzz(&ct.LivenessProbe) + c.Fuzz(&ct.Lifecycle) + c.Fuzz(&ct.ImagePullPolicy) + c.Fuzz(&ct.Privileged) + c.Fuzz(&ct.Capabilities) + }, ) return f } diff --git a/pkg/api/v1beta1/defaults.go b/pkg/api/v1beta1/defaults.go index 6c400dfd45f..743ebbf2b40 100644 --- a/pkg/api/v1beta1/defaults.go +++ b/pkg/api/v1beta1/defaults.go @@ -17,6 +17,8 @@ limitations under the License. package v1beta1 import ( + "strings" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -24,8 +26,8 @@ import ( func init() { api.Scheme.AddDefaultingFuncs( func(obj *Volume) { - if obj.Source == nil || util.AllPtrFieldsNil(obj.Source) { - obj.Source = &VolumeSource{ + if util.AllPtrFieldsNil(&obj.Source) { + obj.Source = VolumeSource{ EmptyDir: &EmptyDir{}, } } @@ -36,13 +38,18 @@ func init() { } }, func(obj *Container) { - // TODO: delete helper functions that touch this if obj.ImagePullPolicy == "" { - obj.ImagePullPolicy = PullIfNotPresent + // TODO(dchen1107): Move ParseImageName code to pkg/util + parts := strings.Split(obj.Image, ":") + // Check image tag + if parts[len(parts)-1] == "latest" { + obj.ImagePullPolicy = PullAlways + } else { + obj.ImagePullPolicy = PullIfNotPresent + } } if obj.TerminationMessagePath == "" { - // TODO: fix other code that sets this - obj.TerminationMessagePath = api.TerminationMessagePathDefault + obj.TerminationMessagePath = TerminationMessagePathDefault } }, func(obj *RestartPolicy) { @@ -54,8 +61,19 @@ func init() { if obj.Protocol == "" { obj.Protocol = ProtocolTCP } + if obj.SessionAffinity == "" { + obj.SessionAffinity = AffinityTypeNone + } + }, + func(obj *PodSpec) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = DNSClusterFirst + } + }, + func(obj *ContainerManifest) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = DNSClusterFirst + } }, ) } - -// TODO: remove redundant code in validation and conversion diff --git a/pkg/api/v1beta1/defaults_test.go b/pkg/api/v1beta1/defaults_test.go new file mode 100644 index 00000000000..26eae9dac67 --- /dev/null +++ b/pkg/api/v1beta1/defaults_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1_test + +import ( + "reflect" + "testing" + + current "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + data, err := current.Codec.Encode(obj) + if err != nil { + t.Errorf("%v\n %#v", err, obj) + return nil + } + obj2 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) + err = current.Codec.DecodeInto(data, obj2) + if err != nil { + t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) + return nil + } + return obj2 +} + +func TestSetDefaultService(t *testing.T) { + svc := ¤t.Service{} + obj2 := roundTrip(t, runtime.Object(svc)) + svc2 := obj2.(*current.Service) + if svc2.Protocol != current.ProtocolTCP { + t.Errorf("Expected default protocol :%s, got: %s", current.ProtocolTCP, svc2.Protocol) + } + if svc2.SessionAffinity != current.AffinityTypeNone { + t.Errorf("Expected default sesseion affinity type:%s, got: %s", current.AffinityTypeNone, svc2.SessionAffinity) + } +} + +func TestSetDefaulPodSpec(t *testing.T) { + bp := ¤t.BoundPod{} + obj2 := roundTrip(t, runtime.Object(bp)) + bp2 := obj2.(*current.BoundPod) + if bp2.Spec.DNSPolicy != current.DNSClusterFirst { + t.Errorf("Expected default dns policy :%s, got: %s", current.DNSClusterFirst, bp2.Spec.DNSPolicy) + } + policy := bp2.Spec.RestartPolicy + if policy.Never != nil || policy.OnFailure != nil || policy.Always == nil { + t.Errorf("Expected only policy.Aways is set, got: %s", policy) + } +} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 6eb8e11cdda..796085b00ee 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -71,6 +71,11 @@ type ContainerManifestList struct { Items []ContainerManifest `json:"items" description:"list of pod container manifests"` } +const ( + // TerminationMessagePathDefault means the default path to capture the application termination message running in a container + TerminationMessagePathDefault string = "/dev/termination-log" +) + // Volume represents a named volume in a pod that may be accessed by any containers in the pod. type Volume struct { // Required: This must be a DNS_LABEL. Each volume in a pod must have diff --git a/pkg/api/v1beta2/defaults.go b/pkg/api/v1beta2/defaults.go new file mode 100644 index 00000000000..0897186b855 --- /dev/null +++ b/pkg/api/v1beta2/defaults.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta2 + +import ( + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +func init() { + api.Scheme.AddDefaultingFuncs( + func(obj *Volume) { + if util.AllPtrFieldsNil(&obj.Source) { + obj.Source = VolumeSource{ + EmptyDir: &EmptyDir{}, + } + } + }, + func(obj *Port) { + if obj.Protocol == "" { + obj.Protocol = ProtocolTCP + } + }, + func(obj *Container) { + if obj.ImagePullPolicy == "" { + // TODO(dchen1107): Move ParseImageName code to pkg/util + parts := strings.Split(obj.Image, ":") + // Check image tag + if parts[len(parts)-1] == "latest" { + obj.ImagePullPolicy = PullAlways + } else { + obj.ImagePullPolicy = PullIfNotPresent + } + } + if obj.TerminationMessagePath == "" { + obj.TerminationMessagePath = TerminationMessagePathDefault + } + }, + func(obj *RestartPolicy) { + if util.AllPtrFieldsNil(obj) { + obj.Always = &RestartPolicyAlways{} + } + }, + func(obj *Service) { + if obj.Protocol == "" { + obj.Protocol = ProtocolTCP + } + if obj.SessionAffinity == "" { + obj.SessionAffinity = AffinityTypeNone + } + }, + func(obj *PodSpec) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = DNSClusterFirst + } + }, + func(obj *ContainerManifest) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = DNSClusterFirst + } + }, + ) +} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 4d5898b3247..cacbe07845e 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -252,6 +252,11 @@ type Container struct { Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container"` } +const ( + // TerminationMessagePathDefault means the default path to capture the application termination message running in a container + TerminationMessagePathDefault string = "/dev/termination-log" +) + // Handler defines a specific action that should be taken // TODO: pass structured data to these actions, and document that data here. type Handler struct { diff --git a/pkg/api/v1beta3/defaults.go b/pkg/api/v1beta3/defaults.go new file mode 100644 index 00000000000..2c908e6c1ee --- /dev/null +++ b/pkg/api/v1beta3/defaults.go @@ -0,0 +1,74 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta3 + +import ( + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +func init() { + api.Scheme.AddDefaultingFuncs( + func(obj *Volume) { + if util.AllPtrFieldsNil(&obj.Source) { + obj.Source = VolumeSource{ + EmptyDir: &EmptyDir{}, + } + } + }, + func(obj *Port) { + if obj.Protocol == "" { + obj.Protocol = ProtocolTCP + } + }, + func(obj *Container) { + if obj.ImagePullPolicy == "" { + // TODO(dchen1107): Move ParseImageName code to pkg/util + parts := strings.Split(obj.Image, ":") + // Check image tag + if parts[len(parts)-1] == "latest" { + obj.ImagePullPolicy = PullAlways + } else { + obj.ImagePullPolicy = PullIfNotPresent + } + } + if obj.TerminationMessagePath == "" { + obj.TerminationMessagePath = TerminationMessagePathDefault + } + }, + func(obj *RestartPolicy) { + if util.AllPtrFieldsNil(obj) { + obj.Always = &RestartPolicyAlways{} + } + }, + func(obj *Service) { + if obj.Spec.Protocol == "" { + obj.Spec.Protocol = ProtocolTCP + } + if obj.Spec.SessionAffinity == "" { + obj.Spec.SessionAffinity = AffinityTypeNone + } + }, + func(obj *PodSpec) { + if obj.DNSPolicy == "" { + obj.DNSPolicy = DNSClusterFirst + } + }, + ) +} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index c8918aa6f69..79f64f73426 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -341,6 +341,11 @@ type ResourceRequirementSpec struct { Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"` } +const ( + // TerminationMessagePathDefault means the default path to capture the application termination message running in a container + TerminationMessagePathDefault string = "/dev/termination-log" +) + // Container represents a single container that is expected to be run on the host. type Container struct { // Required: This must be a DNS_LABEL. Each container in a pod must diff --git a/pkg/api/validation/schema_test.go b/pkg/api/validation/schema_test.go index c8a2c9a72b4..3dc098db493 100644 --- a/pkg/api/validation/schema_test.go +++ b/pkg/api/validation/schema_test.go @@ -103,18 +103,17 @@ var invalidPod2 = `{ "manifest": { "version": "v1beta1", "id": "apache-php", - "containers": [ - { - "name": "apache-php", - "image": "php:5.6.2-apache", - "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], - "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] - } - ] + "containers": [{ + "name": "apache-php", + "image": "php:5.6.2-apache", + "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], + "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] + }] } }, "labels": { "name": "apache-php" }, "restartPolicy": {"always": {}}, + "dnsPolicy": "ClusterFirst", "volumes": [ "name": "shared-disk", "source": { @@ -134,18 +133,17 @@ var invalidPod3 = `{ "manifest": { "version": "v1beta1", "id": "apache-php", - "containers": [ - { - "name": "apache-php", - "image": "php:5.6.2-apache", - "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], - "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] - } - ] + "containers": [{ + "name": "apache-php", + "image": "php:5.6.2-apache", + "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], + "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] + }] } }, "labels": { "name": "apache-php" }, "restartPolicy": {"always": {}}, + "dnsPolicy": "ClusterFirst", "volumes": [ { "name": "shared-disk", diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 3387d34a12f..1a9c4e85532 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -189,8 +189,7 @@ func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ValidationError allErrs := errs.ValidationErrorList{} allNames := util.StringSet{} - for i := range volumes { - vol := &volumes[i] // so we can set default values + for i, vol := range volumes { el := validateSource(&vol.Source).Prefix("source") if len(vol.Name) == 0 { el = append(el, errs.NewFieldRequired("name", vol.Name)) @@ -227,10 +226,7 @@ func validateSource(source *api.VolumeSource) errs.ValidationErrorList { numVolumes++ allErrs = append(allErrs, validateGCEPersistentDisk(source.GCEPersistentDisk).Prefix("persistentDisk")...) } - if numVolumes == 0 { - // TODO: Enforce that a source is set once we deprecate the implied form. - source.EmptyDir = &api.EmptyDir{} - } else if numVolumes != 1 { + if numVolumes != 1 { allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required")) } return allErrs @@ -272,9 +268,8 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} allNames := util.StringSet{} - for i := range ports { + for i, port := range ports { pErrs := errs.ValidationErrorList{} - port := &ports[i] // so we can set default values if len(port.Name) > 0 { if len(port.Name) > 63 || !util.IsDNSLabel(port.Name) { pErrs = append(pErrs, errs.NewFieldInvalid("name", port.Name, "")) @@ -293,7 +288,7 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList { pErrs = append(pErrs, errs.NewFieldInvalid("hostPort", port.HostPort, "")) } if len(port.Protocol) == 0 { - port.Protocol = "TCP" + pErrs = append(pErrs, errs.NewFieldRequired("protocol", port.Protocol)) } else if !supportedPortProtocols.Has(strings.ToUpper(string(port.Protocol))) { pErrs = append(pErrs, errs.NewFieldNotSupported("protocol", port.Protocol)) } @@ -305,9 +300,8 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList { func validateEnv(vars []api.EnvVar) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - for i := range vars { + for i, ev := range vars { vErrs := errs.ValidationErrorList{} - ev := &vars[i] // so we can set default values if len(ev.Name) == 0 { vErrs = append(vErrs, errs.NewFieldRequired("name", ev.Name)) } @@ -322,9 +316,8 @@ func validateEnv(vars []api.EnvVar) errs.ValidationErrorList { func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - for i := range mounts { + for i, mnt := range mounts { mErrs := errs.ValidationErrorList{} - mnt := &mounts[i] // so we can set default values if len(mnt.Name) == 0 { mErrs = append(mErrs, errs.NewFieldRequired("name", mnt.Name)) } else if !volumes.Has(mnt.Name) { @@ -343,9 +336,8 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs func AccumulateUniquePorts(containers []api.Container, accumulator map[int]bool, extract func(*api.Port) int) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - for ci := range containers { + for ci, ctr := range containers { cErrs := errs.ValidationErrorList{} - ctr := &containers[ci] for pi := range ctr.Ports { port := extract(&ctr.Ports[pi]) if port == 0 { @@ -413,22 +405,14 @@ func validateLifecycle(lifecycle *api.Lifecycle) errs.ValidationErrorList { return allErrs } -// TODO(dchen1107): Move this along with other defaulting values -func validatePullPolicyWithDefault(ctr *api.Container) errs.ValidationErrorList { +func validatePullPolicy(ctr *api.Container) errs.ValidationErrorList { allErrors := errs.ValidationErrorList{} switch ctr.ImagePullPolicy { - case "": - // TODO(dchen1107): Move ParseImageName code to pkg/util - parts := strings.Split(ctr.Image, ":") - // Check image tag - if parts[len(parts)-1] == "latest" { - ctr.ImagePullPolicy = api.PullAlways - } else { - ctr.ImagePullPolicy = api.PullIfNotPresent - } case api.PullAlways, api.PullIfNotPresent, api.PullNever: break + case "": + allErrors = append(allErrors, errs.NewFieldRequired("", ctr.ImagePullPolicy)) default: allErrors = append(allErrors, errs.NewFieldNotSupported("", ctr.ImagePullPolicy)) } @@ -440,9 +424,8 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs allErrs := errs.ValidationErrorList{} allNames := util.StringSet{} - for i := range containers { + for i, ctr := range containers { cErrs := errs.ValidationErrorList{} - ctr := &containers[i] // so we can set default values capabilities := capabilities.Get() if len(ctr.Name) == 0 { cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name)) @@ -464,8 +447,8 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs cErrs = append(cErrs, validatePorts(ctr.Ports).Prefix("ports")...) cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...) cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...) - cErrs = append(cErrs, validatePullPolicyWithDefault(ctr).Prefix("pullPolicy")...) - cErrs = append(cErrs, validateResourceRequirements(ctr).Prefix("resources")...) + cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...) + cErrs = append(cErrs, validateResourceRequirements(&ctr).Prefix("resources")...) allErrs = append(allErrs, cErrs.PrefixIndex(i)...) } // Check for colliding ports across all containers. @@ -513,10 +496,7 @@ func validateRestartPolicy(restartPolicy *api.RestartPolicy) errs.ValidationErro if restartPolicy.Never != nil { numPolicies++ } - if numPolicies == 0 { - restartPolicy.Always = &api.RestartPolicyAlways{} - } - if numPolicies > 1 { + if numPolicies != 1 { allErrors = append(allErrors, errs.NewFieldInvalid("", restartPolicy, "only 1 policy is allowed")) } return allErrors @@ -525,11 +505,10 @@ func validateRestartPolicy(restartPolicy *api.RestartPolicy) errs.ValidationErro func validateDNSPolicy(dnsPolicy *api.DNSPolicy) errs.ValidationErrorList { allErrors := errs.ValidationErrorList{} switch *dnsPolicy { - case "": - // TODO: move this out to standard defaulting logic, when that is ready. - *dnsPolicy = api.DNSClusterFirst // Default value. case api.DNSClusterFirst, api.DNSDefault: break + case "": + allErrors = append(allErrors, errs.NewFieldRequired("", *dnsPolicy)) default: allErrors = append(allErrors, errs.NewFieldNotSupported("", dnsPolicy)) } @@ -598,7 +577,7 @@ func ValidateService(service *api.Service) errs.ValidationErrorList { allErrs = append(allErrs, errs.NewFieldInvalid("spec.port", service.Spec.Port, "")) } if len(service.Spec.Protocol) == 0 { - service.Spec.Protocol = "TCP" + allErrs = append(allErrs, errs.NewFieldRequired("spec.protocol", service.Spec.Protocol)) } else if !supportedPortProtocols.Has(strings.ToUpper(string(service.Spec.Protocol))) { allErrs = append(allErrs, errs.NewFieldNotSupported("spec.protocol", service.Spec.Protocol)) } @@ -608,7 +587,7 @@ func ValidateService(service *api.Service) errs.ValidationErrorList { } if service.Spec.SessionAffinity == "" { - service.Spec.SessionAffinity = api.AffinityTypeNone + allErrs = append(allErrs, errs.NewFieldRequired("spec.sessionAffinity", service.Spec.SessionAffinity)) } else if !supportedSessionAffinityType.Has(string(service.Spec.SessionAffinity)) { allErrs = append(allErrs, errs.NewFieldNotSupported("spec.sessionAffinity", service.Spec.SessionAffinity)) } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 210f4c72963..92ded41f7ad 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -137,7 +137,7 @@ func TestValidateAnnotations(t *testing.T) { func TestValidateVolumes(t *testing.T) { successCase := []api.Volume{ - {Name: "abc"}, + {Name: "abc", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path1"}}}, {Name: "123", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path2"}}}, {Name: "abc-123", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path3"}}}, {Name: "empty", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, @@ -151,16 +151,16 @@ func TestValidateVolumes(t *testing.T) { if len(names) != 6 || !names.HasAll("abc", "123", "abc-123", "empty", "gcepd", "gitrepo") { t.Errorf("wrong names result: %v", names) } - + emptyVS := api.VolumeSource{EmptyDir: &api.EmptyDir{}} errorCases := map[string]struct { V []api.Volume T errors.ValidationErrorType F string }{ - "zero-length name": {[]api.Volume{{Name: ""}}, errors.ValidationErrorTypeRequired, "[0].name"}, - "name > 63 characters": {[]api.Volume{{Name: strings.Repeat("a", 64)}}, errors.ValidationErrorTypeInvalid, "[0].name"}, - "name not a DNS label": {[]api.Volume{{Name: "a.b.c"}}, errors.ValidationErrorTypeInvalid, "[0].name"}, - "name not unique": {[]api.Volume{{Name: "abc"}, {Name: "abc"}}, errors.ValidationErrorTypeDuplicate, "[1].name"}, + "zero-length name": {[]api.Volume{{Name: "", Source: emptyVS}}, errors.ValidationErrorTypeRequired, "[0].name"}, + "name > 63 characters": {[]api.Volume{{Name: strings.Repeat("a", 64), Source: emptyVS}}, errors.ValidationErrorTypeInvalid, "[0].name"}, + "name not a DNS label": {[]api.Volume{{Name: "a.b.c", Source: emptyVS}}, errors.ValidationErrorTypeInvalid, "[0].name"}, + "name not unique": {[]api.Volume{{Name: "abc", Source: emptyVS}, {Name: "abc", Source: emptyVS}}, errors.ValidationErrorTypeDuplicate, "[1].name"}, } for k, v := range errorCases { _, errs := validateVolumes(v.V) @@ -182,42 +182,39 @@ func TestValidateVolumes(t *testing.T) { func TestValidatePorts(t *testing.T) { successCase := []api.Port{ {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, - {Name: "123", ContainerPort: 81, HostPort: 81}, {Name: "easy", ContainerPort: 82, Protocol: "TCP"}, {Name: "as", ContainerPort: 83, Protocol: "UDP"}, - {Name: "do-re-me", ContainerPort: 84}, + {Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"}, {Name: "baby-you-and-me", ContainerPort: 82, Protocol: "tcp"}, - {ContainerPort: 85}, + {ContainerPort: 85, Protocol: "TCP"}, } if errs := validatePorts(successCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) } nonCanonicalCase := []api.Port{ - {ContainerPort: 80}, + {ContainerPort: 80, Protocol: "TCP"}, } if errs := validatePorts(nonCanonicalCase); len(errs) != 0 { t.Errorf("expected success: %v", errs) } - if nonCanonicalCase[0].HostPort != 0 || nonCanonicalCase[0].Protocol != "TCP" { - t.Errorf("expected default values: %+v", nonCanonicalCase[0]) - } errorCases := map[string]struct { P []api.Port T errors.ValidationErrorType F string }{ - "name > 63 characters": {[]api.Port{{Name: strings.Repeat("a", 64), ContainerPort: 80}}, errors.ValidationErrorTypeInvalid, "[0].name"}, - "name not a DNS label": {[]api.Port{{Name: "a.b.c", ContainerPort: 80}}, errors.ValidationErrorTypeInvalid, "[0].name"}, + "name > 63 characters": {[]api.Port{{Name: strings.Repeat("a", 64), ContainerPort: 80, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].name"}, + "name not a DNS label": {[]api.Port{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].name"}, "name not unique": {[]api.Port{ - {Name: "abc", ContainerPort: 80}, - {Name: "abc", ContainerPort: 81}, + {Name: "abc", ContainerPort: 80, Protocol: "TCP"}, + {Name: "abc", ContainerPort: 81, Protocol: "TCP"}, }, errors.ValidationErrorTypeDuplicate, "[1].name"}, - "zero container port": {[]api.Port{{ContainerPort: 0}}, errors.ValidationErrorTypeRequired, "[0].containerPort"}, - "invalid container port": {[]api.Port{{ContainerPort: 65536}}, errors.ValidationErrorTypeInvalid, "[0].containerPort"}, - "invalid host port": {[]api.Port{{ContainerPort: 80, HostPort: 65536}}, errors.ValidationErrorTypeInvalid, "[0].hostPort"}, + "zero container port": {[]api.Port{{ContainerPort: 0, Protocol: "TCP"}}, errors.ValidationErrorTypeRequired, "[0].containerPort"}, + "invalid container port": {[]api.Port{{ContainerPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].containerPort"}, + "invalid host port": {[]api.Port{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].hostPort"}, "invalid protocol": {[]api.Port{{ContainerPort: 80, Protocol: "ICMP"}}, errors.ValidationErrorTypeNotSupported, "[0].protocol"}, + "protocol required": {[]api.Port{{Name: "abc", ContainerPort: 80}}, errors.ValidationErrorTypeRequired, "[0].protocol"}, //yjhong } for k, v := range errorCases { errs := validatePorts(v.P) @@ -311,14 +308,10 @@ func TestValidatePullPolicy(t *testing.T) { api.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"}, api.PullNever, }, - "DefaultToNotPresent": {api.Container{Name: "notPresent", Image: "image"}, api.PullIfNotPresent}, - "DefaultToNotPresent2": {api.Container{Name: "notPresent1", Image: "image:sometag"}, api.PullIfNotPresent}, - "DefaultToAlways1": {api.Container{Name: "always", Image: "image:latest"}, api.PullAlways}, - "DefaultToAlways2": {api.Container{Name: "always", Image: "foo.bar.com:5000/my/image:latest"}, api.PullAlways}, } for k, v := range testCases { ctr := &v.Container - errs := validatePullPolicyWithDefault(ctr) + errs := validatePullPolicy(ctr) if len(errs) != 0 { t.Errorf("case[%s] expected success, got %#v", k, errs) } @@ -343,9 +336,9 @@ func TestValidateContainers(t *testing.T) { }) successCase := []api.Container{ - {Name: "abc", Image: "image"}, - {Name: "123", Image: "image"}, - {Name: "abc-123", Image: "image"}, + {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}, + {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent"}, + {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent"}, { Name: "life-123", Image: "image", @@ -354,6 +347,7 @@ func TestValidateContainers(t *testing.T) { Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, }, }, + ImagePullPolicy: "IfNotPresent", }, { Name: "resources-test", @@ -365,8 +359,9 @@ func TestValidateContainers(t *testing.T) { api.ResourceName("my.org/resource"): resource.MustParse("10m"), }, }, + ImagePullPolicy: "IfNotPresent", }, - {Name: "abc-1234", Image: "image", Privileged: true}, + {Name: "abc-1234", Image: "image", Privileged: true, ImagePullPolicy: "IfNotPresent"}, } if errs := validateContainers(successCase, volumes); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -467,7 +462,6 @@ func TestValidateContainers(t *testing.T) { func TestValidateRestartPolicy(t *testing.T) { successCases := []api.RestartPolicy{ - {}, {Always: &api.RestartPolicyAlways{}}, {OnFailure: &api.RestartPolicyOnFailure{}}, {Never: &api.RestartPolicyNever{}}, @@ -479,6 +473,7 @@ func TestValidateRestartPolicy(t *testing.T) { } errorCases := []api.RestartPolicy{ + {}, {Always: &api.RestartPolicyAlways{}, Never: &api.RestartPolicyNever{}}, {Never: &api.RestartPolicyNever{}, OnFailure: &api.RestartPolicyOnFailure{}}, } @@ -487,19 +482,10 @@ func TestValidateRestartPolicy(t *testing.T) { t.Errorf("expected failure for %d", k) } } - - noPolicySpecified := api.RestartPolicy{} - errs := validateRestartPolicy(&noPolicySpecified) - if len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - if noPolicySpecified.Always == nil { - t.Errorf("expected Always policy specified") - } } func TestValidateDNSPolicy(t *testing.T) { - successCases := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault, api.DNSPolicy("")} + successCases := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault, api.DNSPolicy(api.DNSClusterFirst)} for _, policy := range successCases { if errs := validateDNSPolicy(&policy); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -516,9 +502,12 @@ func TestValidateDNSPolicy(t *testing.T) { func TestValidateManifest(t *testing.T) { successCases := []api.ContainerManifest{ - {Version: "v1beta1", ID: "abc"}, - {Version: "v1beta2", ID: "123"}, - {Version: "V1BETA1", ID: "abc.123.do-re-mi"}, + {Version: "v1beta1", ID: "abc", RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst}, + {Version: "v1beta2", ID: "123", RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst}, + {Version: "V1BETA1", ID: "abc.123.do-re-mi", + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, DNSPolicy: api.DNSClusterFirst}, { Version: "v1beta1", ID: "abc", @@ -537,9 +526,9 @@ func TestValidateManifest(t *testing.T) { }, }, Ports: []api.Port{ - {Name: "p1", ContainerPort: 80, HostPort: 8080}, - {Name: "p2", ContainerPort: 81}, - {ContainerPort: 82}, + {Name: "p1", ContainerPort: 80, HostPort: 8080, Protocol: "TCP"}, + {Name: "p2", ContainerPort: 81, Protocol: "TCP"}, + {ContainerPort: 82, Protocol: "TCP"}, }, Env: []api.EnvVar{ {Name: "ev1", Value: "val1"}, @@ -550,8 +539,11 @@ func TestValidateManifest(t *testing.T) { {Name: "vol1", MountPath: "/foo"}, {Name: "vol1", MountPath: "/bar"}, }, + ImagePullPolicy: "IfNotPresent", }, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, } for _, manifest := range successCases { @@ -583,22 +575,23 @@ func TestValidateManifest(t *testing.T) { func TestValidatePodSpec(t *testing.T) { successCases := []api.PodSpec{ - {}, // empty is valid, if not very useful */ { // Populate basic fields, leave defaults for most. - Volumes: []api.Volume{{Name: "vol"}}, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Volumes: []api.Volume{{Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, { // Populate all fields. Volumes: []api.Volume{ - {Name: "vol"}, + {Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, }, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, - DNSPolicy: api.DNSClusterFirst, NodeSelector: map[string]string{ "key": "value", }, - Host: "foobar", + Host: "foobar", + DNSPolicy: api.DNSClusterFirst, }, } for i := range successCases { @@ -623,38 +616,26 @@ func TestValidatePodSpec(t *testing.T) { t.Errorf("expected failure for %q", k) } } - - defaultPod := api.PodSpec{} // all empty fields - if errs := ValidatePodSpec(&defaultPod); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - if util.AllPtrFieldsNil(defaultPod.RestartPolicy) { - t.Errorf("expected a default RestartPolicy") - } - if defaultPod.DNSPolicy == "" { - t.Errorf("expected a default DNSPolicy") - } } func TestValidatePod(t *testing.T) { successCases := []api.Pod{ - { // Mostly empty. - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"}, - }, { // Basic fields. ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: api.PodSpec{ - Volumes: []api.Volume{{Name: "vol"}}, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Volumes: []api.Volume{{Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, { // Just about everything. ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, Spec: api.PodSpec{ Volumes: []api.Volume{ - {Name: "vol"}, + {Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, }, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, DNSPolicy: api.DNSClusterFirst, NodeSelector: map[string]string{ @@ -896,21 +877,27 @@ func TestValidateBoundPods(t *testing.T) { successCases := []api.BoundPod{ { // Mostly empty. ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, { // Basic fields. ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"}, Spec: api.PodSpec{ - Volumes: []api.Volume{{Name: "vol"}}, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Volumes: []api.Volume{{Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, { // Just about everything. ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, Spec: api.PodSpec{ Volumes: []api.Volume{ - {Name: "vol"}, + {Name: "vol", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, }, - Containers: []api.Container{{Name: "ctr", Image: "image"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, DNSPolicy: api.DNSClusterFirst, NodeSelector: map[string]string{ @@ -955,20 +942,50 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the ID is missing. numErrs: 1, }, + { + name: "missing protocol", + svc: api.Service{ + ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + SessionAffinity: "None", + }, + }, + // Should fail because protocol is missing. + numErrs: 1, + }, + { + name: "missing session affinity", + svc: api.Service{ + ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + }, + }, + // Should fail because protocol is missing. + numErrs: 1, + }, { name: "missing namespace", svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the Namespace is missing. @@ -979,8 +996,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "-123abc", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the ID is invalid. @@ -995,8 +1014,10 @@ func TestValidateService(t *testing.T) { Namespace: api.NamespaceDefault, }, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the Base value for generation is invalid @@ -1011,8 +1032,10 @@ func TestValidateService(t *testing.T) { Namespace: api.NamespaceDefault, }, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the generate name type is invalid. @@ -1023,7 +1046,9 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Selector: map[string]string{"foo": "bar"}, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the port number is missing/invalid. @@ -1034,8 +1059,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 66536, - Selector: map[string]string{"foo": "bar"}, + Port: 66536, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should fail because the port number is invalid. @@ -1046,9 +1073,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, - Protocol: "INVALID", + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "INVALID", + SessionAffinity: "None", }, }, // Should fail because the protocol is invalid. @@ -1059,7 +1087,9 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, + Port: 8675, + Protocol: "TCP", + SessionAffinity: "None", }, }, // Should be ok because the selector is missing. @@ -1070,9 +1100,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, - Protocol: "TCP", + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, numErrs: 0, @@ -1082,9 +1113,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, - Protocol: "UDP", + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "UDP", + SessionAffinity: "None", }, }, numErrs: 0, @@ -1094,8 +1126,10 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "UDP", + SessionAffinity: "None", }, }, numErrs: 0, @@ -1108,13 +1142,15 @@ func TestValidateService(t *testing.T) { Port: 80, CreateExternalLoadBalancer: true, Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, existing: api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, - Spec: api.ServiceSpec{Port: 80, CreateExternalLoadBalancer: true}, + Spec: api.ServiceSpec{Port: 80, CreateExternalLoadBalancer: true, Protocol: "TCP"}, }, }, }, @@ -1128,13 +1164,15 @@ func TestValidateService(t *testing.T) { Port: 80, CreateExternalLoadBalancer: true, Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, existing: api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, - Spec: api.ServiceSpec{Port: 80}, + Spec: api.ServiceSpec{Port: 80, Protocol: "TCP"}, }, }, }, @@ -1145,15 +1183,17 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 80, - Selector: map[string]string{"foo": "bar"}, + Port: 80, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, existing: api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, - Spec: api.ServiceSpec{Port: 80, CreateExternalLoadBalancer: true}, + Spec: api.ServiceSpec{Port: 80, CreateExternalLoadBalancer: true, Protocol: "TCP"}, }, }, }, @@ -1164,15 +1204,17 @@ func TestValidateService(t *testing.T) { svc: api.Service{ ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 80, - Selector: map[string]string{"foo": "bar"}, + Port: 80, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, existing: api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, - Spec: api.ServiceSpec{Port: 80}, + Spec: api.ServiceSpec{Port: 80, Protocol: "TCP"}, }, }, }, @@ -1189,7 +1231,9 @@ func TestValidateService(t *testing.T) { }, }, Spec: api.ServiceSpec{ - Port: 8675, + Port: 8675, + Protocol: "TCP", + SessionAffinity: "None", }, }, numErrs: 1, @@ -1205,7 +1249,9 @@ func TestValidateService(t *testing.T) { }, }, Spec: api.ServiceSpec{ - Port: 8675, + Port: 8675, + Protocol: "TCP", + SessionAffinity: "None", }, }, numErrs: 1, @@ -1218,8 +1264,10 @@ func TestValidateService(t *testing.T) { Namespace: api.NamespaceDefault, }, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, }, numErrs: 1, @@ -1238,17 +1286,16 @@ func TestValidateService(t *testing.T) { svc := api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Port: 8675, - Selector: map[string]string{"foo": "bar"}, + Port: 8675, + Selector: map[string]string{"foo": "bar"}, + Protocol: "TCP", + SessionAffinity: "None", }, } errs := ValidateService(&svc) if len(errs) != 0 { t.Errorf("Unexpected non-zero error list: %#v", errs) } - if svc.Spec.Protocol != "TCP" { - t.Errorf("Expected default protocol of 'TCP': %#v", errs) - } } func TestValidateReplicationController(t *testing.T) { @@ -1258,6 +1305,10 @@ func TestValidateReplicationController(t *testing.T) { ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, } invalidVolumePodTemplate := api.PodTemplate{ @@ -1271,9 +1322,8 @@ func TestValidateReplicationController(t *testing.T) { invalidPodTemplate := api.PodTemplate{ Spec: api.PodTemplateSpec{ Spec: api.PodSpec{ - RestartPolicy: api.RestartPolicy{ - Always: &api.RestartPolicyAlways{}, - }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, ObjectMeta: api.ObjectMeta{ Labels: invalidSelector, @@ -1400,6 +1450,7 @@ func TestValidateReplicationController(t *testing.T) { RestartPolicy: api.RestartPolicy{ OnFailure: &api.RestartPolicyOnFailure{}, }, + DNSPolicy: api.DNSClusterFirst, }, ObjectMeta: api.ObjectMeta{ Labels: validSelector, @@ -1419,6 +1470,7 @@ func TestValidateReplicationController(t *testing.T) { RestartPolicy: api.RestartPolicy{ Never: &api.RestartPolicyNever{}, }, + DNSPolicy: api.DNSClusterFirst, }, ObjectMeta: api.ObjectMeta{ Labels: validSelector, diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index c9e7d4f713e..2ddb20fab02 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient { func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) { c.ValidateCommon(t, err) - if c.Response.Body != nil && !api.Semantic.DeepEqual(c.Response.Body, received) { + if c.Response.Body != nil && !api.Semantic.DeepDerivative(c.Response.Body, received) { t.Errorf("bad response for request %#v: expected %#v, got %#v", c.Request, c.Response.Body, received) } } diff --git a/pkg/client/request_test.go b/pkg/client/request_test.go index 1439625d0ee..bba74b8f19d 100644 --- a/pkg/client/request_test.go +++ b/pkg/client/request_test.go @@ -26,7 +26,7 @@ import ( "net/http/httptest" "net/url" "os" - "reflect" + // "reflect" "strings" "testing" "time" @@ -62,7 +62,7 @@ func TestRequestWithErrorWontChange(t *testing.T) { if changed != &r { t.Errorf("returned request should point to the same object") } - if !reflect.DeepEqual(&original, changed) { + if !api.Semantic.DeepDerivative(changed, &original) { t.Errorf("expected %#v, got %#v", &original, changed) } } @@ -148,7 +148,7 @@ func TestRequestParseSelectorParam(t *testing.T) { func TestRequestParam(t *testing.T) { r := (&Request{}).Param("foo", "a") - if !reflect.DeepEqual(map[string]string{"foo": "a"}, r.params) { + if !api.Semantic.DeepDerivative(r.params, map[string]string{"foo": "a"}) { t.Errorf("should have set a param: %#v", r) } } @@ -218,7 +218,7 @@ func TestTransformResponse(t *testing.T) { if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) } - if !(test.Data == nil && response == nil) && !reflect.DeepEqual(test.Data, response) { + if !(test.Data == nil && response == nil) && !api.Semantic.DeepDerivative(test.Data, response) { t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response) } if test.Created != created { @@ -491,7 +491,7 @@ func TestDoRequestNewWay(t *testing.T) { } if obj == nil { t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { + } else if !api.Semantic.DeepDerivative(expectedObj, obj) { t.Errorf("Expected: %#v, got %#v", expectedObj, obj) } fakeHandler.ValidateRequest(t, "/api/v1beta2/foo/bar/baz?labels=name%3Dfoo&timeout=1s", "POST", &reqBody) @@ -526,7 +526,7 @@ func TestDoRequestNewWayReader(t *testing.T) { } if obj == nil { t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { + } else if !api.Semantic.DeepDerivative(expectedObj, obj) { t.Errorf("Expected: %#v, got %#v", expectedObj, obj) } tmpStr := string(reqBodyExpected) @@ -562,7 +562,7 @@ func TestDoRequestNewWayObj(t *testing.T) { } if obj == nil { t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { + } else if !api.Semantic.DeepDerivative(expectedObj, obj) { t.Errorf("Expected: %#v, got %#v", expectedObj, obj) } tmpStr := string(reqBodyExpected) @@ -611,7 +611,7 @@ func TestDoRequestNewWayFile(t *testing.T) { } if obj == nil { t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { + } else if !api.Semantic.DeepDerivative(expectedObj, obj) { t.Errorf("Expected: %#v, got %#v", expectedObj, obj) } if wasCreated { @@ -653,7 +653,7 @@ func TestWasCreated(t *testing.T) { } if obj == nil { t.Error("nil obj") - } else if !reflect.DeepEqual(obj, expectedObj) { + } else if !api.Semantic.DeepDerivative(expectedObj, obj) { t.Errorf("Expected: %#v, got %#v", expectedObj, obj) } if !wasCreated { @@ -790,7 +790,7 @@ func checkAuth(t *testing.T, expect *Config, r *http.Request) { foundAuth, found := authFromReq(r) if !found { t.Errorf("no auth found") - } else if e, a := expect, foundAuth; !reflect.DeepEqual(e, a) { + } else if e, a := expect, foundAuth; !api.Semantic.DeepDerivative(e, a) { t.Fatalf("Wrong basic auth: wanted %#v, got %#v", e, a) } } @@ -849,7 +849,7 @@ func TestWatch(t *testing.T) { if e, a := item.t, got.Type; e != a { t.Errorf("Expected %v, got %v", e, a) } - if e, a := item.obj, got.Object; !reflect.DeepEqual(e, a) { + if e, a := item.obj, got.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Expected %v, got %v", e, a) } } diff --git a/pkg/controller/replication_controller_test.go b/pkg/controller/replication_controller_test.go index 58c88e5f186..bf9b772bb7d 100644 --- a/pkg/controller/replication_controller_test.go +++ b/pkg/controller/replication_controller_test.go @@ -239,7 +239,7 @@ func TestCreateReplica(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %#v", err) } - if !api.Semantic.DeepEqual(&expectedPod, actualPod) { + if !api.Semantic.DeepDerivative(&expectedPod, actualPod) { t.Logf("Body: %s", fakeHandler.RequestBody) t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod) } @@ -351,7 +351,7 @@ func TestWatchControllers(t *testing.T) { var testControllerSpec api.ReplicationController received := make(chan struct{}) manager.syncHandler = func(controllerSpec api.ReplicationController) error { - if !api.Semantic.DeepEqual(controllerSpec, testControllerSpec) { + if !api.Semantic.DeepDerivative(controllerSpec, testControllerSpec) { t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec) } close(received) diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index 8d46f83685e..c2ab406a69f 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -68,7 +68,6 @@ func NewConverter() *Converter { return &Converter{ conversionFuncs: map[typePair]reflect.Value{}, defaultingFuncs: map[reflect.Type]reflect.Value{}, - funcs: map[typePair]reflect.Value{}, nameFunc: func(t reflect.Type) string { return t.Name() }, structFieldDests: map[typeNamePair][]typeNamePair{}, structFieldSources: map[typeNamePair][]typeNamePair{}, @@ -221,7 +220,7 @@ func (s *scope) error(message string, args ...interface{}) error { // used if recursive conversion calls are desired). It must return an error. // // Example: -// c.RegisteConversionFuncr( +// c.RegisteConversionFunc( // func(in *Pod, out *v1beta1.Pod, s Scope) error { // // conversion logic... // return nil @@ -279,7 +278,7 @@ func (c *Converter) SetStructFieldCopy(srcFieldType interface{}, srcFieldName st // defaultingFunc must take one parameters: a pointer to the input type. // // Example: -// c.RegisteDefaultingFuncr( +// c.RegisteDefaultingFunc( // func(in *v1beta1.Pod) { // // defaulting logic... // }) @@ -415,7 +414,6 @@ func (c *Converter) callCustom(sv, dv, custom reflect.Value, scope *scope) error // one is registered. func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error { dt, st := dv.Type(), sv.Type() - // Apply default values. if fv, ok := c.defaultingFuncs[st]; ok { if c.Debug != nil { diff --git a/pkg/conversion/converter_test.go b/pkg/conversion/converter_test.go index 1b22c685931..4f807760eac 100644 --- a/pkg/conversion/converter_test.go +++ b/pkg/conversion/converter_test.go @@ -36,11 +36,11 @@ func TestConverter_DefaultConvert(t *testing.T) { } c := NewConverter() c.Debug = t - c.NameFunc = func(t reflect.Type) string { return "MyType" } + c.nameFunc = func(t reflect.Type) string { return "MyType" } // Ensure conversion funcs can call DefaultConvert to get default behavior, // then fixup remaining fields manually - err := c.Register(func(in *A, out *B, s Scope) error { + err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error { if err := s.DefaultConvert(in, out, IgnoreMissingFields); err != nil { return err } @@ -224,7 +224,7 @@ func TestConverter_MapElemAddr(t *testing.T) { } c := NewConverter() c.Debug = t - err := c.Register( + err := c.RegisterConversionFunc( func(in *int, out *string, s Scope) error { *out = fmt.Sprintf("%v", *in) return nil @@ -233,7 +233,7 @@ func TestConverter_MapElemAddr(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } - err = c.Register( + err = c.RegisterConversionFunc( func(in *string, out *int, s Scope) error { if str, err := strconv.Atoi(*in); err != nil { return err diff --git a/pkg/conversion/decode.go b/pkg/conversion/decode.go index 6e7b50de4e7..f8ef14d1798 100644 --- a/pkg/conversion/decode.go +++ b/pkg/conversion/decode.go @@ -103,27 +103,19 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error { dataVersion = objVersion } - if objVersion == dataVersion { - // Easy case! - err = yaml.Unmarshal(data, obj) - if err != nil { - return err - } - } else { - external, err := s.NewObject(dataVersion, dataKind) - if err != nil { - return err - } - // yaml is a superset of json, so we use it to decode here. That way, - // we understand both. - err = yaml.Unmarshal(data, external) - if err != nil { - return err - } - err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion)) - if err != nil { - return err - } + external, err := s.NewObject(dataVersion, dataKind) + if err != nil { + return err + } + // yaml is a superset of json, so we use it to decode here. That way, + // we understand both. + err = yaml.Unmarshal(data, external) + if err != nil { + return err + } + err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion)) + if err != nil { + return err } // Version and Kind should be blank in memory. diff --git a/pkg/conversion/deep_equal.go b/pkg/conversion/deep_equal.go index 237185d1827..617f3d793ca 100644 --- a/pkg/conversion/deep_equal.go +++ b/pkg/conversion/deep_equal.go @@ -233,3 +233,140 @@ func (e Equalities) DeepEqual(a1, a2 interface{}) bool { } return e.deepValueEqual(v1, v2, make(map[visit]bool), 0) } + +func (e Equalities) deepValueDerive(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { + if !v1.IsValid() || !v2.IsValid() { + return v1.IsValid() == v2.IsValid() + } + if v1.Type() != v2.Type() { + return false + } + if fv, ok := e[v1.Type()]; ok { + return fv.Call([]reflect.Value{v1, v2})[0].Bool() + } + + hard := func(k reflect.Kind) bool { + switch k { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return true + } + return false + } + + if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { + addr1 := v1.UnsafeAddr() + addr2 := v2.UnsafeAddr() + if addr1 > addr2 { + // Canonicalize order to reduce number of entries in visited. + addr1, addr2 = addr2, addr1 + } + + // Short circuit if references are identical ... + if addr1 == addr2 { + return true + } + + // ... or already seen + typ := v1.Type() + v := visit{addr1, addr2, typ} + if visited[v] { + return true + } + + // Remember for later. + visited[v] = true + } + + switch v1.Kind() { + case reflect.Array: + for i := 0; i < v1.Len(); i++ { + if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + } + return true + case reflect.Slice: + if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { + return false + } + if v1.IsNil() || v1.Len() == 0 { + return true + } + if v1.Pointer() == v2.Pointer() { + return true + } + for i := 0; i < v1.Len(); i++ { + if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + } + return true + case reflect.String: + if v1.Len() == 0 { + return true + } + return v1.String() == v2.String() + case reflect.Interface: + if v1.IsNil() { + return true + } + return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Ptr: + if v1.IsNil() { + return true + } + return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Struct: + for i, n := 0, v1.NumField(); i < n; i++ { + if !e.deepValueDerive(v1.Field(i), v2.Field(i), visited, depth+1) { + return false + } + } + return true + case reflect.Map: + if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { + return false + } + if v1.IsNil() || v1.Len() == 0 { + return true + } + if v1.Pointer() == v2.Pointer() { + return true + } + for _, k := range v1.MapKeys() { + if !e.deepValueDerive(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { + return false + } + } + return true + case reflect.Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false + default: + // Normal equality suffices + if v1.CanInterface() && v2.CanInterface() { + return v1.Interface() == v2.Interface() + } + return v1.CanInterface() == v2.CanInterface() + } +} + +// DeepDerivative is similar to DeepEqual except that unset fields in a1 are +// ignored (not compared). This allows we to focus on the fields that matter to +// the semantic comparison. +// +// The unset fields include a nil pointer and an empty string. +func (e Equalities) DeepDerivative(a1, a2 interface{}) bool { + if a1 == nil { + return true + } + v1 := reflect.ValueOf(a1) + v2 := reflect.ValueOf(a2) + if v1.Type() != v2.Type() { + return false + } + return e.deepValueDerive(v1, v2, make(map[visit]bool), 0) +} diff --git a/pkg/kubecfg/parse_test.go b/pkg/kubecfg/parse_test.go index 4100fbfc6e3..42d2b1f34ce 100644 --- a/pkg/kubecfg/parse_test.go +++ b/pkg/kubecfg/parse_test.go @@ -73,11 +73,17 @@ func TestParsePod(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "test pod"}, Spec: api.PodSpec{ Containers: []api.Container{ - {Name: "my container"}, + { + Name: "my container", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, + }, }, Volumes: []api.Volume{ - {Name: "volume"}, + {Name: "volume", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, v1beta1.Codec, testParser) } @@ -96,6 +102,8 @@ func TestParseService(t *testing.T) { Selector: map[string]string{ "area": "staging", }, + Protocol: "TCP", + SessionAffinity: "None", }, }, v1beta1.Codec, testParser) } @@ -109,11 +117,17 @@ func TestParseController(t *testing.T) { Template: &api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ - {Name: "my container"}, + { + Name: "my container", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, + }, }, Volumes: []api.Volume{ - {Name: "volume"}, + {Name: "volume", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, }, diff --git a/pkg/kubecfg/resource_printer_test.go b/pkg/kubecfg/resource_printer_test.go index f8253b43543..b826053fb20 100644 --- a/pkg/kubecfg/resource_printer_test.go +++ b/pkg/kubecfg/resource_printer_test.go @@ -71,6 +71,10 @@ func TestYAMLPrinterPrint(t *testing.T) { obj := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } buf.Reset() printer.PrintObj(obj, buf) @@ -95,6 +99,10 @@ func TestIdentityPrinter(t *testing.T) { obj := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } buff.Reset() printer.PrintObj(obj, buff) diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 6bf792d115c..43eebf61331 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -42,9 +42,17 @@ func testData() (*api.PodList, *api.ServiceList) { Items: []api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, { ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, }, } @@ -55,6 +63,10 @@ func testData() (*api.PodList, *api.ServiceList) { Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: api.ServiceSpec{ + Protocol: "TCP", + SessionAffinity: "None", + }, }, }, } @@ -296,6 +308,10 @@ func watchTestData() ([]api.Pod, []watch.Event) { Namespace: "test", ResourceVersion: "10", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, } events := []watch.Event{ @@ -307,6 +323,10 @@ func watchTestData() ([]api.Pod, []watch.Event) { Namespace: "test", ResourceVersion: "11", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, }, { @@ -317,6 +337,10 @@ func watchTestData() ([]api.Pod, []watch.Event) { Namespace: "test", ResourceVersion: "12", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, }, } diff --git a/pkg/kubectl/cmd/helpers_test.go b/pkg/kubectl/cmd/helpers_test.go index 57c229539ea..ed0ec504ea3 100644 --- a/pkg/kubectl/cmd/helpers_test.go +++ b/pkg/kubectl/cmd/helpers_test.go @@ -42,6 +42,12 @@ func TestMerge(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "foo", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{ + Always: &api.RestartPolicyAlways{}, + }, + DNSPolicy: api.DNSClusterFirst, + }, }, }, { @@ -57,6 +63,10 @@ func TestMerge(t *testing.T) { }, Spec: api.PodSpec{ Host: "bar", + RestartPolicy: api.RestartPolicy{ + Always: &api.RestartPolicyAlways{}, + }, + DNSPolicy: api.DNSClusterFirst, }, }, }, @@ -74,12 +84,18 @@ func TestMerge(t *testing.T) { Spec: api.PodSpec{ Volumes: []api.Volume{ { - Name: "v1", + Name: "v1", + Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}, }, { - Name: "v2", + Name: "v2", + Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}, }, }, + RestartPolicy: api.RestartPolicy{ + Always: &api.RestartPolicyAlways{}, + }, + DNSPolicy: api.DNSClusterFirst, }, }, }, @@ -91,13 +107,13 @@ func TestMerge(t *testing.T) { }, } - for _, test := range tests { + for i, test := range tests { err := Merge(test.obj, test.fragment, "Pod") if !test.expectErr { if err != nil { t.Errorf("unexpected error: %v", err) } else if !reflect.DeepEqual(test.obj, test.expected) { - t.Errorf("\nexpected:\n%v\nsaw:\n%v", test.expected, test.obj) + t.Errorf("\n\ntestcase[%d]\nexpected:\n%v\nsaw:\n%v", i, test.expected, test.obj) } } if test.expectErr && err == nil { diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 99a2094d2ab..26f0ed011f0 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -88,9 +88,17 @@ func testData() (*api.PodList, *api.ServiceList) { Items: []api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, { ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, }, } @@ -376,7 +384,7 @@ func TestSelector(t *testing.T) { if err != nil || singular || len(test.Infos) != 3 { t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) } - if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) { + if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) { t.Errorf("unexpected visited objects: %#v", test.Objects()) } @@ -419,7 +427,7 @@ func TestStream(t *testing.T) { if err != nil || singular || len(test.Infos) != 3 { t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) } - if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) { + if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) { t.Errorf("unexpected visited objects: %#v", test.Objects()) } } @@ -436,7 +444,7 @@ func TestYAMLStream(t *testing.T) { if err != nil || singular || len(test.Infos) != 3 { t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) } - if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) { + if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) { t.Errorf("unexpected visited objects: %#v", test.Objects()) } } @@ -458,7 +466,7 @@ func TestMultipleObject(t *testing.T) { &svc.Items[0], }, } - if !reflect.DeepEqual(expected, obj) { + if !api.Semantic.DeepDerivative(expected, obj) { t.Errorf("unexpected visited objects: %#v", obj) } } @@ -612,7 +620,7 @@ func TestLatest(t *testing.T) { if err != nil || singular || len(test.Infos) != 3 { t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) } - if !reflect.DeepEqual([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) { + if !api.Semantic.DeepDerivative([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) { t.Errorf("unexpected visited objects: %#v", test.Objects()) } } @@ -646,7 +654,7 @@ func TestIgnoreStreamErrors(t *testing.T) { t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) } - if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &svc.Items[0]}, test.Objects()) { + if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &svc.Items[0]}, test.Objects()) { t.Errorf("unexpected visited objects: %#v", test.Objects()) } } diff --git a/pkg/kubectl/resource/helper_test.go b/pkg/kubectl/resource/helper_test.go index 1b4a8c8eac5..11d4e8752d0 100644 --- a/pkg/kubectl/resource/helper_test.go +++ b/pkg/kubectl/resource/helper_test.go @@ -162,11 +162,17 @@ func TestHelperCreate(t *testing.T) { Req: expectPost, }, { - Modify: true, - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, - ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, - Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, - Req: expectPost, + Modify: true, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectObject: &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, + Req: expectPost, }, } for i, test := range tests { @@ -400,9 +406,14 @@ func TestHelperUpdate(t *testing.T) { Req: expectPut, }, { - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, - ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, - + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + ExpectObject: &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, Overwrite: true, RespFunc: func(req *http.Request) (*http.Response, error) { if req.Method == "PUT" { diff --git a/pkg/kubelet/config/config_test.go b/pkg/kubelet/config/config_test.go index 0b9d0450c46..a26cccfa254 100644 --- a/pkg/kubelet/config/config_test.go +++ b/pkg/kubelet/config/config_test.go @@ -177,7 +177,7 @@ func TestNewPodAddedSnapshotAndUpdates(t *testing.T) { // container updates are separated as UPDATE pod := podUpdate.Pods[0] - pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}} + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod) expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, NoneSource, pod)) } @@ -195,7 +195,7 @@ func TestNewPodAddedSnapshot(t *testing.T) { // container updates are separated as UPDATE pod := podUpdate.Pods[0] - pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}} + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod) expectPodUpdate(t, ch, CreatePodUpdate(kubelet.SET, TestSource, pod)) } @@ -213,7 +213,7 @@ func TestNewPodAddedUpdatedRemoved(t *testing.T) { // an kubelet.ADD should be converted to kubelet.UPDATE pod := CreateValidPod("foo", "new", "test") - pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}} + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} podUpdate = CreatePodUpdate(kubelet.ADD, NoneSource, pod) channel <- podUpdate expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, NoneSource, pod)) @@ -236,7 +236,7 @@ func TestNewPodAddedUpdatedSet(t *testing.T) { // should be converted to an kubelet.ADD, kubelet.REMOVE, and kubelet.UPDATE pod := CreateValidPod("foo2", "new", "test") - pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}} + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} podUpdate = CreatePodUpdate(kubelet.SET, NoneSource, pod, CreateValidPod("foo3", "new", ""), CreateValidPod("foo4", "new", "test")) channel <- podUpdate expectPodUpdate(t, ch, diff --git a/pkg/kubelet/config/file_test.go b/pkg/kubelet/config/file_test.go index c171f836bd1..07a25fee27a 100644 --- a/pkg/kubelet/config/file_test.go +++ b/pkg/kubelet/config/file_test.go @@ -62,7 +62,6 @@ func ExampleManifestAndPod(id string) (v1beta1.ContainerManifest, api.BoundPod) { Name: "c" + id, Image: "foo", - TerminationMessagePath: "/somepath", }, }, Volumes: []api.Volume{ @@ -94,7 +93,7 @@ func TestUpdateOnNonExistentFile(t *testing.T) { case got := <-ch: update := got.(kubelet.PodUpdate) expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } @@ -137,15 +136,7 @@ func TestReadFromFile(t *testing.T) { Namespace: "", SelfLink: "", }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "test/image", - TerminationMessagePath: "/dev/termination-log", - ImagePullPolicy: api.PullAlways, - }, - }, - }, + Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}}, }) // There's no way to provide namespace in ContainerManifest, so @@ -161,7 +152,7 @@ func TestReadFromFile(t *testing.T) { } update.Pods[0].ObjectMeta.SelfLink = "" - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } @@ -191,15 +182,7 @@ func TestReadFromFileWithoutID(t *testing.T) { Namespace: "", SelfLink: "", }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "test/image", - TerminationMessagePath: "/dev/termination-log", - ImagePullPolicy: api.PullAlways, - }, - }, - }, + Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}}, }) if len(update.Pods[0].ObjectMeta.Name) == 0 { @@ -209,7 +192,7 @@ func TestReadFromFileWithoutID(t *testing.T) { update.Pods[0].ObjectMeta.Namespace = "" update.Pods[0].ObjectMeta.SelfLink = "" - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } @@ -240,21 +223,13 @@ func TestReadV1Beta2FromFile(t *testing.T) { Namespace: "", SelfLink: "", }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "test/image", - TerminationMessagePath: "/dev/termination-log", - ImagePullPolicy: api.PullAlways, - }, - }, - }, + Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}}, }) update.Pods[0].ObjectMeta.Namespace = "" update.Pods[0].ObjectMeta.SelfLink = "" - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } @@ -315,7 +290,7 @@ func TestExtractFromEmptyDir(t *testing.T) { update := (<-ch).(kubelet.PodUpdate) expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Errorf("Expected %#v, Got %#v", expected, update) } } @@ -371,7 +346,7 @@ func TestExtractFromDir(t *testing.T) { } sort.Sort(sortedPods(update.Pods)) sort.Sort(sortedPods(expected.Pods)) - if !api.Semantic.DeepEqual(expected, update) { + if !api.Semantic.DeepDerivative(expected, update) { t.Fatalf("Expected %#v, Got %#v", expected, update) } for i := range update.Pods { diff --git a/pkg/master/publish.go b/pkg/master/publish.go index fecdbf3845d..54710b19d9e 100644 --- a/pkg/master/publish.go +++ b/pkg/master/publish.go @@ -90,8 +90,10 @@ func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.I Spec: api.ServiceSpec{ Port: servicePort, // maintained by this code, not by the pod selector - Selector: nil, - PortalIP: serviceIP.String(), + Selector: nil, + PortalIP: serviceIP.String(), + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } // Kids, don't do this at home: this is a hack. There's no good way to call the business diff --git a/pkg/registry/controller/rest_test.go b/pkg/registry/controller/rest_test.go index aaaea3e9460..577b1061415 100644 --- a/pkg/registry/controller/rest_test.go +++ b/pkg/registry/controller/rest_test.go @@ -126,6 +126,10 @@ func TestControllerDecode(t *testing.T) { "name": "nginx", }, }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, }, }, } @@ -225,10 +229,13 @@ var validPodTemplate = api.PodTemplate{ Spec: api.PodSpec{ Containers: []api.Container{ { - Name: "test", - Image: "test_image", + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, }, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, } diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index 8a30c7c5d45..ef13c2dfd13 100644 --- a/pkg/registry/etcd/etcd_test.go +++ b/pkg/registry/etcd/etcd_test.go @@ -17,7 +17,6 @@ limitations under the License. package etcd import ( - "reflect" "strconv" "strings" "testing" @@ -441,6 +440,10 @@ func TestEtcdUpdatePodNotScheduled(t *testing.T) { "foo": "bar", }, }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } err := registry.UpdatePod(ctx, &podIn) if err != nil { @@ -515,9 +518,13 @@ func TestEtcdUpdatePodScheduled(t *testing.T) { Spec: api.PodSpec{ Containers: []api.Container{ { - Image: "foo:v2", + Image: "foo:v2", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, }, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, Status: api.PodStatus{ Host: "machine", @@ -1338,6 +1345,8 @@ func TestEtcdUpdateService(t *testing.T) { Selector: map[string]string{ "baz": "bar", }, + Protocol: "TCP", + SessionAffinity: "None", }, } err := registry.UpdateService(ctx, &testService) @@ -1352,7 +1361,7 @@ func TestEtcdUpdateService(t *testing.T) { // Clear modified indices before the equality test. svc.ResourceVersion = "" testService.ResourceVersion = "" - if !reflect.DeepEqual(*svc, testService) { + if !api.Semantic.DeepEqual(*svc, testService) { t.Errorf("Unexpected service: got\n %#v\n, wanted\n %#v", svc, testService) } } @@ -1404,7 +1413,7 @@ func TestEtcdGetEndpoints(t *testing.T) { t.Errorf("unexpected error: %v", err) } - if e, a := endpoints, got; !reflect.DeepEqual(e, a) { + if e, a := endpoints, got; !api.Semantic.DeepEqual(e, a) { t.Errorf("Unexpected endpoints: %#v, expected %#v", e, a) } } @@ -1433,7 +1442,7 @@ func TestEtcdUpdateEndpoints(t *testing.T) { } var endpointsOut api.Endpoints err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut) - if !reflect.DeepEqual(endpoints, endpointsOut) { + if !api.Semantic.DeepEqual(endpoints, endpointsOut) { t.Errorf("Unexpected endpoints: %#v, expected %#v", endpointsOut, endpoints) } } diff --git a/pkg/registry/generic/etcd/etcd_test.go b/pkg/registry/generic/etcd/etcd_test.go index 1d14e3302dc..e717399111b 100644 --- a/pkg/registry/generic/etcd/etcd_test.go +++ b/pkg/registry/generic/etcd/etcd_test.go @@ -19,7 +19,6 @@ package etcd import ( "fmt" "path" - "reflect" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -146,7 +145,7 @@ func TestEtcdList(t *testing.T) { continue } - if e, a := item.out, list; !reflect.DeepEqual(e, a) { + if e, a := item.out, list; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v: Expected %#v, got %#v", name, e, a) } } @@ -209,7 +208,7 @@ func TestEtcdCreate(t *testing.T) { t.Errorf("%v: unexpected error: %v", name, err) } - if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) { + if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } @@ -284,7 +283,7 @@ func TestEtcdUpdate(t *testing.T) { t.Errorf("%v: unexpected error: %v", name, err) } - if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) { + if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } @@ -340,7 +339,7 @@ func TestEtcdGet(t *testing.T) { t.Errorf("%v: unexpected error: %v", name, err) } - if e, a := item.expect, got; !reflect.DeepEqual(e, a) { + if e, a := item.expect, got; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } @@ -396,7 +395,7 @@ func TestEtcdDelete(t *testing.T) { t.Errorf("%v: unexpected error: %v", name, err) } - if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) { + if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } @@ -432,7 +431,7 @@ func TestEtcdWatch(t *testing.T) { t.Fatalf("unexpected channel close") } - if e, a := podA, got.Object; !reflect.DeepEqual(e, a) { + if e, a := podA, got.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("difference: %s", util.ObjectDiff(e, a)) } } diff --git a/pkg/registry/pod/rest_test.go b/pkg/registry/pod/rest_test.go index aefbb509fcb..e0b2c1c9031 100644 --- a/pkg/registry/pod/rest_test.go +++ b/pkg/registry/pod/rest_test.go @@ -88,6 +88,10 @@ func TestCreatePodRegistryError(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "foo", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } ctx := api.NewDefaultContext() ch, err := storage.Create(ctx, pod) @@ -108,6 +112,10 @@ func TestCreatePodSetsIds(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "foo", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } ctx := api.NewDefaultContext() ch, err := storage.Create(ctx, pod) @@ -135,6 +143,10 @@ func TestCreatePodSetsUID(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "foo", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } ctx := api.NewDefaultContext() ch, err := storage.Create(ctx, pod) @@ -346,6 +358,10 @@ func TestPodDecode(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "foo", }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } body, err := latest.Codec.Encode(expected) if err != nil { @@ -447,7 +463,12 @@ func TestCreatePod(t *testing.T) { registry: podRegistry, podCache: &fakeCache{statusToReturn: &api.PodStatus{}}, } - pod := &api.Pod{} + pod := &api.Pod{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + } pod.Name = "foo" ctx := api.NewDefaultContext() channel, err := storage.Create(ctx, pod) @@ -470,6 +491,10 @@ func TestCreatePodWithConflictingNamespace(t *testing.T) { storage := REST{} pod := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } ctx := api.NewDefaultContext() @@ -488,6 +513,10 @@ func TestUpdatePodWithConflictingNamespace(t *testing.T) { storage := REST{} pod := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, } ctx := api.NewDefaultContext() @@ -647,10 +676,13 @@ func TestCreate(t *testing.T) { Spec: api.PodSpec{ Containers: []api.Container{ { - Name: "test1", - Image: "foo", + Name: "test1", + Image: "foo", + ImagePullPolicy: api.PullIfNotPresent, }, }, + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, }, }, // invalid diff --git a/pkg/registry/service/rest_test.go b/pkg/registry/service/rest_test.go index b18d7de0ad8..cf5897cd78f 100644 --- a/pkg/registry/service/rest_test.go +++ b/pkg/registry/service/rest_test.go @@ -49,8 +49,10 @@ func TestServiceRegistryCreate(t *testing.T) { svc := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Port: 6502, - Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -91,14 +93,18 @@ func TestServiceStorageValidatesCreate(t *testing.T) { "empty ID": { ObjectMeta: api.ObjectMeta{Name: ""}, Spec: api.ServiceSpec{ - Port: 6502, - Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, }, "empty selector": { ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, + Selector: map[string]string{"bar": "baz"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, }, } @@ -129,8 +135,10 @@ func TestServiceRegistryUpdate(t *testing.T) { c, err := storage.Update(ctx, &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Port: 6502, - Selector: map[string]string{"bar": "baz2"}, + Port: 6502, + Selector: map[string]string{"bar": "baz2"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, }) if err != nil { @@ -164,15 +172,19 @@ func TestServiceStorageValidatesUpdate(t *testing.T) { "empty ID": { ObjectMeta: api.ObjectMeta{Name: ""}, Spec: api.ServiceSpec{ - Port: 6502, - Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, }, "invalid selector": { ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Port: 6502, - Selector: map[string]string{"ThisSelectorFailsValidation": "ok"}, + Port: 6502, + Selector: map[string]string{"ThisSelectorFailsValidation": "ok"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, }, } @@ -199,6 +211,8 @@ func TestServiceRegistryExternalService(t *testing.T) { Port: 6502, Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } c, _ := storage.Create(ctx, svc) @@ -228,6 +242,8 @@ func TestServiceRegistryExternalServiceError(t *testing.T) { Port: 6502, Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -250,7 +266,9 @@ func TestServiceRegistryDelete(t *testing.T) { svc := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, + Selector: map[string]string{"bar": "baz"}, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } registry.CreateService(ctx, svc) @@ -275,6 +293,8 @@ func TestServiceRegistryDeleteExternal(t *testing.T) { Spec: api.ServiceSpec{ Selector: map[string]string{"bar": "baz"}, CreateExternalLoadBalancer: true, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } registry.CreateService(ctx, svc) @@ -389,8 +409,10 @@ func TestServiceRegistryIPAllocation(t *testing.T) { svc1 := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -407,8 +429,10 @@ func TestServiceRegistryIPAllocation(t *testing.T) { svc2 := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }} ctx = api.NewDefaultContext() c2, _ := rest.Create(ctx, svc2) @@ -424,9 +448,11 @@ func TestServiceRegistryIPAllocation(t *testing.T) { svc3 := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "quux"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - PortalIP: "1.2.3.93", - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + PortalIP: "1.2.3.93", + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx = api.NewDefaultContext() @@ -448,8 +474,10 @@ func TestServiceRegistryIPReallocation(t *testing.T) { svc1 := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -469,8 +497,10 @@ func TestServiceRegistryIPReallocation(t *testing.T) { svc2 := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx = api.NewDefaultContext() @@ -495,8 +525,10 @@ func TestServiceRegistryIPUpdate(t *testing.T) { svc := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -551,6 +583,8 @@ func TestServiceRegistryIPExternalLoadBalancer(t *testing.T) { Selector: map[string]string{"bar": "baz"}, Port: 6502, CreateExternalLoadBalancer: true, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -586,8 +620,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) { svc := &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } ctx := api.NewDefaultContext() @@ -596,8 +632,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) { svc = &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } c, _ = rest1.Create(ctx, svc) @@ -610,8 +648,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) { svc = &api.Service{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: api.ProtocolTCP, + SessionAffinity: api.AffinityTypeNone, }, } c, _ = rest2.Create(ctx, svc) @@ -671,8 +711,10 @@ func TestCreate(t *testing.T) { // valid &api.Service{ Spec: api.ServiceSpec{ - Selector: map[string]string{"bar": "baz"}, - Port: 6502, + Selector: map[string]string{"bar": "baz"}, + Port: 6502, + Protocol: "TCP", + SessionAffinity: "None", }, }, // invalid diff --git a/pkg/tools/etcd_tools_test.go b/pkg/tools/etcd_tools_test.go index e151f002ddc..0961aa60bf0 100644 --- a/pkg/tools/etcd_tools_test.go +++ b/pkg/tools/etcd_tools_test.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/coreos/go-etcd/etcd" ) @@ -47,6 +48,12 @@ func init() { scheme.AddKnownTypes("", &TestResource{}) scheme.AddKnownTypes("v1beta1", &TestResource{}) codec = runtime.CodecFor(scheme, "v1beta1") + scheme.AddConversionFuncs( + func(in *TestResource, out *TestResource, s conversion.Scope) error { + *out = *in + return nil + }, + ) } func TestIsEtcdNotFound(t *testing.T) { @@ -94,10 +101,27 @@ func TestExtractToList(t *testing.T) { expect := api.PodList{ ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ - // We expect items to be sorted by its name. - {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, - {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, - {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, + { + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, }, } @@ -160,9 +184,27 @@ func TestExtractToListAcrossDirectories(t *testing.T) { ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ // We expect list to be sorted by directory (e.g. namespace) first, then by name. - {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "1"}}, - {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, - {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, + { + ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "1"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, }, } @@ -212,9 +254,27 @@ func TestExtractToListExcludesDirectories(t *testing.T) { expect := api.PodList{ ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ - {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, - {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, - {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, + { + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + }, }, } @@ -231,7 +291,13 @@ func TestExtractToListExcludesDirectories(t *testing.T) { func TestExtractObj(t *testing.T) { fakeClient := NewFakeEtcdClient(t) - expect := api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + expect := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + } fakeClient.Set("/some/key", runtime.EncodeOrDie(testapi.Codec(), &expect), 0) helper := EtcdHelper{fakeClient, testapi.Codec(), versioner} var got api.Pod diff --git a/pkg/tools/etcd_tools_watch_test.go b/pkg/tools/etcd_tools_watch_test.go index 67e508c6773..16602ec7ac3 100644 --- a/pkg/tools/etcd_tools_watch_test.go +++ b/pkg/tools/etcd_tools_watch_test.go @@ -18,7 +18,6 @@ package tools import ( "fmt" - "reflect" "testing" "time" @@ -123,7 +122,7 @@ func TestWatchInterpretations(t *testing.T) { if e, a := item.expectType, event.Type; e != a { t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a) } - if e, a := item.expectObject, event.Object; !reflect.DeepEqual(e, a) { + if e, a := item.expectObject, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a) } } @@ -250,7 +249,7 @@ func TestWatch(t *testing.T) { if e, a := watch.Added, event.Type; e != a { t.Errorf("Expected %v, got %v", e, a) } - if e, a := pod, event.Object; !reflect.DeepEqual(e, a) { + if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Expected %v, got %v", e, a) } @@ -381,7 +380,7 @@ func TestWatchEtcdState(t *testing.T) { t.Errorf("%s: expected type %v, got %v", k, e, a) break } - if e, a := testCase.Expected[i].Endpoints, event.Object.(*api.Endpoints).Endpoints; !reflect.DeepEqual(e, a) { + if e, a := testCase.Expected[i].Endpoints, event.Object.(*api.Endpoints).Endpoints; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%s: expected type %v, got %v", k, e, a) break } @@ -456,7 +455,7 @@ func TestWatchFromZeroIndex(t *testing.T) { t.Errorf("%s: expected pod with resource version %v, Got %#v", k, testCase.ExpectedVersion, actualPod) } pod.ResourceVersion = testCase.ExpectedVersion - if e, a := pod, event.Object; !reflect.DeepEqual(e, a) { + if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%s: expected %v, got %v", k, e, a) } watching.Stop() @@ -515,7 +514,7 @@ func TestWatchListFromZeroIndex(t *testing.T) { t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod) } pod.ResourceVersion = "1" - if e, a := pod, event.Object; !reflect.DeepEqual(e, a) { + if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Expected %v, got %v", e, a) } } diff --git a/pkg/watch/json/decoder_test.go b/pkg/watch/json/decoder_test.go index 2b81279d0af..91eeb5936c7 100644 --- a/pkg/watch/json/decoder_test.go +++ b/pkg/watch/json/decoder_test.go @@ -19,7 +19,6 @@ package json import ( "encoding/json" "io" - "reflect" "testing" "time" @@ -58,7 +57,7 @@ func TestDecoder(t *testing.T) { if e, a := eventType, action; e != a { t.Errorf("Expected %v, got %v", e, a) } - if e, a := expect, got; !reflect.DeepEqual(e, a) { + if e, a := expect, got; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Expected %v, got %v", e, a) } t.Logf("Exited read") diff --git a/pkg/watch/json/encoder_test.go b/pkg/watch/json/encoder_test.go index 4ff7d18e882..bd3bdd6cd20 100644 --- a/pkg/watch/json/encoder_test.go +++ b/pkg/watch/json/encoder_test.go @@ -19,7 +19,6 @@ package json import ( "bytes" "io/ioutil" - "reflect" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -66,7 +65,7 @@ func TestEncodeDecodeRoundTrip(t *testing.T) { t.Errorf("%d: unexpected error: %v", i, err) continue } - if !reflect.DeepEqual(testCase.Object, obj) { + if !api.Semantic.DeepDerivative(testCase.Object, obj) { t.Errorf("%d: expected %#v, got %#v", i, testCase.Object, obj) } if event != testCase.Type { diff --git a/plugin/pkg/scheduler/factory/factory_test.go b/plugin/pkg/scheduler/factory/factory_test.go index 4c58bee2914..455c2822038 100644 --- a/plugin/pkg/scheduler/factory/factory_test.go +++ b/plugin/pkg/scheduler/factory/factory_test.go @@ -195,7 +195,13 @@ func makeURL(suffix string) string { } func TestDefaultErrorFunc(t *testing.T) { - testPod := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}} + testPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}}, + DNSPolicy: api.DNSClusterFirst, + }, + } handler := util.FakeHandler{ StatusCode: 200, ResponseBody: runtime.EncodeOrDie(latest.Codec, testPod),