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

View File

@ -47,14 +47,6 @@ func init() {
out.Spec.DNSPolicy = in.DNSPolicy
out.Name = in.ID
out.UID = in.UUID
// TODO(dchen1107): Move this conversion to pkg/api/v1beta[123]/conversion.go
// along with fixing #1502
for i := range out.Spec.Containers {
ctr := &out.Spec.Containers[i]
if len(ctr.TerminationMessagePath) == 0 {
ctr.TerminationMessagePath = TerminationMessagePathDefault
}
}
return nil
},
func(in *BoundPod, out *ContainerManifest, s conversion.Scope) error {
@ -65,12 +57,6 @@ func init() {
out.Version = "v1beta2"
out.ID = in.Name
out.UUID = in.UID
for i := range out.Containers {
ctr := &out.Containers[i]
if len(ctr.TerminationMessagePath) == 0 {
ctr.TerminationMessagePath = TerminationMessagePathDefault
}
}
return nil
},

View File

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

View File

@ -32,6 +32,14 @@ import (
"speter.net/go/exp/math/dec/inf"
)
func fuzzOneOf(c fuzz.Continue, objs ...interface{}) {
// Use a new fuzzer which cannot populate nil to ensure one obj will be set.
// FIXME: would be nicer to use FuzzOnePtr() and reflect.
f := fuzz.New().NilChance(0).NumElements(1, 1)
i := c.RandUint64() % uint64(len(objs))
f.Fuzz(objs[i])
}
// FuzzerFor can randomly populate api objects that are destined for version.
func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
f := fuzz.New().NilChance(.5).NumElements(1, 1)
@ -84,15 +92,18 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
*j = statuses[c.Rand.Intn(len(statuses))]
},
func(j *api.PodTemplateSpec, c fuzz.Continue) {
// TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2
// conversion compare converted object to nil via DeepEqual
j.ObjectMeta = api.ObjectMeta{}
c.Fuzz(&j.ObjectMeta)
j.ObjectMeta = api.ObjectMeta{Labels: j.ObjectMeta.Labels}
j.Spec = api.PodSpec{}
c.Fuzz(&j.Spec)
},
func(j *api.ReplicationControllerSpec, c fuzz.Continue) {
// TemplateRef must be nil for round trip
// TemplateRef is set to nil by omission; this is required for round trip
c.Fuzz(&j.Template)
if j.Template == nil {
// TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2
// conversion compare converted object to nil via DeepEqual
j.Template = &api.PodTemplateSpec{}
}
j.Template.ObjectMeta = api.ObjectMeta{Labels: j.Template.ObjectMeta.Labels}
c.Fuzz(&j.Selector)
j.Replicas = int(c.RandUint64())
},
@ -160,6 +171,46 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
policies := []api.PullPolicy{api.PullAlways, api.PullNever, api.PullIfNotPresent}
*p = policies[c.Rand.Intn(len(policies))]
},
func(rp *api.RestartPolicy, c fuzz.Continue) {
// Exactly one of the fields should be set.
fuzzOneOf(c, &rp.Always, &rp.OnFailure, &rp.Never)
},
func(vs *api.VolumeSource, c fuzz.Continue) {
// Exactly one of the fields should be set.
//FIXME: the fuzz can still end up nil. What if fuzz allowed me to say that?
fuzzOneOf(c, &vs.HostPath, &vs.EmptyDir, &vs.GCEPersistentDisk, &vs.GitRepo)
},
func(d *api.DNSPolicy, c fuzz.Continue) {
policies := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault}
*d = policies[c.Rand.Intn(len(policies))]
},
func(p *api.Protocol, c fuzz.Continue) {
protocols := []api.Protocol{api.ProtocolTCP, api.ProtocolUDP}
*p = protocols[c.Rand.Intn(len(protocols))]
},
func(p *api.AffinityType, c fuzz.Continue) {
types := []api.AffinityType{api.AffinityTypeClientIP, api.AffinityTypeNone}
*p = types[c.Rand.Intn(len(types))]
},
func(ct *api.Container, c fuzz.Continue) {
// This function exists soley to set TerminationMessagePath to a
// non-empty string. TODO: consider making TerminationMessagePath a
// new type to simplify fuzzing.
ct.TerminationMessagePath = api.TerminationMessagePathDefault
// Let fuzzer handle the rest of the fileds.
c.Fuzz(&ct.Name)
c.Fuzz(&ct.Image)
c.Fuzz(&ct.Command)
c.Fuzz(&ct.Ports)
c.Fuzz(&ct.WorkingDir)
c.Fuzz(&ct.Env)
c.Fuzz(&ct.VolumeMounts)
c.Fuzz(&ct.LivenessProbe)
c.Fuzz(&ct.Lifecycle)
c.Fuzz(&ct.ImagePullPolicy)
c.Fuzz(&ct.Privileged)
c.Fuzz(&ct.Capabilities)
},
)
return f
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1beta1
import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
@ -24,8 +26,8 @@ import (
func init() {
api.Scheme.AddDefaultingFuncs(
func(obj *Volume) {
if obj.Source == nil || util.AllPtrFieldsNil(obj.Source) {
obj.Source = &VolumeSource{
if util.AllPtrFieldsNil(&obj.Source) {
obj.Source = VolumeSource{
EmptyDir: &EmptyDir{},
}
}
@ -36,13 +38,18 @@ func init() {
}
},
func(obj *Container) {
// TODO: delete helper functions that touch this
if obj.ImagePullPolicy == "" {
obj.ImagePullPolicy = PullIfNotPresent
// TODO(dchen1107): Move ParseImageName code to pkg/util
parts := strings.Split(obj.Image, ":")
// Check image tag
if parts[len(parts)-1] == "latest" {
obj.ImagePullPolicy = PullAlways
} else {
obj.ImagePullPolicy = PullIfNotPresent
}
}
if obj.TerminationMessagePath == "" {
// TODO: fix other code that sets this
obj.TerminationMessagePath = api.TerminationMessagePathDefault
obj.TerminationMessagePath = TerminationMessagePathDefault
}
},
func(obj *RestartPolicy) {
@ -54,8 +61,19 @@ func init() {
if obj.Protocol == "" {
obj.Protocol = ProtocolTCP
}
if obj.SessionAffinity == "" {
obj.SessionAffinity = AffinityTypeNone
}
},
func(obj *PodSpec) {
if obj.DNSPolicy == "" {
obj.DNSPolicy = DNSClusterFirst
}
},
func(obj *ContainerManifest) {
if obj.DNSPolicy == "" {
obj.DNSPolicy = DNSClusterFirst
}
},
)
}
// TODO: remove redundant code in validation and conversion

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"`
}
const (
// TerminationMessagePathDefault means the default path to capture the application termination message running in a container
TerminationMessagePathDefault string = "/dev/termination-log"
)
// Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct {
// Required: This must be a DNS_LABEL. Each volume in a pod must have

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"`
}
const (
// TerminationMessagePathDefault means the default path to capture the application termination message running in a container
TerminationMessagePathDefault string = "/dev/termination-log"
)
// Handler defines a specific action that should be taken
// TODO: pass structured data to these actions, and document that data here.
type Handler struct {

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"`
}
const (
// TerminationMessagePathDefault means the default path to capture the application termination message running in a container
TerminationMessagePathDefault string = "/dev/termination-log"
)
// Container represents a single container that is expected to be run on the host.
type Container struct {
// Required: This must be a DNS_LABEL. Each container in a pod must

View File

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

View File

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

View File

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

View File

@ -96,7 +96,7 @@ func (c *testClient) Setup() *testClient {
func (c *testClient) Validate(t *testing.T, received runtime.Object, err error) {
c.ValidateCommon(t, err)
if c.Response.Body != nil && !api.Semantic.DeepEqual(c.Response.Body, received) {
if c.Response.Body != nil && !api.Semantic.DeepDerivative(c.Response.Body, received) {
t.Errorf("bad response for request %#v: expected %#v, got %#v", c.Request, c.Response.Body, received)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -233,3 +233,140 @@ func (e Equalities) DeepEqual(a1, a2 interface{}) bool {
}
return e.deepValueEqual(v1, v2, make(map[visit]bool), 0)
}
func (e Equalities) deepValueDerive(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
if fv, ok := e[v1.Type()]; ok {
return fv.Call([]reflect.Value{v1, v2})[0].Bool()
}
hard := func(k reflect.Kind) bool {
switch k {
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
return true
}
return false
}
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
addr1 := v1.UnsafeAddr()
addr2 := v2.UnsafeAddr()
if addr1 > addr2 {
// Canonicalize order to reduce number of entries in visited.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are identical ...
if addr1 == addr2 {
return true
}
// ... or already seen
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}
// Remember for later.
visited[v] = true
}
switch v1.Kind() {
case reflect.Array:
for i := 0; i < v1.Len(); i++ {
if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.Slice:
if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) {
return false
}
if v1.IsNil() || v1.Len() == 0 {
return true
}
if v1.Pointer() == v2.Pointer() {
return true
}
for i := 0; i < v1.Len(); i++ {
if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case reflect.String:
if v1.Len() == 0 {
return true
}
return v1.String() == v2.String()
case reflect.Interface:
if v1.IsNil() {
return true
}
return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Ptr:
if v1.IsNil() {
return true
}
return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !e.deepValueDerive(v1.Field(i), v2.Field(i), visited, depth+1) {
return false
}
}
return true
case reflect.Map:
if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) {
return false
}
if v1.IsNil() || v1.Len() == 0 {
return true
}
if v1.Pointer() == v2.Pointer() {
return true
}
for _, k := range v1.MapKeys() {
if !e.deepValueDerive(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
return false
}
}
return true
case reflect.Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
default:
// Normal equality suffices
if v1.CanInterface() && v2.CanInterface() {
return v1.Interface() == v2.Interface()
}
return v1.CanInterface() == v2.CanInterface()
}
}
// DeepDerivative is similar to DeepEqual except that unset fields in a1 are
// ignored (not compared). This allows we to focus on the fields that matter to
// the semantic comparison.
//
// The unset fields include a nil pointer and an empty string.
func (e Equalities) DeepDerivative(a1, a2 interface{}) bool {
if a1 == nil {
return true
}
v1 := reflect.ValueOf(a1)
v2 := reflect.ValueOf(a2)
if v1.Type() != v2.Type() {
return false
}
return e.deepValueDerive(v1, v2, make(map[visit]bool), 0)
}

View File

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

View File

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

View File

@ -42,9 +42,17 @@ func testData() (*api.PodList, *api.ServiceList) {
Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -55,6 +63,10 @@ func testData() (*api.PodList, *api.ServiceList) {
Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: api.ServiceSpec{
Protocol: "TCP",
SessionAffinity: "None",
},
},
},
}
@ -296,6 +308,10 @@ func watchTestData() ([]api.Pod, []watch.Event) {
Namespace: "test",
ResourceVersion: "10",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
}
events := []watch.Event{
@ -307,6 +323,10 @@ func watchTestData() ([]api.Pod, []watch.Event) {
Namespace: "test",
ResourceVersion: "11",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
{
@ -317,6 +337,10 @@ func watchTestData() ([]api.Pod, []watch.Event) {
Namespace: "test",
ResourceVersion: "12",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}

View File

@ -42,6 +42,12 @@ func TestMerge(t *testing.T) {
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{
Always: &api.RestartPolicyAlways{},
},
DNSPolicy: api.DNSClusterFirst,
},
},
},
{
@ -57,6 +63,10 @@ func TestMerge(t *testing.T) {
},
Spec: api.PodSpec{
Host: "bar",
RestartPolicy: api.RestartPolicy{
Always: &api.RestartPolicyAlways{},
},
DNSPolicy: api.DNSClusterFirst,
},
},
},
@ -74,12 +84,18 @@ func TestMerge(t *testing.T) {
Spec: api.PodSpec{
Volumes: []api.Volume{
{
Name: "v1",
Name: "v1",
Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}},
},
{
Name: "v2",
Name: "v2",
Source: api.VolumeSource{EmptyDir: &api.EmptyDir{}},
},
},
RestartPolicy: api.RestartPolicy{
Always: &api.RestartPolicyAlways{},
},
DNSPolicy: api.DNSClusterFirst,
},
},
},
@ -91,13 +107,13 @@ func TestMerge(t *testing.T) {
},
}
for _, test := range tests {
for i, test := range tests {
err := Merge(test.obj, test.fragment, "Pod")
if !test.expectErr {
if err != nil {
t.Errorf("unexpected error: %v", err)
} else if !reflect.DeepEqual(test.obj, test.expected) {
t.Errorf("\nexpected:\n%v\nsaw:\n%v", test.expected, test.obj)
t.Errorf("\n\ntestcase[%d]\nexpected:\n%v\nsaw:\n%v", i, test.expected, test.obj)
}
}
if test.expectErr && err == nil {

View File

@ -88,9 +88,17 @@ func testData() (*api.PodList, *api.ServiceList) {
Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -376,7 +384,7 @@ func TestSelector(t *testing.T) {
if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
}
if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
t.Errorf("unexpected visited objects: %#v", test.Objects())
}
@ -419,7 +427,7 @@ func TestStream(t *testing.T) {
if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
}
if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
t.Errorf("unexpected visited objects: %#v", test.Objects())
}
}
@ -436,7 +444,7 @@ func TestYAMLStream(t *testing.T) {
if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
}
if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
t.Errorf("unexpected visited objects: %#v", test.Objects())
}
}
@ -458,7 +466,7 @@ func TestMultipleObject(t *testing.T) {
&svc.Items[0],
},
}
if !reflect.DeepEqual(expected, obj) {
if !api.Semantic.DeepDerivative(expected, obj) {
t.Errorf("unexpected visited objects: %#v", obj)
}
}
@ -612,7 +620,7 @@ func TestLatest(t *testing.T) {
if err != nil || singular || len(test.Infos) != 3 {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
}
if !reflect.DeepEqual([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) {
if !api.Semantic.DeepDerivative([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) {
t.Errorf("unexpected visited objects: %#v", test.Objects())
}
}
@ -646,7 +654,7 @@ func TestIgnoreStreamErrors(t *testing.T) {
t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
}
if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &svc.Items[0]}, test.Objects()) {
if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &svc.Items[0]}, test.Objects()) {
t.Errorf("unexpected visited objects: %#v", test.Objects())
}
}

