Merge pull request #4174 from gmarek/master

Add more information to Validator error messages.
pull/6/head
Tim Hockin 2015-02-09 09:19:12 -08:00
commit 28cd5bd440
3 changed files with 181 additions and 154 deletions

View File

@ -30,12 +30,26 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
const qualifiedNameErrorMsg string = "must match regex [" + util.DNS1123SubdomainFmt + " / ] " + util.DNS1123LabelFmt
const cIdentifierErrorMsg string = "must match regex " + util.CIdentifierFmt
const isNegativeErrorMsg string = "value must not be negative"
func intervalErrorMsg(lo, hi int) string {
return fmt.Sprintf("must be greater than %d and less than %d", lo, hi)
}
var dnsSubdomainErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS1123SubdomainMaxLength, util.DNS1123SubdomainFmt)
var dnsLabelErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS1123LabelMaxLength, util.DNS1123LabelFmt)
var dns952LabelErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS952LabelMaxLength, util.DNS952LabelFmt)
var pdPartitionErrorMsg string = intervalErrorMsg(0, 255)
var portRangeErrorMsg string = intervalErrorMsg(0, 65536)
// ValidateLabels validates that a set of labels are correctly defined. // ValidateLabels validates that a set of labels are correctly defined.
func ValidateLabels(labels map[string]string, field string) errs.ValidationErrorList { func ValidateLabels(labels map[string]string, field string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for k := range labels { for k := range labels {
if !util.IsQualifiedName(k) { if !util.IsQualifiedName(k) {
allErrs = append(allErrs, errs.NewFieldInvalid(field, k, "")) allErrs = append(allErrs, errs.NewFieldInvalid(field, k, qualifiedNameErrorMsg))
} }
} }
return allErrs return allErrs
@ -46,7 +60,7 @@ func ValidateAnnotations(annotations map[string]string, field string) errs.Valid
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for k := range annotations { for k := range annotations {
if !util.IsQualifiedName(strings.ToLower(k)) { if !util.IsQualifiedName(strings.ToLower(k)) {
allErrs = append(allErrs, errs.NewFieldInvalid(field, k, "")) allErrs = append(allErrs, errs.NewFieldInvalid(field, k, qualifiedNameErrorMsg))
} }
} }
return allErrs return allErrs
@ -103,7 +117,7 @@ func nameIsDNSSubdomain(name string, prefix bool) (bool, string) {
if util.IsDNSSubdomain(name) { if util.IsDNSSubdomain(name) {
return true, "" return true, ""
} }
return false, "name must be lowercase letters and numbers, with inline dashes or periods" return false, dnsSubdomainErrorMsg
} }
// nameIsDNS952Label is a ValidateNameFunc for names that must be a DNS 952 label. // nameIsDNS952Label is a ValidateNameFunc for names that must be a DNS 952 label.
@ -114,7 +128,7 @@ func nameIsDNS952Label(name string, prefix bool) (bool, string) {
if util.IsDNS952Label(name) { if util.IsDNS952Label(name) {
return true, "" return true, ""
} }
return false, "name must be lowercase letters, numbers, and dashes" return false, dns952LabelErrorMsg
} }
// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
@ -141,7 +155,7 @@ func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn Val
if len(meta.Namespace) == 0 { if len(meta.Namespace) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("namespace", meta.Namespace)) allErrs = append(allErrs, errs.NewFieldRequired("namespace", meta.Namespace))
} else if !util.IsDNSSubdomain(meta.Namespace) { } else if !util.IsDNSSubdomain(meta.Namespace) {
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", meta.Namespace, "")) allErrs = append(allErrs, errs.NewFieldInvalid("namespace", meta.Namespace, dnsSubdomainErrorMsg))
} }
} else { } else {
if len(meta.Namespace) != 0 { if len(meta.Namespace) != 0 {
@ -194,7 +208,7 @@ func validateVolumes(volumes []api.Volume) (util.StringSet, errs.ValidationError
if len(vol.Name) == 0 { if len(vol.Name) == 0 {
el = append(el, errs.NewFieldRequired("name", vol.Name)) el = append(el, errs.NewFieldRequired("name", vol.Name))
} else if !util.IsDNSLabel(vol.Name) { } else if !util.IsDNSLabel(vol.Name) {
el = append(el, errs.NewFieldInvalid("name", vol.Name, "")) el = append(el, errs.NewFieldInvalid("name", vol.Name, dnsLabelErrorMsg))
} else if allNames.Has(vol.Name) { } else if allNames.Has(vol.Name) {
el = append(el, errs.NewFieldDuplicate("name", vol.Name)) el = append(el, errs.NewFieldDuplicate("name", vol.Name))
} }
@ -257,7 +271,7 @@ func validateGCEPersistentDisk(PD *api.GCEPersistentDisk) errs.ValidationErrorLi
allErrs = append(allErrs, errs.NewFieldRequired("fsType", PD.FSType)) allErrs = append(allErrs, errs.NewFieldRequired("fsType", PD.FSType))
} }
if PD.Partition < 0 || PD.Partition > 255 { if PD.Partition < 0 || PD.Partition > 255 {
allErrs = append(allErrs, errs.NewFieldInvalid("partition", PD.Partition, "")) allErrs = append(allErrs, errs.NewFieldInvalid("partition", PD.Partition, pdPartitionErrorMsg))
} }
return allErrs return allErrs
} }
@ -271,8 +285,8 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList {
for i, port := range ports { for i, port := range ports {
pErrs := errs.ValidationErrorList{} pErrs := errs.ValidationErrorList{}
if len(port.Name) > 0 { if len(port.Name) > 0 {
if len(port.Name) > 63 || !util.IsDNSLabel(port.Name) { if len(port.Name) > util.DNS1123LabelMaxLength || !util.IsDNSLabel(port.Name) {
pErrs = append(pErrs, errs.NewFieldInvalid("name", port.Name, "")) pErrs = append(pErrs, errs.NewFieldInvalid("name", port.Name, dnsLabelErrorMsg))
} else if allNames.Has(port.Name) { } else if allNames.Has(port.Name) {
pErrs = append(pErrs, errs.NewFieldDuplicate("name", port.Name)) pErrs = append(pErrs, errs.NewFieldDuplicate("name", port.Name))
} else { } else {
@ -280,12 +294,12 @@ func validatePorts(ports []api.Port) errs.ValidationErrorList {
} }
} }
if port.ContainerPort == 0 { if port.ContainerPort == 0 {
pErrs = append(pErrs, errs.NewFieldRequired("containerPort", port.ContainerPort)) pErrs = append(pErrs, errs.NewFieldInvalid("containerPort", port.ContainerPort, portRangeErrorMsg))
} else if !util.IsValidPortNum(port.ContainerPort) { } else if !util.IsValidPortNum(port.ContainerPort) {
pErrs = append(pErrs, errs.NewFieldInvalid("containerPort", port.ContainerPort, "")) pErrs = append(pErrs, errs.NewFieldInvalid("containerPort", port.ContainerPort, portRangeErrorMsg))
} }
if port.HostPort != 0 && !util.IsValidPortNum(port.HostPort) { if port.HostPort != 0 && !util.IsValidPortNum(port.HostPort) {
pErrs = append(pErrs, errs.NewFieldInvalid("hostPort", port.HostPort, "")) pErrs = append(pErrs, errs.NewFieldInvalid("hostPort", port.HostPort, portRangeErrorMsg))
} }
if len(port.Protocol) == 0 { if len(port.Protocol) == 0 {
pErrs = append(pErrs, errs.NewFieldRequired("protocol", port.Protocol)) pErrs = append(pErrs, errs.NewFieldRequired("protocol", port.Protocol))
@ -306,7 +320,7 @@ func validateEnv(vars []api.EnvVar) errs.ValidationErrorList {
vErrs = append(vErrs, errs.NewFieldRequired("name", ev.Name)) vErrs = append(vErrs, errs.NewFieldRequired("name", ev.Name))
} }
if !util.IsCIdentifier(ev.Name) { if !util.IsCIdentifier(ev.Name) {
vErrs = append(vErrs, errs.NewFieldInvalid("name", ev.Name, "")) vErrs = append(vErrs, errs.NewFieldInvalid("name", ev.Name, cIdentifierErrorMsg))
} }
allErrs = append(allErrs, vErrs.PrefixIndex(i)...) allErrs = append(allErrs, vErrs.PrefixIndex(i)...)
} }
@ -430,7 +444,7 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs
if len(ctr.Name) == 0 { if len(ctr.Name) == 0 {
cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name)) cErrs = append(cErrs, errs.NewFieldRequired("name", ctr.Name))
} else if !util.IsDNSLabel(ctr.Name) { } else if !util.IsDNSLabel(ctr.Name) {
cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name, "")) cErrs = append(cErrs, errs.NewFieldInvalid("name", ctr.Name, dnsLabelErrorMsg))
} else if allNames.Has(ctr.Name) { } else if allNames.Has(ctr.Name) {
cErrs = append(cErrs, errs.NewFieldDuplicate("name", ctr.Name)) cErrs = append(cErrs, errs.NewFieldDuplicate("name", ctr.Name))
} else if ctr.Privileged && !capabilities.AllowPrivileged { } else if ctr.Privileged && !capabilities.AllowPrivileged {
@ -574,7 +588,7 @@ func ValidateService(service *api.Service) errs.ValidationErrorList {
allErrs = append(allErrs, ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName).Prefix("metadata")...) allErrs = append(allErrs, ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName).Prefix("metadata")...)
if !util.IsValidPortNum(service.Spec.Port) { if !util.IsValidPortNum(service.Spec.Port) {
allErrs = append(allErrs, errs.NewFieldInvalid("spec.port", service.Spec.Port, "")) allErrs = append(allErrs, errs.NewFieldInvalid("spec.port", service.Spec.Port, portRangeErrorMsg))
} }
if len(service.Spec.Protocol) == 0 { if len(service.Spec.Protocol) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("spec.protocol", service.Spec.Protocol)) allErrs = append(allErrs, errs.NewFieldRequired("spec.protocol", service.Spec.Protocol))
@ -635,7 +649,7 @@ func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec) errs
allErrs = append(allErrs, errs.NewFieldRequired("selector", spec.Selector)) allErrs = append(allErrs, errs.NewFieldRequired("selector", spec.Selector))
} }
if spec.Replicas < 0 { if spec.Replicas < 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("replicas", spec.Replicas, "")) allErrs = append(allErrs, errs.NewFieldInvalid("replicas", spec.Replicas, isNegativeErrorMsg))
} }
if spec.Template == nil { if spec.Template == nil {
@ -694,7 +708,7 @@ func ValidateBoundPod(pod *api.BoundPod) errs.ValidationErrorList {
if len(pod.Namespace) == 0 { if len(pod.Namespace) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("namespace", pod.Namespace)) allErrs = append(allErrs, errs.NewFieldRequired("namespace", pod.Namespace))
} else if !util.IsDNSSubdomain(pod.Namespace) { } else if !util.IsDNSSubdomain(pod.Namespace) {
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", pod.Namespace, "")) allErrs = append(allErrs, errs.NewFieldInvalid("namespace", pod.Namespace, dnsSubdomainErrorMsg))
} }
allErrs = append(allErrs, ValidatePodSpec(&pod.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidatePodSpec(&pod.Spec).Prefix("spec")...)
return allErrs return allErrs
@ -726,6 +740,7 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
// Clear status // Clear status
oldMinion.Status = minion.Status oldMinion.Status = minion.Status
// TODO: Add a 'real' ValidationError type for this error and provide print actual diffs.
if !api.Semantic.DeepEqual(oldMinion, minion) { if !api.Semantic.DeepEqual(oldMinion, minion) {
glog.V(4).Infof("Update failed validation %#v vs %#v", oldMinion, minion) glog.V(4).Infof("Update failed validation %#v vs %#v", oldMinion, minion)
allErrs = append(allErrs, fmt.Errorf("update contains more than labels or capacity changes")) allErrs = append(allErrs, fmt.Errorf("update contains more than labels or capacity changes"))
@ -737,14 +752,15 @@ func ValidateMinionUpdate(oldMinion *api.Node, minion *api.Node) errs.Validation
// Validate compute resource typename. // Validate compute resource typename.
// Refer to docs/resources.md for more details. // Refer to docs/resources.md for more details.
func validateResourceName(str string) errs.ValidationErrorList { func validateResourceName(value string, field string) errs.ValidationErrorList {
if !util.IsQualifiedName(str) { allErrs := errs.ValidationErrorList{}
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename format %q", str)} if !util.IsQualifiedName(value) {
return append(allErrs, errs.NewFieldInvalid(field, value, "resource typename: "+qualifiedNameErrorMsg))
} }
if len(strings.Split(str, "/")) == 1 { if len(strings.Split(value, "/")) == 1 {
if !api.IsStandardResourceName(str) { if !api.IsStandardResourceName(value) {
return errs.ValidationErrorList{fmt.Errorf("invalid compute resource typename. %q is neither a standard resource type nor is fully qualified", str)} return append(allErrs, errs.NewFieldInvalid(field, value, "is neither a standard resource type nor is fully qualified"))
} }
} }
@ -757,21 +773,21 @@ func ValidateLimitRange(limitRange *api.LimitRange) errs.ValidationErrorList {
if len(limitRange.Name) == 0 { if len(limitRange.Name) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("name", limitRange.Name)) allErrs = append(allErrs, errs.NewFieldRequired("name", limitRange.Name))
} else if !util.IsDNSSubdomain(limitRange.Name) { } else if !util.IsDNSSubdomain(limitRange.Name) {
allErrs = append(allErrs, errs.NewFieldInvalid("name", limitRange.Name, "")) allErrs = append(allErrs, errs.NewFieldInvalid("name", limitRange.Name, dnsSubdomainErrorMsg))
} }
if len(limitRange.Namespace) == 0 { if len(limitRange.Namespace) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("namespace", limitRange.Namespace)) allErrs = append(allErrs, errs.NewFieldRequired("namespace", limitRange.Namespace))
} else if !util.IsDNSSubdomain(limitRange.Namespace) { } else if !util.IsDNSSubdomain(limitRange.Namespace) {
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", limitRange.Namespace, "")) allErrs = append(allErrs, errs.NewFieldInvalid("namespace", limitRange.Namespace, dnsSubdomainErrorMsg))
} }
// ensure resource names are properly qualified per docs/resources.md // ensure resource names are properly qualified per docs/resources.md
for i := range limitRange.Spec.Limits { for i := range limitRange.Spec.Limits {
limit := limitRange.Spec.Limits[i] limit := limitRange.Spec.Limits[i]
for k := range limit.Max { for k := range limit.Max {
allErrs = append(allErrs, validateResourceName(string(k))...) allErrs = append(allErrs, validateResourceName(string(k), fmt.Sprintf("spec.limits[%d].max[%s]", i, k))...)
} }
for k := range limit.Min { for k := range limit.Min {
allErrs = append(allErrs, validateResourceName(string(k))...) allErrs = append(allErrs, validateResourceName(string(k), fmt.Sprintf("spec.limits[%d].min[%s]", i, k))...)
} }
} }
return allErrs return allErrs
@ -789,7 +805,7 @@ func validateResourceRequirements(container *api.Container) errs.ValidationError
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
for resourceName, quantity := range container.Resources.Limits { for resourceName, quantity := range container.Resources.Limits {
// Validate resource name. // Validate resource name.
errs := validateResourceName(resourceName.String()) errs := validateResourceName(resourceName.String(), fmt.Sprintf("resources.limits[%s]", resourceName))
if api.IsStandardResourceName(resourceName.String()) { if api.IsStandardResourceName(resourceName.String()) {
errs = append(errs, validateBasicResource(quantity).Prefix(fmt.Sprintf("Resource %s: ", resourceName))...) errs = append(errs, validateBasicResource(quantity).Prefix(fmt.Sprintf("Resource %s: ", resourceName))...)
} }
@ -805,21 +821,21 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
if len(resourceQuota.Name) == 0 { if len(resourceQuota.Name) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("name", resourceQuota.Name)) allErrs = append(allErrs, errs.NewFieldRequired("name", resourceQuota.Name))
} else if !util.IsDNSSubdomain(resourceQuota.Name) { } else if !util.IsDNSSubdomain(resourceQuota.Name) {
allErrs = append(allErrs, errs.NewFieldInvalid("name", resourceQuota.Name, "")) allErrs = append(allErrs, errs.NewFieldInvalid("name", resourceQuota.Name, dnsSubdomainErrorMsg))
} }
if len(resourceQuota.Namespace) == 0 { if len(resourceQuota.Namespace) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("namespace", resourceQuota.Namespace)) allErrs = append(allErrs, errs.NewFieldRequired("namespace", resourceQuota.Namespace))
} else if !util.IsDNSSubdomain(resourceQuota.Namespace) { } else if !util.IsDNSSubdomain(resourceQuota.Namespace) {
allErrs = append(allErrs, errs.NewFieldInvalid("namespace", resourceQuota.Namespace, "")) allErrs = append(allErrs, errs.NewFieldInvalid("namespace", resourceQuota.Namespace, dnsSubdomainErrorMsg))
} }
for k := range resourceQuota.Spec.Hard { for k := range resourceQuota.Spec.Hard {
allErrs = append(allErrs, validateResourceName(string(k))...) allErrs = append(allErrs, validateResourceName(string(k), string(resourceQuota.TypeMeta.Kind))...)
} }
for k := range resourceQuota.Status.Hard { for k := range resourceQuota.Status.Hard {
allErrs = append(allErrs, validateResourceName(string(k))...) allErrs = append(allErrs, validateResourceName(string(k), string(resourceQuota.TypeMeta.Kind))...)
} }
for k := range resourceQuota.Status.Used { for k := range resourceQuota.Status.Used {
allErrs = append(allErrs, validateResourceName(string(k))...) allErrs = append(allErrs, validateResourceName(string(k), string(resourceQuota.TypeMeta.Kind))...)
} }
return allErrs return allErrs
} }

