Merge pull request #3854 from yujuhong/defaults

Migrate API defaulting to a centralized location
pull/6/head
Brian Grant 2015-02-04 11:41:23 -08:00
commit 3f28badae3
42 changed files with 1212 additions and 397 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

@ -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 v1beta1
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

@ -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

@ -40,7 +40,7 @@ type DebugLogger interface {
type Converter struct {
// Map from the conversion pair to a function which can
// do the conversion.
funcs map[typePair]reflect.Value
conversionFuncs map[typePair]reflect.Value
// This is a map from a source field type and name, to a list of destination
// field type and name.
@ -51,20 +51,24 @@ type Converter struct {
// source field name and type to look for.
structFieldSources map[typeNamePair][]typeNamePair
// Map from a type to a function which applies defaults.
defaultingFuncs map[reflect.Type]reflect.Value
// If non-nil, will be called to print helpful debugging info. Quite verbose.
Debug DebugLogger
// NameFunc is called to retrieve the name of a type; this name is used for the
// nameFunc is called to retrieve the name of a type; this name is used for the
// purpose of deciding whether two types match or not (i.e., will we attempt to
// do a conversion). The default returns the go type name.
NameFunc func(t reflect.Type) string
nameFunc func(t reflect.Type) string
}
// NewConverter creates a new Converter object.
func NewConverter() *Converter {
return &Converter{
funcs: map[typePair]reflect.Value{},
NameFunc: func(t reflect.Type) string { return t.Name() },
conversionFuncs: map[typePair]reflect.Value{},
defaultingFuncs: map[reflect.Type]reflect.Value{},
nameFunc: func(t reflect.Type) string { return t.Name() },
structFieldDests: map[typeNamePair][]typeNamePair{},
structFieldSources: map[typeNamePair][]typeNamePair{},
}
@ -210,14 +214,18 @@ func (s *scope) error(message string, args ...interface{}) error {
return fmt.Errorf(where+message, args...)
}
// Register registers a conversion func with the Converter. conversionFunc must take
// three parameters: a pointer to the input type, a pointer to the output type, and
// a conversion.Scope (which should be used if recursive conversion calls are desired).
// It must return an error.
// RegisterConversionFunc registers a conversion func with the
// Converter. conversionFunc must take three parameters: a pointer to the input
// type, a pointer to the output type, and a conversion.Scope (which should be
// used if recursive conversion calls are desired). It must return an error.
//
// Example:
// c.Register(func(in *Pod, out *v1beta1.Pod, s Scope) error { ... return nil })
func (c *Converter) Register(conversionFunc interface{}) error {
// c.RegisteConversionFunc(
// func(in *Pod, out *v1beta1.Pod, s Scope) error {
// // conversion logic...
// return nil
// })
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc)
ft := fv.Type()
if ft.Kind() != reflect.Func {
@ -246,7 +254,7 @@ func (c *Converter) Register(conversionFunc interface{}) error {
if ft.Out(0) != errorType {
return fmt.Errorf("expected error return, got: %v", ft)
}
c.funcs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
c.conversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
return nil
}
@ -266,6 +274,33 @@ func (c *Converter) SetStructFieldCopy(srcFieldType interface{}, srcFieldName st
return nil
}
// RegisterDefaultingFunc registers a value-defaulting func with the Converter.
// defaultingFunc must take one parameters: a pointer to the input type.
//
// Example:
// c.RegisteDefaultingFunc(
// func(in *v1beta1.Pod) {
// // defaulting logic...
// })
func (c *Converter) RegisterDefaultingFunc(defaultingFunc interface{}) error {
fv := reflect.ValueOf(defaultingFunc)
ft := fv.Type()
if ft.Kind() != reflect.Func {
return fmt.Errorf("expected func, got: %v", ft)
}
if ft.NumIn() != 1 {
return fmt.Errorf("expected one 'in' param, got: %v", ft)
}
if ft.NumOut() != 0 {
return fmt.Errorf("expected zero 'out' params, got: %v", ft)
}
if ft.In(0).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'in' param 0, got: %v", ft)
}
c.defaultingFuncs[ft.In(0).Elem()] = fv
return nil
}
// FieldMatchingFlags contains a list of ways in which struct fields could be
// copied. These constants may be | combined.
type FieldMatchingFlags int
@ -379,7 +414,17 @@ 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()
if fv, ok := c.funcs[typePair{st, dt}]; ok {
// Apply default values.
if fv, ok := c.defaultingFuncs[st]; ok {
if c.Debug != nil {
c.Debug.Logf("Applying defaults for '%v'", st)
}
args := []reflect.Value{sv.Addr()}
fv.Call(args)
}
// Convert sv to dv.
if fv, ok := c.conversionFuncs[typePair{st, dt}]; ok {
if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
}
@ -398,10 +443,10 @@ func (c *Converter) defaultConvert(sv, dv reflect.Value, scope *scope) error {
return scope.error("Cannot set dest. (Tried to deep copy something with unexported fields?)")
}
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.NameFunc(dt) != c.NameFunc(st) {
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.nameFunc(dt) != c.nameFunc(st) {
return scope.error(
"type names don't match (%v, %v), and no conversion 'func (%v, %v) error' registered.",
c.NameFunc(st), c.NameFunc(dt), st, dt)
c.nameFunc(st), c.nameFunc(dt), st, dt)
}
switch st.Kind() {
@ -498,6 +543,7 @@ func toKVValue(v reflect.Value) kvValue {
case reflect.Struct:
return structAdaptor(v)
}
return 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
}
@ -118,14 +118,14 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
type C struct{}
c := NewConverter()
c.Debug = t
err := c.Register(func(in *A, out *B, s Scope) error {
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
out.Bar = in.Foo
return s.Convert(&in.Baz, &out.Baz, 0)
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = c.Register(func(in *B, out *A, s Scope) error {
err = c.RegisterConversionFunc(func(in *B, out *A, s Scope) error {
out.Foo = in.Bar
return s.Convert(&in.Baz, &out.Baz, 0)
})
@ -161,7 +161,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
t.Errorf("expected %v, got %v", e, a)
}
err = c.Register(func(in *A, out *C, s Scope) error {
err = c.RegisterConversionFunc(func(in *A, out *C, s Scope) error {
return fmt.Errorf("C can't store an A, silly")
})
if err != nil {
@ -184,7 +184,7 @@ func TestConverter_fuzz(t *testing.T) {
f := fuzz.New().NilChance(.5).NumElements(0, 100)
c := NewConverter()
c.NameFunc = func(t reflect.Type) string {
c.nameFunc = func(t reflect.Type) string {
// Hide the fact that we don't have separate packages for these things.
return map[reflect.Type]string{
reflect.TypeOf(TestType1{}): "TestType1",
@ -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
@ -270,7 +270,7 @@ func TestConverter_tags(t *testing.T) {
}
c := NewConverter()
c.Debug = t
err := c.Register(
err := c.RegisterConversionFunc(
func(in *string, out *string, s Scope) error {
if e, a := "foo", s.SrcTag().Get("test"); e != a {
t.Errorf("expected %v, got %v", e, a)
@ -296,7 +296,7 @@ func TestConverter_meta(t *testing.T) {
c := NewConverter()
c.Debug = t
checks := 0
err := c.Register(
err := c.RegisterConversionFunc(
func(in *Foo, out *Bar, s Scope) error {
if s.Meta() == nil || s.Meta().SrcVersion != "test" || s.Meta().DestVersion != "passes" {
t.Errorf("Meta did not get passed!")
@ -309,7 +309,7 @@ func TestConverter_meta(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = c.Register(
err = c.RegisterConversionFunc(
func(in *string, out *string, s Scope) error {
if s.Meta() == nil || s.Meta().SrcVersion != "test" || s.Meta().DestVersion != "passes" {
t.Errorf("Meta did not get passed a second time!")

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

@ -63,7 +63,7 @@ func NewScheme() *Scheme {
InternalVersion: "",
MetaFactory: DefaultMetaFactory,
}
s.converter.NameFunc = s.nameFunc
s.converter.nameFunc = s.nameFunc
return s
}
@ -194,7 +194,7 @@ func (s *Scheme) NewObject(versionName, kind string) (interface{}, error) {
// add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs {
if err := s.converter.Register(f); err != nil {
if err := s.converter.RegisterConversionFunc(f); err != nil {
return err
}
}
@ -209,6 +209,29 @@ func (s *Scheme) AddStructFieldConversion(srcFieldType interface{}, srcFieldName
return s.converter.SetStructFieldCopy(srcFieldType, srcFieldName, destFieldType, destFieldName)
}
// AddDefaultingFuncs adds functions to the list of default-value functions.
// Each of the given functions is responsible for applying default values
// when converting an instance of a versioned API object into an internal
// API object. These functions do not need to handle sub-objects. We deduce
// how to call these functions from the types of their two parameters.
//
// s.AddDefaultingFuncs(
// func(obj *v1beta1.Pod) {
// if obj.OptionalField == "" {
// obj.OptionalField = "DefaultValue"
// }
// },
// )
func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
for _, f := range defaultingFuncs {
err := s.converter.RegisterDefaultingFunc(f)
if err != nil {
return err
}
}
return nil
}
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example,

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()
@ -88,14 +90,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,
},
},
}
@ -126,8 +132,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 {
@ -161,15 +169,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,
},
},
}
@ -196,6 +208,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)
@ -225,6 +239,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()
@ -247,7 +263,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)
@ -272,6 +290,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)
@ -386,8 +406,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()
@ -404,8 +426,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)
@ -421,9 +445,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()
@ -445,8 +471,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()
@ -466,8 +494,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()
@ -492,8 +522,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()
@ -541,6 +573,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()
@ -573,8 +607,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()
@ -583,8 +619,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)
@ -597,8 +635,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)
@ -658,8 +698,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

@ -265,7 +265,8 @@ func (s *Scheme) Log(l conversion.DebugLogger) {
// AddConversionFuncs adds a function to the list of conversion functions. The given
// function should know how to convert between two API objects. We deduce how to call
// it from the types of its two parameters; see the comment for Converter.Register.
// it from the types of its two parameters; see the comment for
// Converter.RegisterConversionFunction.
//
// Note that, if you need to copy sub-objects that didn't change, it's safe to call
// Convert() inside your conversionFuncs, as long as you don't start a conversion
@ -287,6 +288,13 @@ func (s *Scheme) AddStructFieldConversion(srcFieldType interface{}, srcFieldName
return s.raw.AddStructFieldConversion(srcFieldType, srcFieldName, destFieldType, destFieldName)
}
// AddDefaultingFuncs adds a function to the list of value-defaulting functions.
// We deduce how to call it from the types of its two parameters; see the
// comment for Converter.RegisterDefaultingFunction.
func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
return s.raw.AddDefaultingFuncs(defaultingFuncs...)
}
// Convert will attempt to convert in into out. Both must be pointers.
// For easy testing of conversion functions. Returns an error if the conversion isn't
// possible.

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