View File

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

View File

@ -177,7 +177,7 @@ func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
// container updates are separated as UPDATE
pod := podUpdate.Pods[0]
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}}
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}}
channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, NoneSource, pod))
}
@ -195,7 +195,7 @@ func TestNewPodAddedSnapshot(t *testing.T) {
// container updates are separated as UPDATE
pod := podUpdate.Pods[0]
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}}
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}}
channel <- CreatePodUpdate(kubelet.ADD, NoneSource, pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.SET, TestSource, pod))
}
@ -213,7 +213,7 @@ func TestNewPodAddedUpdatedRemoved(t *testing.T) {
// an kubelet.ADD should be converted to kubelet.UPDATE
pod := CreateValidPod("foo", "new", "test")
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}}
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}}
podUpdate = CreatePodUpdate(kubelet.ADD, NoneSource, pod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubelet.UPDATE, NoneSource, pod))
@ -236,7 +236,7 @@ func TestNewPodAddedUpdatedSet(t *testing.T) {
// should be converted to an kubelet.ADD, kubelet.REMOVE, and kubelet.UPDATE
pod := CreateValidPod("foo2", "new", "test")
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test"}}
pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}}
podUpdate = CreatePodUpdate(kubelet.SET, NoneSource, pod, CreateValidPod("foo3", "new", ""), CreateValidPod("foo4", "new", "test"))
channel <- podUpdate
expectPodUpdate(t, ch,

View File

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

View File

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

View File

@ -126,6 +126,10 @@ func TestControllerDecode(t *testing.T) {
"name": "nginx",
},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -225,10 +229,13 @@ var validPodTemplate = api.PodTemplate{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
},
},
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
}