View File

@ -94,6 +94,11 @@ func TestValidateLabels(t *testing.T) {
errs := ValidateLabels(errorCases[i], "field") errs := ValidateLabels(errorCases[i], "field")
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} else {
detail := errs[0].(*errors.ValidationError).Detail
if detail != qualifiedNameErrorMsg {
t.Errorf("error detail %s should be equal %s", detail, qualifiedNameErrorMsg)
}
} }
} }
} }
@ -131,6 +136,11 @@ func TestValidateAnnotations(t *testing.T) {
errs := ValidateAnnotations(errorCases[i], "field") errs := ValidateAnnotations(errorCases[i], "field")
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} else {
detail := errs[0].(*errors.ValidationError).Detail
if detail != qualifiedNameErrorMsg {
t.Errorf("error detail %s should be equal %s", detail, qualifiedNameErrorMsg)
}
} }
} }
} }
@ -175,6 +185,10 @@ func TestValidateVolumes(t *testing.T) {
if errs[i].(*errors.ValidationError).Field != v.F { if errs[i].(*errors.ValidationError).Field != v.F {
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i]) t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
} }
detail := errs[i].(*errors.ValidationError).Detail
if detail != "" && detail != dnsLabelErrorMsg {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, dnsLabelErrorMsg, detail)
}
} }
} }
} }
@ -203,18 +217,19 @@ func TestValidatePorts(t *testing.T) {
P []api.Port P []api.Port
T errors.ValidationErrorType T errors.ValidationErrorType
F string F string
D string
}{ }{
"name > 63 characters": {[]api.Port{{Name: strings.Repeat("a", 64), ContainerPort: 80, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].name"}, "name > 63 characters": {[]api.Port{{Name: strings.Repeat("a", 64), ContainerPort: 80, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].name", dnsLabelErrorMsg},
"name not a DNS label": {[]api.Port{{Name: "a.b.c", 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", dnsLabelErrorMsg},
"name not unique": {[]api.Port{ "name not unique": {[]api.Port{
{Name: "abc", ContainerPort: 80, Protocol: "TCP"}, {Name: "abc", ContainerPort: 80, Protocol: "TCP"},
{Name: "abc", ContainerPort: 81, Protocol: "TCP"}, {Name: "abc", ContainerPort: 81, Protocol: "TCP"},
}, errors.ValidationErrorTypeDuplicate, "[1].name"}, }, errors.ValidationErrorTypeDuplicate, "[1].name", ""},
"zero container port": {[]api.Port{{ContainerPort: 0, Protocol: "TCP"}}, errors.ValidationErrorTypeRequired, "[0].containerPort"}, "zero container port": {[]api.Port{{ContainerPort: 0, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].containerPort", portRangeErrorMsg},
"invalid container port": {[]api.Port{{ContainerPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].containerPort"}, "invalid container port": {[]api.Port{{ContainerPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].containerPort", portRangeErrorMsg},
"invalid host port": {[]api.Port{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].hostPort"}, "invalid host port": {[]api.Port{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, errors.ValidationErrorTypeInvalid, "[0].hostPort", portRangeErrorMsg},
"invalid protocol": {[]api.Port{{ContainerPort: 80, Protocol: "ICMP"}}, errors.ValidationErrorTypeNotSupported, "[0].protocol"}, "invalid protocol": {[]api.Port{{ContainerPort: 80, Protocol: "ICMP"}}, errors.ValidationErrorTypeNotSupported, "[0].protocol", ""},
"protocol required": {[]api.Port{{Name: "abc", ContainerPort: 80}}, errors.ValidationErrorTypeRequired, "[0].protocol"}, "protocol required": {[]api.Port{{Name: "abc", ContainerPort: 80}}, errors.ValidationErrorTypeRequired, "[0].protocol", ""},
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := validatePorts(v.P) errs := validatePorts(v.P)
@ -228,6 +243,10 @@ func TestValidatePorts(t *testing.T) {
if errs[i].(*errors.ValidationError).Field != v.F { if errs[i].(*errors.ValidationError).Field != v.F {
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i]) t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
} }
detail := errs[i].(*errors.ValidationError).Detail
if detail != v.D {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail)
}
} }
} }
} }
@ -250,6 +269,13 @@ func TestValidateEnv(t *testing.T) {
for k, v := range errorCases { for k, v := range errorCases {
if errs := validateEnv(v); len(errs) == 0 { if errs := validateEnv(v); len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} else {
for i := range errs {
detail := errs[i].(*errors.ValidationError).Detail
if detail != "" && detail != cIdentifierErrorMsg {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, cIdentifierErrorMsg, detail)
}
}
} }
} }
} }
@ -1076,7 +1102,7 @@ func TestValidateService(t *testing.T) {
Protocol: "TCP", Protocol: "TCP",
}, },
}, },
// Should fail because protocol is missing. // Should fail because the session affinity is missing.
numErrs: 1, numErrs: 1,
}, },
{ {
@ -1397,6 +1423,9 @@ func TestValidateService(t *testing.T) {
errs := ValidateService(&svc) errs := ValidateService(&svc)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("Unexpected non-zero error list: %#v", errs) t.Errorf("Unexpected non-zero error list: %#v", errs)
for i := range errs {
t.Errorf("Found error: %s", errs[i].Error())
}
} }
} }
@ -2168,38 +2197,46 @@ func TestValidateResourceNames(t *testing.T) {
{"kubernetes.io", false}, {"kubernetes.io", false},
{"kubernetes.io/will/not/work/", false}, {"kubernetes.io/will/not/work/", false},
} }
for _, item := range table { for k, item := range table {
err := validateResourceName(item.input) err := validateResourceName(item.input, "sth")
if len(err) != 0 && item.success { if len(err) != 0 && item.success {
t.Errorf("expected no failure for input %q", item.input) t.Errorf("expected no failure for input %q", item.input)
} else if len(err) == 0 && !item.success { } else if len(err) == 0 && !item.success {
t.Errorf("expected failure for input %q", item.input) t.Errorf("expected failure for input %q", item.input)
for i := range err {
detail := err[i].(*errors.ValidationError).Detail
if detail != "" && detail != qualifiedNameErrorMsg {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, qualifiedNameErrorMsg, detail)
}
}
} }
} }
} }
func TestValidateLimitRange(t *testing.T) { func TestValidateLimitRange(t *testing.T) {
spec := api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Type: api.LimitTypePod,
Max: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("0"),
api.ResourceMemory: resource.MustParse("100"),
},
},
},
}
successCases := []api.LimitRange{ successCases := []api.LimitRange{
{ {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "abc", Name: "abc",
Namespace: "foo", Namespace: "foo",
}, },
Spec: api.LimitRangeSpec{ Spec: spec,
Limits: []api.LimitRangeItem{
{
Type: api.LimitTypePod,
Max: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("0"),
api.ResourceMemory: resource.MustParse("100"),
},
},
},
},
}, },
} }
@ -2209,82 +2246,65 @@ func TestValidateLimitRange(t *testing.T) {
} }
} }
errorCases := map[string]api.LimitRange{ errorCases := map[string]struct {
R api.LimitRange
D string
}{
"zero-length Name": { "zero-length Name": {
ObjectMeta: api.ObjectMeta{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
Name: "", "",
Namespace: "foo",
},
Spec: api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Type: api.LimitTypePod,
Max: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("0"),
api.ResourceMemory: resource.MustParse("100"),
},
},
},
},
}, },
"zero-length-namespace": { "zero-length-namespace": {
ObjectMeta: api.ObjectMeta{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
Name: "abc", "",
Namespace: "", },
}, "invalid Name": {
Spec: api.LimitRangeSpec{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
Limits: []api.LimitRangeItem{ dnsSubdomainErrorMsg,
{ },
Type: api.LimitTypePod, "invalid Namespace": {
Max: api.ResourceList{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
api.ResourceCPU: resource.MustParse("100"), dnsSubdomainErrorMsg,
api.ResourceMemory: resource.MustParse("10000"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("0"),
api.ResourceMemory: resource.MustParse("100"),
},
},
},
},
}, },
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidateLimitRange(&v) errs := ValidateLimitRange(&v.R)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
for i := range errs { for i := range errs {
field := errs[i].(*errors.ValidationError).Field field := errs[i].(*errors.ValidationError).Field
detail := errs[i].(*errors.ValidationError).Detail
if field != "name" && if field != "name" &&
field != "namespace" { field != "namespace" {
t.Errorf("%s: missing prefix for: %v", k, errs[i]) t.Errorf("%s: missing prefix for: %v", k, errs[i])
} }
if detail != v.D {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail)
}
} }
} }
} }
func TestValidateResourceQuota(t *testing.T) { func TestValidateResourceQuota(t *testing.T) {
spec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
api.ResourcePods: resource.MustParse("10"),
api.ResourceServices: resource.MustParse("10"),
api.ResourceReplicationControllers: resource.MustParse("10"),
api.ResourceQuotas: resource.MustParse("10"),
},
}
successCases := []api.ResourceQuota{ successCases := []api.ResourceQuota{
{ {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "abc", Name: "abc",
Namespace: "foo", Namespace: "foo",
}, },
Spec: api.ResourceQuotaSpec{ Spec: spec,
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
api.ResourcePods: resource.MustParse("10"),
api.ResourceServices: resource.MustParse("10"),
api.ResourceReplicationControllers: resource.MustParse("10"),
api.ResourceQuotas: resource.MustParse("10"),
},
},
}, },
} }
@ -2294,51 +2314,42 @@ func TestValidateResourceQuota(t *testing.T) {
} }
} }
errorCases := map[string]api.ResourceQuota{ errorCases := map[string]struct {
R api.ResourceQuota
D string
}{
"zero-length Name": { "zero-length Name": {
ObjectMeta: api.ObjectMeta{ api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "", Namespace: "foo"}, Spec: spec},
Name: "", "",
Namespace: "foo",
},
Spec: api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"),
api.ResourcePods: resource.MustParse("10"),
api.ResourceServices: resource.MustParse("10"),
api.ResourceReplicationControllers: resource.MustParse("10"),
api.ResourceQuotas: resource.MustParse("10"),
},
},
}, },
"zero-length-namespace": { "zero-length Namespace": {
ObjectMeta: api.ObjectMeta{ api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: ""}, Spec: spec},
Name: "abc", "",
Namespace: "", },
}, "invalid Name": {
Spec: api.ResourceQuotaSpec{ api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec},
Hard: api.ResourceList{ dnsSubdomainErrorMsg,
api.ResourceCPU: resource.MustParse("100"), },
api.ResourceMemory: resource.MustParse("10000"), "invalid Namespace": {
api.ResourcePods: resource.MustParse("10"), api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec},
api.ResourceServices: resource.MustParse("10"), dnsSubdomainErrorMsg,
api.ResourceReplicationControllers: resource.MustParse("10"),
api.ResourceQuotas: resource.MustParse("10"),
},
},
}, },
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidateResourceQuota(&v) errs := ValidateResourceQuota(&v.R)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
for i := range errs { for i := range errs {
field := errs[i].(*errors.ValidationError).Field field := errs[i].(*errors.ValidationError).Field
detail := errs[i].(*errors.ValidationError).Detail
if field != "name" && if field != "name" &&
field != "namespace" { field != "namespace" {
t.Errorf("%s: missing prefix for: %v", k, errs[i]) t.Errorf("%s: missing prefix for: %v", k, errs[i])
} }
if detail != v.D {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail)
}
} }
} }
} }

