Factor out API defaulting from validation logic

Currently, the validation logic validates fields in an object and supply default
values wherever applies. This change factors out defaulting to a set of
defaulting callback functions for decoding (see #1502 for more discussion).

 * This change is based on pull request 2587.

 * Most defaulting has been migrated to defaults.go where the defaulting
   functions are added.

 * validation_test.go and converter_test.go have been adapted to not testing the
   default values.

 * Fixed all tests with that create invalid objects with the absence of
   defaulting logic.
This commit is contained in:
Yu-Ju Hong 2015-01-26 09:52:50 -08:00
parent 1ddb68d8d7
commit 4a72addaeb
40 changed files with 1059 additions and 384 deletions

View File

@ -319,6 +319,8 @@ func runSelfLinkTest(c *client.Client) {
Selector: map[string]string{ Selector: map[string]string{
"foo": "bar", "foo": "bar",
}, },
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
).Do().Into(&svc) ).Do().Into(&svc)
@ -381,6 +383,8 @@ func runAtomicPutTest(c *client.Client) {
Selector: map[string]string{ Selector: map[string]string{
"foo": "bar", "foo": "bar",
}, },
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
).Do().Into(&svc) ).Do().Into(&svc)
@ -521,8 +525,11 @@ func runServiceTest(client *client.Client) {
Ports: []api.Port{ Ports: []api.Port{
{ContainerPort: 1234}, {ContainerPort: 1234},
}, },
ImagePullPolicy: "PullIfNotPresent",
}, },
}, },
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
}, },
Status: api.PodStatus{ Status: api.PodStatus{
PodIP: "1.2.3.4", PodIP: "1.2.3.4",
@ -541,7 +548,9 @@ func runServiceTest(client *client.Client) {
Selector: map[string]string{ Selector: map[string]string{
"name": "thisisalonglabel", "name": "thisisalonglabel",
}, },
Port: 8080, Port: 8080,
Protocol: "TCP",
SessionAffinity: "None",
}, },
} }
_, err = client.Services(api.NamespaceDefault).Create(&svc1) _, err = client.Services(api.NamespaceDefault).Create(&svc1)
@ -558,7 +567,9 @@ func runServiceTest(client *client.Client) {
Selector: map[string]string{ Selector: map[string]string{
"name": "thisisalonglabel", "name": "thisisalonglabel",
}, },
Port: 8080, Port: 8080,
Protocol: "TCP",
SessionAffinity: "None",
}, },
} }
_, err = client.Services(api.NamespaceDefault).Create(&svc2) _, err = client.Services(api.NamespaceDefault).Create(&svc2)

View File

@ -47,14 +47,6 @@ func init() {
out.Spec.DNSPolicy = in.DNSPolicy out.Spec.DNSPolicy = in.DNSPolicy
out.Name = in.ID out.Name = in.ID
out.UID = in.UUID 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 return nil
}, },
func(in *BoundPod, out *ContainerManifest, s conversion.Scope) error { func(in *BoundPod, out *ContainerManifest, s conversion.Scope) error {
@ -65,12 +57,6 @@ func init() {
out.Version = "v1beta2" out.Version = "v1beta2"
out.ID = in.Name out.ID = in.Name
out.UUID = in.UID out.UUID = in.UID
for i := range out.Containers {
ctr := &out.Containers[i]
if len(ctr.TerminationMessagePath) == 0 {
ctr.TerminationMessagePath = TerminationMessagePathDefault
}
}
return nil return nil
}, },

View File

@ -158,6 +158,10 @@ func TestEncode_Ptr(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"name": "foo"}, Labels: map[string]string{"name": "foo"},
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
obj := runtime.Object(pod) obj := runtime.Object(pod)
data, err := latest.Codec.Encode(obj) data, err := latest.Codec.Encode(obj)
@ -169,7 +173,7 @@ func TestEncode_Ptr(t *testing.T) {
t.Fatalf("Got wrong type") t.Fatalf("Got wrong type")
} }
if !api.Semantic.DeepEqual(obj2, pod) { 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)
} }
} }

View File