View File

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

View File

@ -19,7 +19,6 @@ package etcd
import (
"fmt"
"path"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -146,7 +145,7 @@ func TestEtcdList(t *testing.T) {
continue
}
if e, a := item.out, list; !reflect.DeepEqual(e, a) {
if e, a := item.out, list; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%v: Expected %#v, got %#v", name, e, a)
}
}
@ -209,7 +208,7 @@ func TestEtcdCreate(t *testing.T) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
@ -284,7 +283,7 @@ func TestEtcdUpdate(t *testing.T) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
@ -340,7 +339,7 @@ func TestEtcdGet(t *testing.T) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, got; !reflect.DeepEqual(e, a) {
if e, a := item.expect, got; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
@ -396,7 +395,7 @@ func TestEtcdDelete(t *testing.T) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
@ -432,7 +431,7 @@ func TestEtcdWatch(t *testing.T) {
t.Fatalf("unexpected channel close")
}
if e, a := podA, got.Object; !reflect.DeepEqual(e, a) {
if e, a := podA, got.Object; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("difference: %s", util.ObjectDiff(e, a))
}
}

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/coreos/go-etcd/etcd"
)
@ -47,6 +48,12 @@ func init() {
scheme.AddKnownTypes("", &TestResource{})
scheme.AddKnownTypes("v1beta1", &TestResource{})
codec = runtime.CodecFor(scheme, "v1beta1")
scheme.AddConversionFuncs(
func(in *TestResource, out *TestResource, s conversion.Scope) error {
*out = *in
return nil
},
)
}
func TestIsEtcdNotFound(t *testing.T) {
@ -94,10 +101,27 @@ func TestExtractToList(t *testing.T) {
expect := api.PodList{
ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{
// We expect items to be sorted by its name.
{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}},
{ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}},
{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}},
{
ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -160,9 +184,27 @@ func TestExtractToListAcrossDirectories(t *testing.T) {
ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{
// We expect list to be sorted by directory (e.g. namespace) first, then by name.
{ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "1"}},
{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}},
{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}},
{
ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "1"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -212,9 +254,27 @@ func TestExtractToListExcludesDirectories(t *testing.T) {
expect := api.PodList{
ListMeta: api.ListMeta{ResourceVersion: "10"},
Items: []api.Pod{
{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}},
{ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}},
{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}},
{
ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
},
},
}
@ -231,7 +291,13 @@ func TestExtractToListExcludesDirectories(t *testing.T) {
func TestExtractObj(t *testing.T) {
fakeClient := NewFakeEtcdClient(t)
expect := api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
expect := api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}
fakeClient.Set("/some/key", runtime.EncodeOrDie(testapi.Codec(), &expect), 0)
helper := EtcdHelper{fakeClient, testapi.Codec(), versioner}
var got api.Pod

View File

@ -18,7 +18,6 @@ package tools
import (
"fmt"
"reflect"
"testing"
"time"
@ -123,7 +122,7 @@ func TestWatchInterpretations(t *testing.T) {
if e, a := item.expectType, event.Type; e != a {
t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a)
}
if e, a := item.expectObject, event.Object; !reflect.DeepEqual(e, a) {
if e, a := item.expectObject, event.Object; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("'%v - %v': expected %v, got %v", name, action, e, a)
}
}
@ -250,7 +249,7 @@ func TestWatch(t *testing.T) {
if e, a := watch.Added, event.Type; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if e, a := pod, event.Object; !reflect.DeepEqual(e, a) {
if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("Expected %v, got %v", e, a)
}
@ -381,7 +380,7 @@ func TestWatchEtcdState(t *testing.T) {
t.Errorf("%s: expected type %v, got %v", k, e, a)
break
}
if e, a := testCase.Expected[i].Endpoints, event.Object.(*api.Endpoints).Endpoints; !reflect.DeepEqual(e, a) {
if e, a := testCase.Expected[i].Endpoints, event.Object.(*api.Endpoints).Endpoints; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%s: expected type %v, got %v", k, e, a)
break
}
@ -456,7 +455,7 @@ func TestWatchFromZeroIndex(t *testing.T) {
t.Errorf("%s: expected pod with resource version %v, Got %#v", k, testCase.ExpectedVersion, actualPod)
}
pod.ResourceVersion = testCase.ExpectedVersion
if e, a := pod, event.Object; !reflect.DeepEqual(e, a) {
if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("%s: expected %v, got %v", k, e, a)
}
watching.Stop()
@ -515,7 +514,7 @@ func TestWatchListFromZeroIndex(t *testing.T) {
t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod)
}
pod.ResourceVersion = "1"
if e, a := pod, event.Object; !reflect.DeepEqual(e, a) {
if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) {
t.Errorf("Expected %v, got %v", e, a)
}
}

View File

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

View File

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

View File

@ -195,7 +195,13 @@ func makeURL(suffix string) string {
}
func TestDefaultErrorFunc(t *testing.T) {
testPod := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}}
testPod := &api.Pod{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
DNSPolicy: api.DNSClusterFirst,
},
}
handler := util.FakeHandler{
StatusCode: 200,
ResponseBody: runtime.EncodeOrDie(latest.Codec, testPod),