View File

@ -33,45 +33,45 @@ func IsDNSSubdomain(value string) bool {
return IsDNS1123Subdomain(value) return IsDNS1123Subdomain(value)
} }
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" const DNS1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$") var dns1123LabelRegexp = regexp.MustCompile("^" + DNS1123LabelFmt + "$")
const dns1123LabelMaxLength int = 63 const DNS1123LabelMaxLength int = 63
// IsDNS1123Label tests for a string that conforms to the definition of a label in // IsDNS1123Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1123). // DNS (RFC 1123).
func IsDNS1123Label(value string) bool { func IsDNS1123Label(value string) bool {
return len(value) <= dns1123LabelMaxLength && dns1123LabelRegexp.MatchString(value) return len(value) <= DNS1123LabelMaxLength && dns1123LabelRegexp.MatchString(value)
} }
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" const DNS1123SubdomainFmt string = DNS1123LabelFmt + "(\\." + DNS1123LabelFmt + ")*"
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") var dns1123SubdomainRegexp = regexp.MustCompile("^" + DNS1123SubdomainFmt + "$")
const dns1123SubdomainMaxLength int = 253 const DNS1123SubdomainMaxLength int = 253
// IsDNS1123Subdomain tests for a string that conforms to the definition of a // IsDNS1123Subdomain tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123). // subdomain in DNS (RFC 1123).
func IsDNS1123Subdomain(value string) bool { func IsDNS1123Subdomain(value string) bool {
return len(value) <= dns1123SubdomainMaxLength && dns1123SubdomainRegexp.MatchString(value) return len(value) <= DNS1123SubdomainMaxLength && dns1123SubdomainRegexp.MatchString(value)
} }
const dns952LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?" const DNS952LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
var dns952LabelRegexp = regexp.MustCompile("^" + dns952LabelFmt + "$") var dns952LabelRegexp = regexp.MustCompile("^" + DNS952LabelFmt + "$")
const dns952LabelMaxLength int = 24 const DNS952LabelMaxLength int = 24
// IsDNS952Label tests for a string that conforms to the definition of a label in // IsDNS952Label tests for a string that conforms to the definition of a label in
// DNS (RFC 952). // DNS (RFC 952).
func IsDNS952Label(value string) bool { func IsDNS952Label(value string) bool {
return len(value) <= dns952LabelMaxLength && dns952LabelRegexp.MatchString(value) return len(value) <= DNS952LabelMaxLength && dns952LabelRegexp.MatchString(value)
} }
const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*" const CIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") var cIdentifierRegexp = regexp.MustCompile("^" + CIdentifierFmt + "$")
// IsCIdentifier tests for a string that conforms the definition of an identifier // IsCIdentifier tests for a string that conforms the definition of an identifier
// in C. This checks the format, but not the length. // in C. This checks the format, but not the length.