@ -32,6 +32,14 @@ import (
"speter.net/go/exp/math/dec/inf" "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. // FuzzerFor can randomly populate api objects that are destined for version.
func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer { func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
f := fuzz.New().NilChance(.5).NumElements(1, 1) 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} statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
*j = statuses[c.Rand.Intn(len(statuses))] *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) { 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) 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) c.Fuzz(&j.Selector)
j.Replicas = int(c.RandUint64()) 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} policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent}
*p = policies[c.Rand.Intn(len(policies))] *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 return f
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1beta1 package v1beta1
import ( import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
@ -24,8 +26,8 @@ import (
func init() { func init() {
api.Scheme.AddDefaultingFuncs( api.Scheme.AddDefaultingFuncs(
func(obj *Volume) { func(obj *Volume) {
if obj.Source == nil || util.AllPtrFieldsNil(obj.Source) { if util.AllPtrFieldsNil(&obj.Source) {
obj.Source = &VolumeSource{ obj.Source = VolumeSource{
EmptyDir: &EmptyDir{}, EmptyDir: &EmptyDir{},
} }
} }
@ -36,13 +38,18 @@ func init() {
} }
}, },
func(obj *Container) { func(obj *Container) {
// TODO: delete helper functions that touch this
if obj.ImagePullPolicy == "" { 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 == "" { if obj.TerminationMessagePath == "" {
// TODO: fix other code that sets this obj.TerminationMessagePath = TerminationMessagePathDefault
obj.TerminationMessagePath = api.TerminationMessagePathDefault
} }
}, },
func(obj *RestartPolicy) { func(obj *RestartPolicy) {
@ -54,8 +61,19 @@ func init() {
if obj.Protocol == "" { if obj.Protocol == "" {
obj.Protocol = ProtocolTCP 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

View File

@ -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 := &current.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 := &current.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)
}
}

View File

@ -71,6 +71,11 @@ type ContainerManifestList struct {
Items []ContainerManifest `json:"items" description:"list of pod container manifests"` 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. // Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct { type Volume struct {
// Required: This must be a DNS_LABEL. Each volume in a pod must have // Required: This must be a DNS_LABEL. Each volume in a pod must have

View File

@ -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
}
},
)
}

View File

@ -252,6 +252,11 @@ type Container struct {
Capabilities Capabilities `json:"capabilities,omitempty" description:"capabilities for container"` 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 // Handler defines a specific action that should be taken
// TODO: pass structured data to these actions, and document that data here. // TODO: pass structured data to these actions, and document that data here.
type Handler struct { type Handler struct {

View File

@ -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
}
},
)
}

View File

@ -341,6 +341,11 @@ type ResourceRequirementSpec struct {
Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"` 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. // Container represents a single container that is expected to be run on the host.
type Container struct { type Container struct {
// Required: This must be a DNS_LABEL. Each container in a pod must // Required: This must be a DNS_LABEL. Each container in a pod must

View File

@ -103,18 +103,17 @@ var invalidPod2 = `{
"manifest": { "manifest": {
"version": "v1beta1", "version": "v1beta1",
"id": "apache-php", "id": "apache-php",
"containers": [ "containers": [{
{ "name": "apache-php",
"name": "apache-php", "image": "php:5.6.2-apache",
"image": "php:5.6.2-apache", "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }],
"ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}]
"volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] }]
}
]
} }
}, },
"labels": { "name": "apache-php" }, "labels": { "name": "apache-php" },
"restartPolicy": {"always": {}}, "restartPolicy": {"always": {}},
"dnsPolicy": "ClusterFirst",
"volumes": [ "volumes": [
"name": "shared-disk", "name": "shared-disk",
"source": { "source": {
@ -134,18 +133,17 @@ var invalidPod3 = `{
"manifest": { "manifest": {
"version": "v1beta1", "version": "v1beta1",
"id": "apache-php", "id": "apache-php",
"containers": [ "containers": [{
{ "name": "apache-php",
"name": "apache-php", "image": "php:5.6.2-apache",
"image": "php:5.6.2-apache", "ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }],
"ports": [{ "name": "apache", "containerPort": 80, "hostPort":"13380", "protocol":"TCP" }], "volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}]
"volumeMounts": [{"name": "shared-disk","mountPath": "/var/www/html", "readOnly": false}] }]
}
]
} }
}, },
"labels": { "name": "apache-php" }, "labels": { "name": "apache-php" },
"restartPolicy": {"always": {}}, "restartPolicy": {"always": {}},
"dnsPolicy": "ClusterFirst",
"volumes": [ "volumes": [
{ {
"name": "shared-disk", "name": "shared-disk",

View File

@ -189,8 +189,7 @@ func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ValidationError
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allNames := util.StringSet{} allNames := util.StringSet{}
for i := range volumes { for i, vol := range volumes {
vol := &volumes[i] // so we can set default values
el := validateSource(&vol.Source).Prefix("source") el := validateSource(&vol.Source).Prefix("source")
if len(vol.Name) == 0 { if len(vol.Name) == 0 {
el = append(el, errs.NewFieldRequired("name", vol.Name)) el = append(el, errs.NewFieldRequired("name", vol.Name))
@ -227,10 +226,7 @@ func validateSource(source *api.VolumeSource) errs.ValidationErrorList {
numVolumes++ numVolumes++
allErrs = append(allErrs, validateGCEPersistentDisk(source.GCEPersistentDisk).Prefix("persistentDisk")...) allErrs = append(allErrs, validateGCEPersistentDisk(source.GCEPersistentDisk).Prefix("persistentDisk")...)
} }
if numVolumes == 0 { if numVolumes != 1 {
// TODO: Enforce that a source is set once we deprecate the implied form.
source.EmptyDir = &api.EmptyDir{}
} else if numVolumes != 1 {
allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required")) allErrs = append(allErrs, errs.NewFieldInvalid("", source, "exactly 1 volume type is required"))
} }
return allErrs return allErrs
@ -272,9 +268,8 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allNames := util.StringSet{} allNames := util.StringSet{}
for i := range ports { for i, port := range ports {
pErrs := errs.ValidationErrorList{} pErrs := errs.ValidationErrorList{}
port := &ports[i] // so we can set default values
if len(port.Name) > 0 { if len(port.Name) > 0 {
if len(port.Name) > 63 || !util.IsDNSLabel(port.Name) { if len(port.Name) > 63 || !util.IsDNSLabel(port.Name) {
pErrs = append(pErrs, errs.NewFieldInvalid("name", 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, "")) pErrs = append(pErrs, errs.NewFieldInvalid("hostPort", port.HostPort, ""))
} }
if len(port.Protocol) == 0 { 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))) { } else if !supportedPortProtocols.Has(strings.ToUpper(string(port.Protocol))) {
pErrs = append(pErrs, errs.NewFieldNotSupported("protocol", 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 { func validateEnv(vars []api.EnvVar) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for i := range vars { for i, ev := range vars {
vErrs := errs.ValidationErrorList{} vErrs := errs.ValidationErrorList{}
ev := &vars[i] // so we can set default values
if len(ev.Name) == 0 { if len(ev.Name) == 0 {
vErrs = append(vErrs, errs.NewFieldRequired("name", ev.Name)) 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 { func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for i := range mounts { for i, mnt := range mounts {
mErrs := errs.ValidationErrorList{} mErrs := errs.ValidationErrorList{}
mnt := &mounts[i] // so we can set default values
if len(mnt.Name) == 0 { if len(mnt.Name) == 0 {
mErrs = append(mErrs, errs.NewFieldRequired("name", mnt.Name)) mErrs = append(mErrs, errs.NewFieldRequired("name", mnt.Name))
} else if !volumes.Has(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 { func AccumulateUniquePorts(containers []api.Container, accumulator map[int]bool, extract func(*api.Port) int) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for ci := range containers { for ci, ctr := range containers {
cErrs := errs.ValidationErrorList{} cErrs := errs.ValidationErrorList{}
ctr := &containers[ci]
for pi := range ctr.Ports { for pi := range ctr.Ports {
port := extract(&ctr.Ports[pi]) port := extract(&ctr.Ports[pi])
if port == 0 { if port == 0 {
@ -413,22 +405,14 @@ func validateLifecycle(lifecycle *api.Lifecycle) errs.ValidationErrorList {
return allErrs return allErrs
} }
// TODO(dchen1107): Move this along with other defaulting values func validatePullPolicy(ctr *api.Container) errs.ValidationErrorList {
func validatePullPolicyWithDefault(ctr *api.Container) errs.ValidationErrorList {
allErrors := errs.ValidationErrorList{} allErrors := errs.ValidationErrorList{}
switch ctr.ImagePullPolicy { 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: case api.PullAlways, api.PullIfNotPresent, api.PullNever:
break break
case "":
allErrors = append(allErrors, errs.NewFieldRequired("", ctr.ImagePullPolicy))
default: default:
allErrors = append(allErrors, errs.NewFieldNotSupported("", ctr.ImagePullPolicy)) allErrors = append(allErrors, errs.NewFieldNotSupported("", ctr.ImagePullPolicy))
} }
@ -440,9 +424,8 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allNames := util.StringSet{} allNames := util.StringSet{}
for i := range containers { for i, ctr := range containers {
cErrs := errs.ValidationErrorList{} cErrs := errs.ValidationErrorList{}
ctr := &containers[i] // so we can set default values
capabilities := capabilities.Get() capabilities := capabilities.Get()
if len(ctr.Name) == 0 { if len(ctr.Name) == 0 {
cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name)) 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, validatePorts(ctr.Ports).Prefix("ports")...)
cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...) cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...)
cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...) cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...)
cErrs = append(cErrs, validatePullPolicyWithDefault(ctr).Prefix("pullPolicy")...) cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...)
cErrs = append(cErrs, validateResourceRequirements(ctr).Prefix("resources")...) cErrs = append(cErrs, validateResourceRequirements(&ctr).Prefix("resources")...)
allErrs = append(allErrs, cErrs.PrefixIndex(i)...) allErrs = append(allErrs, cErrs.PrefixIndex(i)...)
} }
// Check for colliding ports across all containers. // Check for colliding ports across all containers.
@ -513,10 +496,7 @@ func validateRestartPolicy(restartPolicy *api.RestartPolicy) errs.ValidationErro
if restartPolicy.Never != nil { if restartPolicy.Never != nil {
numPolicies++ numPolicies++
} }
if numPolicies == 0 { if numPolicies != 1 {
restartPolicy.Always = &api.RestartPolicyAlways{}
}
if numPolicies > 1 {
allErrors = append(allErrors, errs.NewFieldInvalid("", restartPolicy, "only 1 policy is allowed")) allErrors = append(allErrors, errs.NewFieldInvalid("", restartPolicy, "only 1 policy is allowed"))
} }
return allErrors return allErrors
@ -525,11 +505,10 @@ func validateRestartPolicy(restartPolicy *api.RestartPolicy) errs.ValidationErro
func validateDNSPolicy(dnsPolicy *api.DNSPolicy) errs.ValidationErrorList { func validateDNSPolicy(dnsPolicy *api.DNSPolicy) errs.ValidationErrorList {
allErrors := errs.ValidationErrorList{} allErrors := errs.ValidationErrorList{}
switch *dnsPolicy { 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: case api.DNSClusterFirst, api.DNSDefault:
break break
case "":
allErrors = append(allErrors, errs.NewFieldRequired("", *dnsPolicy))
default: default:
allErrors = append(allErrors, errs.NewFieldNotSupported("", dnsPolicy)) 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, "")) allErrs = append(allErrs, errs.NewFieldInvalid("spec.port", service.Spec.Port, ""))
} }
if len(service.Spec.Protocol) == 0 { 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))) { } else if !supportedPortProtocols.Has(strings.ToUpper(string(service.Spec.Protocol))) {
allErrs = append(allErrs, errs.NewFieldNotSupported("spec.protocol", 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 == "" { 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)) { } else if !supportedSessionAffinityType.Has(string(service.Spec.SessionAffinity)) {
allErrs = append(allErrs, errs.NewFieldNotSupported("spec.sessionAffinity", service.Spec.SessionAffinity)) allErrs = append(allErrs, errs.NewFieldNotSupported("spec.sessionAffinity", service.Spec.SessionAffinity))
} }

View File

@ -137,7 +137,7 @@ func TestValidateAnnotations(t *testing.T) {
func TestValidateVolumes(t *testing.T) { func TestValidateVolumes(t *testing.T) {
successCase := []api.Volume{ 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: "123", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path2"}}},
{Name: "abc-123", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path3"}}}, {Name: "abc-123", Source: api.VolumeSource{HostPath: &api.HostPath{"/mnt/path3"}}},
{Name: "empty", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}}, {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") { if len(names) != 6 || !names.HasAll("abc", "123", "abc-123", "empty", "gcepd", "gitrepo") {
t.Errorf("wrong names result: %v", names) t.Errorf("wrong names result: %v", names)
} }
emptyVS := api.VolumeSource{EmptyDir: &api.EmptyDir{}}
errorCases := map[string]struct { errorCases := map[string]struct {
V []api.Volume V []api.Volume
T errors.ValidationErrorType T errors.ValidationErrorType
F string F string
}{ }{
"zero-length name": {[]api.Volume{{Name: ""}}, errors.ValidationErrorTypeRequired, "[0].name"}, "zero-length name": {[]api.Volume{{Name: "", Source: emptyVS}}, errors.ValidationErrorTypeRequired, "[0].name"},
"name > 63 characters": {[]api.Volume{{Name: strings.Repeat("a", 64)}}, errors.ValidationErrorTypeInvalid, "[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"}}, 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"}, {Name: "abc"}}, errors.ValidationErrorTypeDuplicate, "[1].name"}, "name not unique": {[]api.Volume{{Name: "abc", Source: emptyVS}, {Name: "abc", Source: emptyVS}}, errors.ValidationErrorTypeDuplicate, "[1].name"},
} }
for k, v := range errorCases { for k, v := range errorCases {
_, errs := validateVolumes(v.V) _, errs := validateVolumes(v.V)
@ -182,42 +182,39 @@ func TestValidateVolumes(t *testing.T) {
func TestValidatePorts(t *testing.T) { func TestValidatePorts(t *testing.T) {
successCase := []api.Port{ successCase := []api.Port{
{Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"}, {Name: "abc", ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
{Name: "123", ContainerPort: 81, HostPort: 81},
{Name: "easy", ContainerPort: 82, Protocol: "TCP"}, {Name: "easy", ContainerPort: 82, Protocol: "TCP"},
{Name: "as", ContainerPort: 83, Protocol: "UDP"}, {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"}, {Name: "baby-you-and-me", ContainerPort: 82, Protocol: "tcp"},
{ContainerPort: 85}, {ContainerPort: 85, Protocol: "TCP"},
} }
if errs := validatePorts(successCase); len(errs) != 0 { if errs := validatePorts(successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
nonCanonicalCase := []api.Port{ nonCanonicalCase := []api.Port{
{ContainerPort: 80}, {ContainerPort: 80, Protocol: "TCP"},
} }
if errs := validatePorts(nonCanonicalCase); len(errs) != 0 { if errs := validatePorts(nonCanonicalCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs) 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 { errorCases := map[string]struct {
P []api.Port P []api.Port
T errors.ValidationErrorType T errors.ValidationErrorType
F string F string
}{ }{
"name > 63 characters": {[]api.Port{{Name: strings.Repeat("a", 64), 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}}, 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 not unique": {[]api.Port{
{Name: "abc", ContainerPort: 80}, {Name: "abc", ContainerPort: 80, Protocol: "TCP"},
{Name: "abc", ContainerPort: 81}, {Name: "abc", ContainerPort: 81, Protocol: "TCP"},
}, errors.ValidationErrorTypeDuplicate, "[1].name"}, }, errors.ValidationErrorTypeDuplicate, "[1].name"},
"zero container port": {[]api.Port{{ContainerPort: 0}}, errors.ValidationErrorTypeRequired, "[0].containerPort"}, "zero container port": {[]api.Port{{ContainerPort: 0, Protocol: "TCP"}}, errors.ValidationErrorTypeRequired, "[0].containerPort"},
"invalid container port": {[]api.Port{{ContainerPort: 65536}}, errors.ValidationErrorTypeInvalid, "[0].containerPort"}, "invalid container port": {[]api.Port{{ContainerPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].containerPort"},
"invalid host port": {[]api.Port{{ContainerPort: 80, HostPort: 65536}}, errors.ValidationErrorTypeInvalid, "[0].hostPort"}, "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"}, "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 { for k, v := range errorCases {
errs := validatePorts(v.P) errs := validatePorts(v.P)
@ -311,14 +308,10 @@ func TestValidatePullPolicy(t *testing.T) {
api.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"}, api.Container{Name: "abc-1234", Image: "image", ImagePullPolicy: "Never"},
api.PullNever, 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 { for k, v := range testCases {
ctr := &v.Container ctr := &v.Container
errs := validatePullPolicyWithDefault(ctr) errs := validatePullPolicy(ctr)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("case[%s] expected success, got %#v", k, errs) t.Errorf("case[%s] expected success, got %#v", k, errs)
} }
@ -343,9 +336,9 @@ func TestValidateContainers(t *testing.T) {
}) })
successCase := []api.Container{ successCase := []api.Container{
{Name: "abc", Image: "image"}, {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "123", Image: "image"}, {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "abc-123", Image: "image"}, {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent"},
{ {
Name: "life-123", Name: "life-123",
Image: "image", Image: "image",
@ -354,6 +347,7 @@ func TestValidateContainers(t *testing.T) {
Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, Exec: &api.ExecAction{Command: []string{"ls", "-l"}},
}, },
}, },
ImagePullPolicy: "IfNotPresent",
}, },
{ {
Name: "resources-test", Name: "resources-test",
@ -365,8 +359,9 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName("my.org/resource"): resource.MustParse("10m"), 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 { if errs := validateContainers(successCase, volumes); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
@ -467,7 +462,6 @@ func TestValidateContainers(t *testing.T) {
func TestValidateRestartPolicy(t *testing.T) { func TestValidateRestartPolicy(t *testing.T) {
successCases := []api.RestartPolicy{ successCases := []api.RestartPolicy{
{},
{Always: &api.RestartPolicyAlways{}}, {Always: &api.RestartPolicyAlways{}},
{OnFailure: &api.RestartPolicyOnFailure{}}, {OnFailure: &api.RestartPolicyOnFailure{}},
{Never: &api.RestartPolicyNever{}}, {Never: &api.RestartPolicyNever{}},
@ -479,6 +473,7 @@ func TestValidateRestartPolicy(t *testing.T) {
} }
errorCases := []api.RestartPolicy{ errorCases := []api.RestartPolicy{
{},
{Always: &api.RestartPolicyAlways{}, Never: &api.RestartPolicyNever{}}, {Always: &api.RestartPolicyAlways{}, Never: &api.RestartPolicyNever{}},
{Never: &api.RestartPolicyNever{}, OnFailure: &api.RestartPolicyOnFailure{}}, {Never: &api.RestartPolicyNever{}, OnFailure: &api.RestartPolicyOnFailure{}},
} }
@ -487,19 +482,10 @@ func TestValidateRestartPolicy(t *testing.T) {
t.Errorf("expected failure for %d", k) 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) { 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 { for _, policy := range successCases {
if errs := validateDNSPolicy(&policy); len(errs) != 0 { if errs := validateDNSPolicy(&policy); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
@ -516,9 +502,12 @@ func TestValidateDNSPolicy(t *testing.T) {
func TestValidateManifest(t *testing.T) { func TestValidateManifest(t *testing.T) {
successCases := []api.ContainerManifest{ successCases := []api.ContainerManifest{
{Version: "v1beta1", ID: "abc"}, {Version: "v1beta1", ID: "abc", RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
{Version: "v1beta2", ID: "123"}, DNSPolicy: api.DNSClusterFirst},
{Version: "V1BETA1", ID: "abc.123.do-re-mi"}, {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", Version: "v1beta1",
ID: "abc", ID: "abc",
@ -537,9 +526,9 @@ func TestValidateManifest(t *testing.T) {
}, },
}, },
Ports: []api.Port{ Ports: []api.Port{
{Name: "p1", ContainerPort: 80, HostPort: 8080}, {Name: "p1", ContainerPort: 80, HostPort: 8080, Protocol: "TCP"},
{Name: "p2", ContainerPort: 81}, {Name: "p2", ContainerPort: 81, Protocol: "TCP"},
{ContainerPort: 82}, {ContainerPort: 82, Protocol: "TCP"},
}, },
Env: []api.EnvVar{ Env: []api.EnvVar{
{Name: "ev1", Value: "val1"}, {Name: "ev1", Value: "val1"},
@ -550,8 +539,11 @@ func TestValidateManifest(t *testing.T) {
{Name: "vol1", MountPath: "/foo"}, {Name: "vol1", MountPath: "/foo"},
{Name: "vol1", MountPath: "/bar"}, {Name: "vol1", MountPath: "/bar"},
}, },
ImagePullPolicy: "IfNotPresent",
}, },
}, },
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
}, },
} }
for _, manifest := range successCases { for _, manifest := range successCases {
@ -583,22 +575,23 @@ func TestValidateManifest(t *testing.T) {
func TestValidatePodSpec(t *testing.T) { func TestValidatePodSpec(t *testing.T) {
successCases := []api.PodSpec{ successCases := []api.PodSpec{
{}, // empty is valid, if not very useful */
{ // Populate basic fields, leave defaults for most. { // Populate basic fields, leave defaults for most.
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{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,
}, },
{ // Populate all fields. { // Populate all fields.
Volumes: []api.Volume{ 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{}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
NodeSelector: map[string]string{ NodeSelector: map[string]string{
"key": "value", "key": "value",
}, },
Host: "foobar", Host: "foobar",
DNSPolicy: api.DNSClusterFirst,
}, },
} }
for i := range successCases { for i := range successCases {
@ -623,38 +616,26 @@ func TestValidatePodSpec(t *testing.T) {
t.Errorf("expected failure for %q", k) 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) { func TestValidatePod(t *testing.T) {
successCases := []api.Pod{ successCases := []api.Pod{
{ // Mostly empty.
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"},
},
{ // Basic fields. { // Basic fields.
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{ Spec: api.PodSpec{
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{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,
}, },
}, },
{ // Just about everything. { // Just about everything.
ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
Spec: api.PodSpec{ Spec: api.PodSpec{
Volumes: []api.Volume{ 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{}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst, DNSPolicy: api.DNSClusterFirst,
NodeSelector: map[string]string{ NodeSelector: map[string]string{
@ -896,21 +877,27 @@ func TestValidateBoundPods(t *testing.T) {
successCases := []api.BoundPod{ successCases := []api.BoundPod{
{ // Mostly empty. { // Mostly empty.
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "ns"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}, },
{ // Basic fields. { // Basic fields.
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{ Spec: api.PodSpec{
Volumes: []api.Volume{{Name: "vol"}}, Volumes: []api.Volume{{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,
}, },
}, },
{ // Just about everything. { // Just about everything.
ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
Spec: api.PodSpec{ Spec: api.PodSpec{
Volumes: []api.Volume{ 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{}}, RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst, DNSPolicy: api.DNSClusterFirst,
NodeSelector: map[string]string{ NodeSelector: map[string]string{
@ -955,20 +942,50 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the ID is missing. // Should fail because the ID is missing.
numErrs: 1, 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", name: "missing namespace",
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the Namespace is missing. // Should fail because the Namespace is missing.
@ -979,8 +996,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "-123abc", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "-123abc", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the ID is invalid. // Should fail because the ID is invalid.
@ -995,8 +1014,10 @@ func TestValidateService(t *testing.T) {
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the Base value for generation is invalid // Should fail because the Base value for generation is invalid
@ -1011,8 +1032,10 @@ func TestValidateService(t *testing.T) {
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the generate name type is invalid. // Should fail because the generate name type is invalid.
@ -1023,7 +1046,9 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ 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. // Should fail because the port number is missing/invalid.
@ -1034,8 +1059,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 66536, Port: 66536,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should fail because the port number is invalid. // Should fail because the port number is invalid.
@ -1046,9 +1073,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "INVALID", Protocol: "INVALID",
SessionAffinity: "None",
}, },
}, },
// Should fail because the protocol is invalid. // Should fail because the protocol is invalid.
@ -1059,7 +1087,9 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// Should be ok because the selector is missing. // Should be ok because the selector is missing.
@ -1070,9 +1100,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP", Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
numErrs: 0, numErrs: 0,
@ -1082,9 +1113,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "UDP", Protocol: "UDP",
SessionAffinity: "None",
}, },
}, },
numErrs: 0, numErrs: 0,
@ -1094,8 +1126,10 @@ func TestValidateService(t *testing.T) {
svc: api.Service{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "UDP",
SessionAffinity: "None",
}, },
}, },
numErrs: 0, numErrs: 0,
@ -1108,13 +1142,15 @@ func TestValidateService(t *testing.T) {
Port: 80, Port: 80,
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
existing: api.ServiceList{ existing: api.ServiceList{
Items: []api.Service{ Items: []api.Service{
{ {
ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, 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, Port: 80,
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
existing: api.ServiceList{ existing: api.ServiceList{
Items: []api.Service{ Items: []api.Service{
{ {
ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, 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{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 80, Port: 80,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
existing: api.ServiceList{ existing: api.ServiceList{
Items: []api.Service{ Items: []api.Service{
{ {
ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, 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{ svc: api.Service{
ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "abc123", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 80, Port: 80,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
existing: api.ServiceList{ existing: api.ServiceList{
Items: []api.Service{ Items: []api.Service{
{ {
ObjectMeta: api.ObjectMeta{Name: "def123", Namespace: api.NamespaceDefault}, 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{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
numErrs: 1, numErrs: 1,
@ -1205,7 +1249,9 @@ func TestValidateService(t *testing.T) {
}, },
}, },
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
numErrs: 1, numErrs: 1,
@ -1218,8 +1264,10 @@ func TestValidateService(t *testing.T) {
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar"}, Selector: map[string]string{"foo": "bar", "NoUppercaseOrSpecialCharsLike=Equals": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
numErrs: 1, numErrs: 1,
@ -1238,17 +1286,16 @@ func TestValidateService(t *testing.T) {
svc := api.Service{ svc := api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 8675, Port: 8675,
Selector: map[string]string{"foo": "bar"}, Selector: map[string]string{"foo": "bar"},
Protocol: "TCP",
SessionAffinity: "None",
}, },
} }
errs := ValidateService(&svc) errs := ValidateService(&svc)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("Unexpected non-zero error list: %#v", errs) 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) { func TestValidateReplicationController(t *testing.T) {
@ -1258,6 +1305,10 @@ func TestValidateReplicationController(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector,
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}, },
} }
invalidVolumePodTemplate := api.PodTemplate{ invalidVolumePodTemplate := api.PodTemplate{
@ -1271,9 +1322,8 @@ func TestValidateReplicationController(t *testing.T) {
invalidPodTemplate := api.PodTemplate{ invalidPodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{ Spec: api.PodTemplateSpec{
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{ RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
Always: &api.RestartPolicyAlways{}, DNSPolicy: api.DNSClusterFirst,
},
}, },
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: invalidSelector, Labels: invalidSelector,
@ -1400,6 +1450,7 @@ func TestValidateReplicationController(t *testing.T) {
RestartPolicy: api.RestartPolicy{ RestartPolicy: api.RestartPolicy{
OnFailure: &api.RestartPolicyOnFailure{}, OnFailure: &api.RestartPolicyOnFailure{},
}, },
DNSPolicy: api.DNSClusterFirst,
}, },
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector,
@ -1419,6 +1470,7 @@ func TestValidateReplicationController(t *testing.T) {
RestartPolicy: api.RestartPolicy{ RestartPolicy: api.RestartPolicy{
Never: &api.RestartPolicyNever{}, Never: &api.RestartPolicyNever{},
}, },
DNSPolicy: api.DNSClusterFirst,
}, },
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector,

View File

@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient {
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) { func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
c.ValidateCommon(t, err) 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) t.Errorf("bad response for request %#v: expected %#v, got %#v", c.Request, c.Response.Body, received)
} }
} }

View File

@ -26,7 +26,7 @@ import (
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"os" "os"
"reflect" // "reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -62,7 +62,7 @@ func TestRequestWithErrorWontChange(t *testing.T) {
if changed != &r { if changed != &r {
t.Errorf("returned request should point to the same object") 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) t.Errorf("expected %#v, got %#v", &original, changed)
} }
} }
@ -148,7 +148,7 @@ func TestRequestParseSelectorParam(t *testing.T) {
func TestRequestParam(t *testing.T) { func TestRequestParam(t *testing.T) {
r := (&Request{}).Param("foo", "a") 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) t.Errorf("should have set a param: %#v", r)
} }
} }
@ -218,7 +218,7 @@ func TestTransformResponse(t *testing.T) {
if hasErr != test.Error { if hasErr != test.Error {
t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) 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) t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response)
} }
if test.Created != created { if test.Created != created {
@ -491,7 +491,7 @@ func TestDoRequestNewWay(t *testing.T) {
} }
if obj == nil { if obj == nil {
t.Error("nil obj") 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) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
fakeHandler.ValidateRequest(t, "/api/v1beta2/foo/bar/baz?labels=name%3Dfoo&timeout=1s", "POST", &reqBody) 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 { if obj == nil {
t.Error("nil obj") 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) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
tmpStr := string(reqBodyExpected) tmpStr := string(reqBodyExpected)
@ -562,7 +562,7 @@ func TestDoRequestNewWayObj(t *testing.T) {
} }
if obj == nil { if obj == nil {
t.Error("nil obj") 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) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
tmpStr := string(reqBodyExpected) tmpStr := string(reqBodyExpected)
@ -611,7 +611,7 @@ func TestDoRequestNewWayFile(t *testing.T) {
} }
if obj == nil { if obj == nil {
t.Error("nil obj") 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) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
if wasCreated { if wasCreated {
@ -653,7 +653,7 @@ func TestWasCreated(t *testing.T) {
} }
if obj == nil { if obj == nil {
t.Error("nil obj") 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) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
if !wasCreated { if !wasCreated {
@ -790,7 +790,7 @@ func checkAuth(t *testing.T, expect *Config, r *http.Request) {
foundAuth, found := authFromReq(r) foundAuth, found := authFromReq(r)
if !found { if !found {
t.Errorf("no auth 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) 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 { if e, a := item.t, got.Type; e != a {
t.Errorf("Expected %v, got %v", 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) t.Errorf("Expected %v, got %v", e, a)
} }
} }

View File

@ -239,7 +239,7 @@ func TestCreateReplica(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %#v", err) 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.Logf("Body: %s", fakeHandler.RequestBody)
t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod) 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 var testControllerSpec api.ReplicationController
received := make(chan struct{}) received := make(chan struct{})
manager.syncHandler = func(controllerSpec api.ReplicationController) error { 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) t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec)
} }
close(received) close(received)

View File

@ -68,7 +68,6 @@ func NewConverter() *Converter {
return &Converter{ return &Converter{
conversionFuncs: map[typePair]reflect.Value{}, conversionFuncs: map[typePair]reflect.Value{},
defaultingFuncs: map[reflect.Type]reflect.Value{}, defaultingFuncs: map[reflect.Type]reflect.Value{},
funcs: map[typePair]reflect.Value{},
nameFunc: func(t reflect.Type) string { return t.Name() }, nameFunc: func(t reflect.Type) string { return t.Name() },
structFieldDests: map[typeNamePair][]typeNamePair{}, structFieldDests: map[typeNamePair][]typeNamePair{},
structFieldSources: 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. // used if recursive conversion calls are desired). It must return an error.
// //
// Example: // Example:
// c.RegisteConversionFuncr( // c.RegisteConversionFunc(
// func(in *Pod, out *v1beta1.Pod, s Scope) error { // func(in *Pod, out *v1beta1.Pod, s Scope) error {
// // conversion logic... // // conversion logic...
// return nil // 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. // defaultingFunc must take one parameters: a pointer to the input type.
// //
// Example: // Example:
// c.RegisteDefaultingFuncr( // c.RegisteDefaultingFunc(
// func(in *v1beta1.Pod) { // func(in *v1beta1.Pod) {
// // defaulting logic... // // defaulting logic...
// }) // })
@ -415,7 +414,6 @@ func (c *Converter) callCustom(sv, dv, custom reflect.Value, scope *scope) error
// one is registered. // one is registered.
func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error { func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
dt, st := dv.Type(), sv.Type() dt, st := dv.Type(), sv.Type()
// Apply default values. // Apply default values.
if fv, ok := c.defaultingFuncs[st]; ok { if fv, ok := c.defaultingFuncs[st]; ok {
if c.Debug != nil { if c.Debug != nil {

View File

@ -36,11 +36,11 @@ func TestConverter_DefaultConvert(t *testing.T) {
} }
c := NewConverter() c := NewConverter()
c.Debug = t 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, // Ensure conversion funcs can call DefaultConvert to get default behavior,
// then fixup remaining fields manually // 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 { if err := s.DefaultConvert(in, out, IgnoreMissingFields); err != nil {
return err return err
} }
@ -224,7 +224,7 @@ func TestConverter_MapElemAddr(t *testing.T) {
} }
c := NewConverter() c := NewConverter()
c.Debug = t c.Debug = t
err := c.Register( err := c.RegisterConversionFunc(
func(in *int, out *string, s Scope) error { func(in *int, out *string, s Scope) error {
*out = fmt.Sprintf("%v", *in) *out = fmt.Sprintf("%v", *in)
return nil return nil
@ -233,7 +233,7 @@ func TestConverter_MapElemAddr(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
err = c.Register( err = c.RegisterConversionFunc(
func(in *string, out *int, s Scope) error { func(in *string, out *int, s Scope) error {
if str, err := strconv.Atoi(*in); err != nil { if str, err := strconv.Atoi(*in); err != nil {
return err return err

View File

@ -103,27 +103,19 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
dataVersion = objVersion dataVersion = objVersion
} }
if objVersion == dataVersion { external, err := s.NewObject(dataVersion, dataKind)
// Easy case! if err != nil {
err = yaml.Unmarshal(data, obj) return err
if err != nil { }
return err // yaml is a superset of json, so we use it to decode here. That way,
} // we understand both.
} else { err = yaml.Unmarshal(data, external)
external, err := s.NewObject(dataVersion, dataKind) if err != nil {
if err != nil { return err
return err }
} err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
// yaml is a superset of json, so we use it to decode here. That way, if err != nil {
// we understand both. return err
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. // Version and Kind should be blank in memory.

View File

@ -233,3 +233,140 @@ func (e Equalities) DeepEqual(a1, a2 interface{}) bool {
} }
return e.deepValueEqual(v1, v2, make(map[visit]bool), 0) 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)
}

View File

@ -73,11 +73,17 @@ func TestParsePod(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "test pod"}, ObjectMeta: api.ObjectMeta{Name: "test pod"},
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{Name: "my container"}, {
Name: "my container",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
}, },
Volumes: []api.Volume{ 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) }, v1beta1.Codec, testParser)
} }
@ -96,6 +102,8 @@ func TestParseService(t *testing.T) {
Selector: map[string]string{ Selector: map[string]string{
"area": "staging", "area": "staging",
}, },
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, v1beta1.Codec, testParser) }, v1beta1.Codec, testParser)
} }
@ -109,11 +117,17 @@ func TestParseController(t *testing.T) {
Template: &api.PodTemplateSpec{ Template: &api.PodTemplateSpec{
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{Name: "my container"}, {
Name: "my container",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
}, },
Volumes: []api.Volume{ Volumes: []api.Volume{
{Name: "volume"}, {Name: "volume", Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}}},
}, },
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
}, },
}, },
}, },

View File

@ -71,6 +71,10 @@ func TestYAMLPrinterPrint(t *testing.T) {
obj := &api.Pod{ obj := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
buf.Reset() buf.Reset()
printer.PrintObj(obj, buf) printer.PrintObj(obj, buf)
@ -95,6 +99,10 @@ func TestIdentityPrinter(t *testing.T) {
obj := &api.Pod{ obj := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
buff.Reset() buff.Reset()
printer.PrintObj(obj, buff) printer.PrintObj(obj, buff)

View File

@ -42,9 +42,17 @@ func testData() (*api.PodList, *api.ServiceList) {
Items: []api.Pod{ Items: []api.Pod{
{ {
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, 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"}, 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{ Items: []api.Service{
{ {
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, 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", Namespace: "test",
ResourceVersion: "10", ResourceVersion: "10",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}, },
} }
events := []watch.Event{ events := []watch.Event{
@ -307,6 +323,10 @@ func watchTestData() ([]api.Pod, []watch.Event) {
Namespace: "test", Namespace: "test",
ResourceVersion: "11", 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", Namespace: "test",
ResourceVersion: "12", ResourceVersion: "12",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}, },
}, },
} }

View File

@ -42,6 +42,12 @@ func TestMerge(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", 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{ Spec: api.PodSpec{
Host: "bar", Host: "bar",
RestartPolicy: api.RestartPolicy{
Always: &api.RestartPolicyAlways{},
},
DNSPolicy: api.DNSClusterFirst,
}, },
}, },
}, },
@ -74,12 +84,18 @@ func TestMerge(t *testing.T) {
Spec: api.PodSpec{ Spec: api.PodSpec{
Volumes: []api.Volume{ 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") err := Merge(test.obj, test.fragment, "Pod")
if !test.expectErr { if !test.expectErr {
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} else if !reflect.DeepEqual(test.obj, test.expected) { } 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 { if test.expectErr && err == nil {

View File

@ -88,9 +88,17 @@ func testData() (*api.PodList, *api.ServiceList) {
Items: []api.Pod{ Items: []api.Pod{
{ {
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, 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"}, 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 { if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) 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()) 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 { if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) 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()) 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 { if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) 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()) t.Errorf("unexpected visited objects: %#v", test.Objects())
} }
} }
@ -458,7 +466,7 @@ func TestMultipleObject(t *testing.T) {
&svc.Items[0], &svc.Items[0],
}, },
} }
if !reflect.DeepEqual(expected, obj) { if !api.Semantic.DeepDerivative(expected, obj) {
t.Errorf("unexpected visited objects: %#v", 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 { if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos) 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()) 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) 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()) t.Errorf("unexpected visited objects: %#v", test.Objects())
} }
} }

View File

@ -162,11 +162,17 @@ func TestHelperCreate(t *testing.T) {
Req: expectPost, Req: expectPost,
}, },
{ {
Modify: true, Modify: true,
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, ExpectObject: &api.Pod{
Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Req: expectPost, 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 { for i, test := range tests {
@ -400,9 +406,14 @@ func TestHelperUpdate(t *testing.T) {
Req: expectPut, Req: expectPut,
}, },
{ {
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, ExpectObject: &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
Overwrite: true, Overwrite: true,
RespFunc: func(req *http.Request) (*http.Response, error) { RespFunc: func(req *http.Request) (*http.Response, error) {
if req.Method == "PUT" { if req.Method == "PUT" {

View File

@ -177,7 +177,7 @@ func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
// container updates are separated as UPDATE // container updates are separated as UPDATE
pod := podUpdate.Pods[0] 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) channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, 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 // container updates are separated as UPDATE
pod := podUpdate.Pods[0] 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) channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.SET, TestSource, 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 // an kubelet.ADD should be converted to kubelet.UPDATE
pod := CreateValidPod("foo", "new", "test") 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) podUpdate = CreatePodUpdate(kubelet.ADD, NoneSource, pod)
channel <- podUpdate channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, NoneSource, pod)) 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 // should be converted to an kubelet.ADD, kubelet.REMOVE, and kubelet.UPDATE
pod := CreateValidPod("foo2", "new", "test") 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")) podUpdate = CreatePodUpdate(kubelet.SET, NoneSource, pod, CreateValidPod("foo3", "new", ""), CreateValidPod("foo4", "new", "test"))
channel <- podUpdate channel <- podUpdate
expectPodUpdate(t, ch, expectPodUpdate(t, ch,

View File

@ -62,7 +62,6 @@ func ExampleManifestAndPod(id string) (v1beta1.ContainerManifest, api.BoundPod)
{ {
Name: "c" + id, Name: "c" + id,
Image: "foo", Image: "foo",
TerminationMessagePath: "/somepath",
}, },
}, },
Volumes: []api.Volume{ Volumes: []api.Volume{
@ -94,7 +93,7 @@ func TestUpdateOnNonExistentFile(t *testing.T) {
case got := <-ch: case got := <-ch:
update := got.(kubelet.PodUpdate) update := got.(kubelet.PodUpdate)
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) 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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
@ -137,15 +136,7 @@ func TestReadFromFile(t *testing.T) {
Namespace: "", Namespace: "",
SelfLink: "", SelfLink: "",
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}},
Containers: []api.Container{
{
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: api.PullAlways,
},
},
},
}) })
// There's no way to provide namespace in ContainerManifest, so // There's no way to provide namespace in ContainerManifest, so
@ -161,7 +152,7 @@ func TestReadFromFile(t *testing.T) {
} }
update.Pods[0].ObjectMeta.SelfLink = "" 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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
@ -191,15 +182,7 @@ func TestReadFromFileWithoutID(t *testing.T) {
Namespace: "", Namespace: "",
SelfLink: "", SelfLink: "",
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}},
Containers: []api.Container{
{
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: api.PullAlways,
},
},
},
}) })
if len(update.Pods[0].ObjectMeta.Name) == 0 { 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.Namespace = ""
update.Pods[0].ObjectMeta.SelfLink = "" 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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
@ -240,21 +223,13 @@ func TestReadV1Beta2FromFile(t *testing.T) {
Namespace: "", Namespace: "",
SelfLink: "", SelfLink: "",
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{Containers: []api.Container{{Image: "test/image"}}},
Containers: []api.Container{
{
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: api.PullAlways,
},
},
},
}) })
update.Pods[0].ObjectMeta.Namespace = "" update.Pods[0].ObjectMeta.Namespace = ""
update.Pods[0].ObjectMeta.SelfLink = "" 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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
@ -315,7 +290,7 @@ func TestExtractFromEmptyDir(t *testing.T) {
update := (<-ch).(kubelet.PodUpdate) update := (<-ch).(kubelet.PodUpdate)
expected := CreatePodUpdate(kubelet.SET, kubelet.FileSource) 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) 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(update.Pods))
sort.Sort(sortedPods(expected.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) t.Fatalf("Expected %#v, Got %#v", expected, update)
} }
for i := range update.Pods { for i := range update.Pods {

View File

@ -90,8 +90,10 @@ func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.I
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: servicePort, Port: servicePort,
// maintained by this code, not by the pod selector // maintained by this code, not by the pod selector
Selector: nil, Selector: nil,
PortalIP: serviceIP.String(), 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 // Kids, don't do this at home: this is a hack. There's no good way to call the business

View File

@ -126,6 +126,10 @@ func TestControllerDecode(t *testing.T) {
"name": "nginx", "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{ Spec: api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: "test", Name: "test",
Image: "test_image", Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
}, },
}, },
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
}, },
}, },
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package etcd package etcd
import ( import (
"reflect"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -441,6 +440,10 @@ func TestEtcdUpdatePodNotScheduled(t *testing.T) {
"foo": "bar", "foo": "bar",
}, },
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
err := registry.UpdatePod(ctx, &podIn) err := registry.UpdatePod(ctx, &podIn)
if err != nil { if err != nil {
@ -515,9 +518,13 @@ func TestEtcdUpdatePodScheduled(t *testing.T) {
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{ 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{ Status: api.PodStatus{
Host: "machine", Host: "machine",
@ -1338,6 +1345,8 @@ func TestEtcdUpdateService(t *testing.T) {
Selector: map[string]string{ Selector: map[string]string{
"baz": "bar", "baz": "bar",
}, },
Protocol: "TCP",
SessionAffinity: "None",
}, },
} }
err := registry.UpdateService(ctx, &testService) err := registry.UpdateService(ctx, &testService)
@ -1352,7 +1361,7 @@ func TestEtcdUpdateService(t *testing.T) {
// Clear modified indices before the equality test. // Clear modified indices before the equality test.
svc.ResourceVersion = "" svc.ResourceVersion = ""
testService.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) 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) 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) t.Errorf("Unexpected endpoints: %#v, expected %#v", e, a)
} }
} }
@ -1433,7 +1442,7 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
} }
var endpointsOut api.Endpoints var endpointsOut api.Endpoints
err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut) 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) t.Errorf("Unexpected endpoints: %#v, expected %#v", endpointsOut, endpoints)
} }
} }

View File

@ -19,7 +19,6 @@ package etcd
import ( import (
"fmt" "fmt"
"path" "path"
"reflect"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -146,7 +145,7 @@ func TestEtcdList(t *testing.T) {
continue 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) 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) 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)) 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) 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)) 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) 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)) 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) 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)) t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
} }
} }
@ -432,7 +431,7 @@ func TestEtcdWatch(t *testing.T) {
t.Fatalf("unexpected channel close") 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)) t.Errorf("difference: %s", util.ObjectDiff(e, a))
} }
} }

View File

@ -88,6 +88,10 @@ func TestCreatePodRegistryError(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
ch, err := storage.Create(ctx, pod) ch, err := storage.Create(ctx, pod)
@ -108,6 +112,10 @@ func TestCreatePodSetsIds(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
ch, err := storage.Create(ctx, pod) ch, err := storage.Create(ctx, pod)
@ -135,6 +143,10 @@ func TestCreatePodSetsUID(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
ch, err := storage.Create(ctx, pod) ch, err := storage.Create(ctx, pod)
@ -346,6 +358,10 @@ func TestPodDecode(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
body, err := latest.Codec.Encode(expected) body, err := latest.Codec.Encode(expected)
if err != nil { if err != nil {
@ -447,7 +463,12 @@ func TestCreatePod(t *testing.T) {
registry: podRegistry, registry: podRegistry,
podCache: &fakeCache{statusToReturn: &api.PodStatus{}}, 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" pod.Name = "foo"
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
channel, err := storage.Create(ctx, pod) channel, err := storage.Create(ctx, pod)
@ -470,6 +491,10 @@ func TestCreatePodWithConflictingNamespace(t *testing.T) {
storage := REST{} storage := REST{}
pod := &api.Pod{ pod := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -488,6 +513,10 @@ func TestUpdatePodWithConflictingNamespace(t *testing.T) {
storage := REST{} storage := REST{}
pod := &api.Pod{ pod := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -647,10 +676,13 @@ func TestCreate(t *testing.T) {
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
{ {
Name: "test1", Name: "test1",
Image: "foo", Image: "foo",
ImagePullPolicy: api.PullIfNotPresent,
}, },
}, },
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
}, },
}, },
// invalid // invalid

View File

@ -49,8 +49,10 @@ func TestServiceRegistryCreate(t *testing.T) {
svc := &api.Service{ svc := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -91,14 +93,18 @@ func TestServiceStorageValidatesCreate(t *testing.T) {
"empty ID": { "empty ID": {
ObjectMeta: api.ObjectMeta{Name: ""}, ObjectMeta: api.ObjectMeta{Name: ""},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
}, },
"empty selector": { "empty selector": {
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ 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{ c, err := storage.Update(ctx, &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz2"}, Selector: map[string]string{"bar": "baz2"},
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
}) })
if err != nil { if err != nil {
@ -164,15 +172,19 @@ func TestServiceStorageValidatesUpdate(t *testing.T) {
"empty ID": { "empty ID": {
ObjectMeta: api.ObjectMeta{Name: ""}, ObjectMeta: api.ObjectMeta{Name: ""},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
}, },
"invalid selector": { "invalid selector": {
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Port: 6502, Port: 6502,
Selector: map[string]string{"ThisSelectorFailsValidation": "ok"}, Selector: map[string]string{"ThisSelectorFailsValidation": "ok"},
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
}, },
} }
@ -199,6 +211,8 @@ func TestServiceRegistryExternalService(t *testing.T) {
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
c, _ := storage.Create(ctx, svc) c, _ := storage.Create(ctx, svc)
@ -228,6 +242,8 @@ func TestServiceRegistryExternalServiceError(t *testing.T) {
Port: 6502, Port: 6502,
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -250,7 +266,9 @@ func TestServiceRegistryDelete(t *testing.T) {
svc := &api.Service{ svc := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ 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) registry.CreateService(ctx, svc)
@ -275,6 +293,8 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
registry.CreateService(ctx, svc) registry.CreateService(ctx, svc)
@ -389,8 +409,10 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
svc1 := &api.Service{ svc1 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -407,8 +429,10 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
svc2 := &api.Service{ svc2 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "bar"}, ObjectMeta: api.ObjectMeta{Name: "bar"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}} }}
ctx = api.NewDefaultContext() ctx = api.NewDefaultContext()
c2, _ := rest.Create(ctx, svc2) c2, _ := rest.Create(ctx, svc2)
@ -424,9 +448,11 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
svc3 := &api.Service{ svc3 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "quux"}, ObjectMeta: api.ObjectMeta{Name: "quux"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
PortalIP: "1.2.3.93", PortalIP: "1.2.3.93",
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx = api.NewDefaultContext() ctx = api.NewDefaultContext()
@ -448,8 +474,10 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
svc1 := &api.Service{ svc1 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -469,8 +497,10 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
svc2 := &api.Service{ svc2 := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "bar"}, ObjectMeta: api.ObjectMeta{Name: "bar"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx = api.NewDefaultContext() ctx = api.NewDefaultContext()
@ -495,8 +525,10 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
svc := &api.Service{ svc := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -551,6 +583,8 @@ func TestServiceRegistryIPExternalLoadBalancer(t *testing.T) {
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
CreateExternalLoadBalancer: true, CreateExternalLoadBalancer: true,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -586,8 +620,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
svc := &api.Service{ svc := &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
@ -596,8 +632,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
svc = &api.Service{ svc = &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
c, _ = rest1.Create(ctx, svc) c, _ = rest1.Create(ctx, svc)
@ -610,8 +648,10 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
svc = &api.Service{ svc = &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: api.ProtocolTCP,
SessionAffinity: api.AffinityTypeNone,
}, },
} }
c, _ = rest2.Create(ctx, svc) c, _ = rest2.Create(ctx, svc)
@ -671,8 +711,10 @@ func TestCreate(t *testing.T) {
// valid // valid
&api.Service{ &api.Service{
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"bar": "baz"}, Selector: map[string]string{"bar": "baz"},
Port: 6502, Port: 6502,
Protocol: "TCP",
SessionAffinity: "None",
}, },
}, },
// invalid // invalid

View File

@ -26,6 +26,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/coreos/go-etcd/etcd" "github.com/coreos/go-etcd/etcd"
) )
@ -47,6 +48,12 @@ func init() {
scheme.AddKnownTypes("", &TestResource{}) scheme.AddKnownTypes("", &TestResource{})
scheme.AddKnownTypes("v1beta1", &TestResource{}) scheme.AddKnownTypes("v1beta1", &TestResource{})
codec = runtime.CodecFor(scheme, "v1beta1") 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) { func TestIsEtcdNotFound(t *testing.T) {
@ -94,10 +101,27 @@ func TestExtractToList(t *testing.T) {
expect := api.PodList{ expect := api.PodList{
ListMeta: api.ListMeta{ResourceVersion: "10"}, ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{ Items: []api.Pod{
// We expect items to be sorted by its name. {
{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"},
{ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, Spec: api.PodSpec{
{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, 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"}, ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{ Items: []api.Pod{
// We expect list to be sorted by directory (e.g. namespace) first, then by name. // 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: "baz", 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: "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{ expect := api.PodList{
ListMeta: api.ListMeta{ResourceVersion: "10"}, ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{ Items: []api.Pod{
{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, {
{ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"},
{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, 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) { func TestExtractObj(t *testing.T) {
fakeClient := NewFakeEtcdClient(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) fakeClient.Set("/some/key", runtime.EncodeOrDie(testapi.Codec(), &expect), 0)
helper := EtcdHelper{fakeClient, testapi.Codec(), versioner} helper := EtcdHelper{fakeClient, testapi.Codec(), versioner}
var got api.Pod var got api.Pod

View File

@ -18,7 +18,6 @@ package tools
import ( import (
"fmt" "fmt"
"reflect"
"testing" "testing"
"time" "time"
@ -123,7 +122,7 @@ func TestWatchInterpretations(t *testing.T) {
if e, a := item.expectType, event.Type; e != a { if e, a := item.expectType, event.Type; e != a {
t.Errorf("'%v - %v': expected %v, got %v", name, action, 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) 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 { if e, a := watch.Added, event.Type; e != a {
t.Errorf("Expected %v, got %v", 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) 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) t.Errorf("%s: expected type %v, got %v", k, e, a)
break 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) t.Errorf("%s: expected type %v, got %v", k, e, a)
break 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) t.Errorf("%s: expected pod with resource version %v, Got %#v", k, testCase.ExpectedVersion, actualPod)
} }
pod.ResourceVersion = testCase.ExpectedVersion 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) t.Errorf("%s: expected %v, got %v", k, e, a)
} }
watching.Stop() watching.Stop()
@ -515,7 +514,7 @@ func TestWatchListFromZeroIndex(t *testing.T) {
t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod) t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod)
} }
pod.ResourceVersion = "1" 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) t.Errorf("Expected %v, got %v", e, a)
} }
} }

View File

@ -19,7 +19,6 @@ package json
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"reflect"
"testing" "testing"
"time" "time"
@ -58,7 +57,7 @@ func TestDecoder(t *testing.T) {
if e, a := eventType, action; e != a { if e, a := eventType, action; e != a {
t.Errorf("Expected %v, got %v", 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.Errorf("Expected %v, got %v", e, a)
} }
t.Logf("Exited read") t.Logf("Exited read")

View File

@ -19,7 +19,6 @@ package json
import ( import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"reflect"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -66,7 +65,7 @@ func TestEncodeDecodeRoundTrip(t *testing.T) {
t.Errorf("%d: unexpected error: %v", i, err) t.Errorf("%d: unexpected error: %v", i, err)
continue 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) t.Errorf("%d: expected %#v, got %#v", i, testCase.Object, obj)
} }
if event != testCase.Type { if event != testCase.Type {

View File

@ -195,7 +195,13 @@ func makeURL(suffix string) string {
} }
func TestDefaultErrorFunc(t *testing.T) { 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{ handler := util.FakeHandler{
StatusCode: 200, StatusCode: 200,
ResponseBody: runtime.EncodeOrDie(latest.Codec, testPod), ResponseBody: runtime.EncodeOrDie(latest.Codec, testPod),