From 80143ee0b4e3ed5f0b4ab8d9d0b29aea3b70ee23 Mon Sep 17 00:00:00 2001 From: deads2k Date: Wed, 4 Jan 2017 12:09:24 -0500 Subject: [PATCH] snip links from genericapiserver to api/validation --- pkg/api/rest/BUILD | 2 +- pkg/api/rest/create.go | 4 +- pkg/api/rest/update.go | 4 +- pkg/api/validation/BUILD | 2 +- pkg/api/validation/genericvalidation/BUILD | 40 ++ pkg/api/validation/genericvalidation/doc.go | 18 + .../genericvalidation/validation.go | 313 ++++++++++++ .../genericvalidation/validation_test.go | 479 ++++++++++++++++++ pkg/api/validation/validation.go | 232 +-------- pkg/api/validation/validation_test.go | 446 ---------------- pkg/serviceaccount/BUILD | 2 +- pkg/serviceaccount/util.go | 6 +- 12 files changed, 882 insertions(+), 666 deletions(-) create mode 100644 pkg/api/validation/genericvalidation/BUILD create mode 100644 pkg/api/validation/genericvalidation/doc.go create mode 100644 pkg/api/validation/genericvalidation/validation.go create mode 100644 pkg/api/validation/genericvalidation/validation_test.go diff --git a/pkg/api/rest/BUILD b/pkg/api/rest/BUILD index 8bbafc7a0d..054eb54fed 100644 --- a/pkg/api/rest/BUILD +++ b/pkg/api/rest/BUILD @@ -23,7 +23,7 @@ go_library( "//pkg/api:go_default_library", "//pkg/api/errors:go_default_library", "//pkg/api/meta:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/validation/genericvalidation:go_default_library", "//pkg/api/validation/path:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//pkg/genericapiserver/api/request:go_default_library", diff --git a/pkg/api/rest/create.go b/pkg/api/rest/create.go index 12538b9204..17570985d1 100644 --- a/pkg/api/rest/create.go +++ b/pkg/api/rest/create.go @@ -19,7 +19,7 @@ package rest import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/validation/genericvalidation" path "k8s.io/kubernetes/pkg/api/validation/path" genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request" "k8s.io/kubernetes/pkg/runtime" @@ -83,7 +83,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, ob // Custom validation (including name validation) passed // Now run common validation on object meta // Do this *after* custom validation so that specific error messages are shown whenever possible - if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 { + if errs := genericvalidation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 { return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs) } diff --git a/pkg/api/rest/update.go b/pkg/api/rest/update.go index 6e5e9f41cb..88eaa2ca81 100644 --- a/pkg/api/rest/update.go +++ b/pkg/api/rest/update.go @@ -22,7 +22,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/validation/genericvalidation" genericapirequest "k8s.io/kubernetes/pkg/genericapiserver/api/request" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation/field" @@ -67,7 +67,7 @@ func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) { if err != nil { return nil, fmt.Errorf("failed to get old object metadata: %v", err) } - allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...) + allErrs = append(allErrs, genericvalidation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...) return allErrs, nil } diff --git a/pkg/api/validation/BUILD b/pkg/api/validation/BUILD index e6e524dc7c..fc1e76e19b 100644 --- a/pkg/api/validation/BUILD +++ b/pkg/api/validation/BUILD @@ -24,7 +24,7 @@ go_library( "//pkg/api/resource:go_default_library", "//pkg/api/service:go_default_library", "//pkg/api/util:go_default_library", - "//pkg/api/v1:go_default_library", + "//pkg/api/validation/genericvalidation:go_default_library", "//pkg/apimachinery/registered:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//pkg/apis/meta/v1/unstructured:go_default_library", diff --git a/pkg/api/validation/genericvalidation/BUILD b/pkg/api/validation/genericvalidation/BUILD new file mode 100644 index 0000000000..c0b1453b54 --- /dev/null +++ b/pkg/api/validation/genericvalidation/BUILD @@ -0,0 +1,40 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "validation.go", + ], + tags = ["automanaged"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/api/v1:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//pkg/apis/meta/v1/validation:go_default_library", + "//pkg/runtime/schema:go_default_library", + "//pkg/util/sets:go_default_library", + "//pkg/util/validation:go_default_library", + "//pkg/util/validation/field:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["validation_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//pkg/util/validation/field:go_default_library", + ], +) diff --git a/pkg/api/validation/genericvalidation/doc.go b/pkg/api/validation/genericvalidation/doc.go new file mode 100644 index 0000000000..a2814c4f2a --- /dev/null +++ b/pkg/api/validation/genericvalidation/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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. +*/ + +// this package is split out to snip links between the API server and the kube resource validation functions +package genericvalidation diff --git a/pkg/api/validation/genericvalidation/validation.go b/pkg/api/validation/genericvalidation/validation.go new file mode 100644 index 0000000000..6da2743b77 --- /dev/null +++ b/pkg/api/validation/genericvalidation/validation.go @@ -0,0 +1,313 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 genericvalidation + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" + v1validation "k8s.io/kubernetes/pkg/apis/meta/v1/validation" + "k8s.io/kubernetes/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/validation" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// TODO: delete this global variable when we enable the validation of common +// fields by default. +var RepairMalformedUpdates bool = true + +const IsNegativeErrorMsg string = `must be greater than or equal to 0` +const FieldImmutableErrorMsg string = `field is immutable` + +const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB + +// BannedOwners is a black list of object that are not allowed to be owners. +var BannedOwners = map[schema.GroupVersionKind]struct{}{ + v1.SchemeGroupVersion.WithKind("Event"): {}, +} + +// ValidateNameFunc validates that the provided name is valid for a given resource type. +// Not all resources have the same validation rules for names. Prefix is true +// if the name will have a value appended to it. If the name is not valid, +// this returns a list of descriptions of individual characteristics of the +// value that were not valid. Otherwise this returns an empty list or nil. +type ValidateNameFunc func(name string, prefix bool) []string + +// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. +func NameIsDNSSubdomain(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Subdomain(name) +} + +// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. +func NameIsDNSLabel(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Label(name) +} + +// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. +func NameIsDNS1035Label(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1035Label(name) +} + +// ValidateNamespaceName can be used to check whether the given namespace name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNamespaceName = NameIsDNSLabel + +// ValidateClusterName can be used to check whether the given cluster name is valid. +var ValidateClusterName = NameIsDNS1035Label + +// ValidateServiceAccountName can be used to check whether the given service account name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateServiceAccountName = NameIsDNSSubdomain + +// maskTrailingDash replaces the final character of a string with a subdomain safe +// value if is a dash. +func maskTrailingDash(name string) string { + if strings.HasSuffix(name, "-") { + return name[:len(name)-2] + "a" + } + return name +} + +// Validates that given value is not negative. +func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value < 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg)) + } + return allErrs +} + +// ValidateAnnotations validates that a set of annotations are correctly defined. +func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + var totalSize int64 + for k, v := range annotations { + for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { + allErrs = append(allErrs, field.Invalid(fldPath, k, msg)) + } + totalSize += (int64)(len(k)) + (int64)(len(v)) + } + if totalSize > (int64)(totalAnnotationSizeLimitB) { + allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB)) + } + return allErrs +} + +func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind) + // gvk.Group is empty for the legacy group. + if len(gvk.Version) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty")) + } + if len(gvk.Kind) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty")) + } + if len(ownerReference.Name) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty")) + } + if len(ownerReference.UID) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty")) + } + if _, ok := BannedOwners[gvk]; ok { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) + } + return allErrs +} + +func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + controllerName := "" + for _, ref := range ownerReferences { + allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...) + if ref.Controller != nil && *ref.Controller { + if controllerName != "" { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, + fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name))) + } else { + controllerName = ref.Name + } + } + } + return allErrs +} + +// Validate finalizer names +func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(stringValue) { + allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) + } + if len(allErrs) != 0 { + return allErrs + } + + if len(strings.Split(stringValue, "/")) == 1 { + if !api.IsStandardFinalizerName(stringValue) { + return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified")) + } + } + + return field.ErrorList{} +} + +func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { + const newFinalizersErrorMsg string = `no new finalizers can be added if the object is being deleted` + allErrs := field.ErrorList{} + extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) + if len(extra) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) + } + return allErrs +} + +func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !api.Semantic.DeepEqual(oldVal, newVal) { + allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg)) + } + return allErrs +} + +// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already +// been performed. +// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. +func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(meta.GenerateName) != 0 { + for _, msg := range nameFn(meta.GenerateName, true) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GenerateName, msg)) + } + } + // If the generated name validates, but the calculated value does not, it's a problem with generation, and we + // report it here. This may confuse users, but indicates a programming bug and still must be validated. + // If there are multiple fields out of which one is required then add an or as a separator + if len(meta.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) + } else { + for _, msg := range nameFn(meta.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.Name, msg)) + } + } + if requiresNamespace { + if len(meta.Namespace) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) + } else { + for _, msg := range ValidateNamespaceName(meta.Namespace, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.Namespace, msg)) + } + } + } else { + if len(meta.Namespace) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) + } + } + if len(meta.ClusterName) != 0 { + for _, msg := range ValidateClusterName(meta.ClusterName, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.ClusterName, msg)) + } + } + allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...) + allErrs = append(allErrs, v1validation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...) + for _, finalizer := range meta.Finalizers { + allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath.Child("finalizers"))...) + } + return allErrs +} + +// ValidateObjectMetaUpdate validates an object's metadata when updated +func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if !RepairMalformedUpdates && newMeta.UID != oldMeta.UID { + allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), newMeta.UID, "field is immutable")) + } + // in the event it is left empty, set it, to allow clients more flexibility + // TODO: remove the following code that repairs the update request when we retire the clients that modify the immutable fields. + // Please do not copy this pattern elsewhere; validation functions should not be modifying the objects they are passed! + if RepairMalformedUpdates { + if len(newMeta.UID) == 0 { + newMeta.UID = oldMeta.UID + } + // ignore changes to timestamp + if oldMeta.CreationTimestamp.IsZero() { + oldMeta.CreationTimestamp = newMeta.CreationTimestamp + } else { + newMeta.CreationTimestamp = oldMeta.CreationTimestamp + } + // an object can never remove a deletion timestamp or clear/change grace period seconds + if !oldMeta.DeletionTimestamp.IsZero() { + newMeta.DeletionTimestamp = oldMeta.DeletionTimestamp + } + if oldMeta.DeletionGracePeriodSeconds != nil && newMeta.DeletionGracePeriodSeconds == nil { + newMeta.DeletionGracePeriodSeconds = oldMeta.DeletionGracePeriodSeconds + } + } + + // TODO: needs to check if newMeta==nil && oldMeta !=nil after the repair logic is removed. + if newMeta.DeletionGracePeriodSeconds != nil && (oldMeta.DeletionGracePeriodSeconds == nil || *newMeta.DeletionGracePeriodSeconds != *oldMeta.DeletionGracePeriodSeconds) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionGracePeriodSeconds"), newMeta.DeletionGracePeriodSeconds, "field is immutable; may only be changed via deletion")) + } + if newMeta.DeletionTimestamp != nil && (oldMeta.DeletionTimestamp == nil || !newMeta.DeletionTimestamp.Equal(*oldMeta.DeletionTimestamp)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionTimestamp"), newMeta.DeletionTimestamp, "field is immutable; may only be changed via deletion")) + } + + // Finalizers cannot be added if the object is already being deleted. + if oldMeta.DeletionTimestamp != nil { + allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...) + } + + // Reject updates that don't specify a resource version + if len(newMeta.ResourceVersion) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.ResourceVersion, "must be specified for an update")) + } + + // Generation shouldn't be decremented + if newMeta.Generation < oldMeta.Generation { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.Generation, "must not be decremented")) + } + + allErrs = append(allErrs, ValidateImmutableField(newMeta.Name, oldMeta.Name, fldPath.Child("name"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.Namespace, oldMeta.Namespace, fldPath.Child("namespace"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.UID, oldMeta.UID, fldPath.Child("uid"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.ClusterName, oldMeta.ClusterName, fldPath.Child("clusterName"))...) + + allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...) + + return allErrs +} diff --git a/pkg/api/validation/genericvalidation/validation_test.go b/pkg/api/validation/genericvalidation/validation_test.go new file mode 100644 index 0000000000..7e3351e5f8 --- /dev/null +++ b/pkg/api/validation/genericvalidation/validation_test.go @@ -0,0 +1,479 @@ +/* +Copyright 2017 The Kubernetes Authors. + +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 genericvalidation + +import ( + "math/rand" + "reflect" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +const ( + maxLengthErrMsg = "must be no more than" + namePartErrMsg = "name part must consist of" + nameErrMsg = "a qualified name must consist of" +) + +// Ensure custom name functions are allowed +func TestValidateObjectMetaCustomName(t *testing.T) { + errs := ValidateObjectMeta( + &api.ObjectMeta{Name: "test", GenerateName: "foo"}, + false, + func(s string, prefix bool) []string { + if s == "test" { + return nil + } + return []string{"name-gen"} + }, + field.NewPath("field")) + if len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } + if !strings.Contains(errs[0].Error(), "name-gen") { + t.Errorf("unexpected error message: %v", errs) + } +} + +// Ensure namespace names follow dns label format +func TestValidateObjectMetaNamespaces(t *testing.T) { + errs := ValidateObjectMeta( + &api.ObjectMeta{Name: "test", Namespace: "foo.bar"}, + true, + func(s string, prefix bool) []string { + return nil + }, + field.NewPath("field")) + if len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } + if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) { + t.Errorf("unexpected error message: %v", errs) + } + maxLength := 63 + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b := make([]rune, maxLength+1) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + errs = ValidateObjectMeta( + &api.ObjectMeta{Name: "test", Namespace: string(b)}, + true, + func(s string, prefix bool) []string { + return nil + }, + field.NewPath("field")) + if len(errs) != 2 { + t.Fatalf("unexpected errors: %v", errs) + } + if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") { + t.Errorf("unexpected error message: %v", errs) + } +} + +func TestValidateObjectMetaOwnerReferences(t *testing.T) { + trueVar := true + falseVar := false + testCases := []struct { + description string + ownerReferences []metav1.OwnerReference + expectError bool + expectedErrorMessage string + }{ + { + description: "simple success - third party extension.", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "1", + }, + }, + expectError: false, + expectedErrorMessage: "", + }, + { + description: "simple failures - event shouldn't be set as an owner", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Event", + Name: "name", + UID: "1", + }, + }, + expectError: true, + expectedErrorMessage: "is disallowed from being an owner", + }, + { + description: "simple controller ref success - one reference with Controller set", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "1", + Controller: &falseVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "2", + Controller: &trueVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "3", + Controller: &falseVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "4", + }, + }, + expectError: false, + expectedErrorMessage: "", + }, + { + description: "simple controller ref failure - two references with Controller set", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "1", + Controller: &falseVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "2", + Controller: &trueVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "3", + Controller: &trueVar, + }, + { + APIVersion: "thirdpartyVersion", + Kind: "thirdpartyKind", + Name: "name", + UID: "4", + }, + }, + expectError: true, + expectedErrorMessage: "Only one reference can have Controller set to true", + }, + } + + for _, tc := range testCases { + errs := ValidateObjectMeta( + &api.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences}, + true, + func(s string, prefix bool) []string { + return nil + }, + field.NewPath("field")) + if len(errs) != 0 && !tc.expectError { + t.Errorf("unexpected error: %v in test case %v", errs, tc.description) + } + if len(errs) == 0 && tc.expectError { + t.Errorf("expect error in test case %v", tc.description) + } + if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) { + t.Errorf("unexpected error message: %v in test case %v", errs, tc.description) + } + } +} + +func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) { + if errs := ValidateObjectMetaUpdate( + &api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + field.NewPath("field"), + ); len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } + if errs := ValidateObjectMetaUpdate( + &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + &api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + field.NewPath("field"), + ); len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } + if errs := ValidateObjectMetaUpdate( + &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))}, + field.NewPath("field"), + ); len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } +} + +func TestValidateFinalizersUpdate(t *testing.T) { + testcases := map[string]struct { + Old api.ObjectMeta + New api.ObjectMeta + ExpectedErr string + }{ + "invalid adding finalizers": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, + ExpectedErr: "y/b", + }, + "invalid changing finalizers": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}}, + ExpectedErr: "x/b", + }, + "valid removing finalizers": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + ExpectedErr: "", + }, + "valid adding finalizers for objects not being deleted": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}}, + ExpectedErr: "", + }, + } + for name, tc := range testcases { + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) == 0 { + if len(tc.ExpectedErr) != 0 { + t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) + } + } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { + t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) + } + } +} + +func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { + now := metav1.NewTime(time.Unix(1000, 0).UTC()) + later := metav1.NewTime(time.Unix(2000, 0).UTC()) + gracePeriodShort := int64(30) + gracePeriodLong := int64(40) + + testcases := map[string]struct { + Old api.ObjectMeta + New api.ObjectMeta + ExpectedNew api.ObjectMeta + ExpectedErrs []string + }{ + "valid without deletion fields": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedErrs: []string{}, + }, + "valid with deletion fields": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedErrs: []string{}, + }, + + "invalid set deletionTimestamp": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable; may only be changed via deletion"}, + }, + "invalid clear deletionTimestamp": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedErrs: []string{}, // no errors, validation copies the old value + }, + "invalid change deletionTimestamp": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedErrs: []string{}, // no errors, validation copies the old value + }, + + "invalid set deletionGracePeriodSeconds": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable; may only be changed via deletion"}, + }, + "invalid clear deletionGracePeriodSeconds": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedErrs: []string{}, // no errors, validation copies the old value + }, + "invalid change deletionGracePeriodSeconds": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, + ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, + ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable; may only be changed via deletion"}, + }, + } + + for k, tc := range testcases { + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) != len(tc.ExpectedErrs) { + t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) + t.Logf("%s: Got: %#v", k, errs) + t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) + continue + } + for i := range errs { + if errs[i].Error() != tc.ExpectedErrs[i] { + t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error()) + } + } + if !reflect.DeepEqual(tc.New, tc.ExpectedNew) { + t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New) + } + } +} + +func TestObjectMetaGenerationUpdate(t *testing.T) { + testcases := map[string]struct { + Old api.ObjectMeta + New api.ObjectMeta + ExpectedErrs []string + }{ + "invalid generation change - decremented": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4}, + ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"}, + }, + "valid generation change - incremented by one": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2}, + ExpectedErrs: []string{}, + }, + "valid generation field - not updated": { + Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + ExpectedErrs: []string{}, + }, + } + + for k, tc := range testcases { + errList := []string{} + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) != len(tc.ExpectedErrs) { + t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) + for _, err := range errs { + errList = append(errList, err.Error()) + } + t.Logf("%s: Got: %#v", k, errList) + t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) + continue + } + for i := range errList { + if errList[i] != tc.ExpectedErrs[i] { + t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i]) + } + } + } +} + +// Ensure trailing slash is allowed in generate name +func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) { + errs := ValidateObjectMeta( + &api.ObjectMeta{Name: "test", GenerateName: "foo-"}, + false, + NameIsDNSSubdomain, + field.NewPath("field")) + if len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } +} + +func TestValidateAnnotations(t *testing.T) { + successCases := []map[string]string{ + {"simple": "bar"}, + {"now-with-dashes": "bar"}, + {"1-starts-with-num": "bar"}, + {"1234": "bar"}, + {"simple/simple": "bar"}, + {"now-with-dashes/simple": "bar"}, + {"now-with-dashes/now-with-dashes": "bar"}, + {"now.with.dots/simple": "bar"}, + {"now-with.dashes-and.dots/simple": "bar"}, + {"1-num.2-num/3-num": "bar"}, + {"1234/5678": "bar"}, + {"1.2.3.4/5678": "bar"}, + {"UpperCase123": "bar"}, + {"a": strings.Repeat("b", totalAnnotationSizeLimitB-1)}, + { + "a": strings.Repeat("b", totalAnnotationSizeLimitB/2-1), + "c": strings.Repeat("d", totalAnnotationSizeLimitB/2-1), + }, + } + for i := range successCases { + errs := ValidateAnnotations(successCases[i], field.NewPath("field")) + if len(errs) != 0 { + t.Errorf("case[%d] expected success, got %#v", i, errs) + } + } + + nameErrorCases := []struct { + annotations map[string]string + expect string + }{ + {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, + {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, + {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, + {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, + } + for i := range nameErrorCases { + errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d]: expected failure", i) + } else { + if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) { + t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail) + } + } + } + totalSizeErrorCases := []map[string]string{ + {"a": strings.Repeat("b", totalAnnotationSizeLimitB)}, + { + "a": strings.Repeat("b", totalAnnotationSizeLimitB/2), + "c": strings.Repeat("d", totalAnnotationSizeLimitB/2), + }, + } + for i := range totalSizeErrorCases { + errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d] expected failure", i) + } + } +} diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 95c0062268..2c6e2749d3 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -27,17 +27,17 @@ import ( "strings" "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" utilpod "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/api/resource" apiservice "k8s.io/kubernetes/pkg/api/service" - "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/api/validation/genericvalidation" metav1 "k8s.io/kubernetes/pkg/apis/meta/v1" unversionedvalidation "k8s.io/kubernetes/pkg/apis/meta/v1/validation" storageutil "k8s.io/kubernetes/pkg/apis/storage/util" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/labels" - "k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/security/apparmor" utilconfig "k8s.io/kubernetes/pkg/util/config" "k8s.io/kubernetes/pkg/util/intstr" @@ -48,22 +48,18 @@ import ( // TODO: delete this global variable when we enable the validation of common // fields by default. -var RepairMalformedUpdates bool = true +var RepairMalformedUpdates bool = genericvalidation.RepairMalformedUpdates -const isNegativeErrorMsg string = `must be greater than or equal to 0` +const isNegativeErrorMsg string = genericvalidation.IsNegativeErrorMsg const isInvalidQuotaResource string = `must be a standard resource for quota` -const fieldImmutableErrorMsg string = `field is immutable` +const fieldImmutableErrorMsg string = genericvalidation.FieldImmutableErrorMsg const isNotIntegerErrorMsg string = `must be an integer` var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255) var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive" -const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB - // BannedOwners is a black list of object that are not allowed to be owners. -var BannedOwners = map[schema.GroupVersionKind]struct{}{ - v1.SchemeGroupVersion.WithKind("Event"): {}, -} +var BannedOwners = genericvalidation.BannedOwners // ValidateHasLabel requires that api.ObjectMeta has a Label with key and expectedValue func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { @@ -83,18 +79,7 @@ func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedVal // ValidateAnnotations validates that a set of annotations are correctly defined. func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - var totalSize int64 - for k, v := range annotations { - for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { - allErrs = append(allErrs, field.Invalid(fldPath, k, msg)) - } - totalSize += (int64)(len(k)) + (int64)(len(v)) - } - if totalSize > (int64)(totalAnnotationSizeLimitB) { - allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB)) - } - return allErrs + return genericvalidation.ValidateAnnotations(annotations, fldPath) } func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList { @@ -185,43 +170,8 @@ func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath return allErrs } -func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind) - // gvk.Group is empty for the legacy group. - if len(gvk.Version) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty")) - } - if len(gvk.Kind) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty")) - } - if len(ownerReference.Name) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty")) - } - if len(ownerReference.UID) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty")) - } - if _, ok := BannedOwners[gvk]; ok { - allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) - } - return allErrs -} - func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - controllerName := "" - for _, ref := range ownerReferences { - allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...) - if ref.Controller != nil && *ref.Controller { - if controllerName != "" { - allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, - fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name))) - } else { - controllerName = ref.Name - } - } - } - return allErrs + return genericvalidation.ValidateOwnerReferences(ownerReferences, fldPath) } // ValidateNameFunc validates that the provided name is valid for a given resource type. @@ -229,7 +179,7 @@ func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *f // if the name will have a value appended to it. If the name is not valid, // this returns a list of descriptions of individual characteristics of the // value that were not valid. Otherwise this returns an empty list or nil. -type ValidateNameFunc func(name string, prefix bool) []string +type ValidateNameFunc genericvalidation.ValidateNameFunc // maskTrailingDash replaces the final character of a string with a subdomain safe // value if is a dash. @@ -264,7 +214,7 @@ var ValidateNodeName = NameIsDNSSubdomain // ValidateNamespaceName can be used to check whether the given namespace name is valid. // Prefix indicates this name will be used as part of generation, in which case // trailing dashes are allowed. -var ValidateNamespaceName = NameIsDNSLabel +var ValidateNamespaceName = genericvalidation.ValidateNamespaceName // ValidateLimitRangeName can be used to check whether the given limit range name is valid. // Prefix indicates this name will be used as part of generation, in which case @@ -285,7 +235,7 @@ var ValidateSecretName = NameIsDNSSubdomain // ValidateServiceAccountName can be used to check whether the given service account name is valid. // Prefix indicates this name will be used as part of generation, in which case // trailing dashes are allowed. -var ValidateServiceAccountName = NameIsDNSSubdomain +var ValidateServiceAccountName = genericvalidation.ValidateServiceAccountName // ValidateEndpointsName can be used to check whether the given endpoints name is valid. // Prefix indicates this name will be used as part of generation, in which case @@ -293,39 +243,27 @@ var ValidateServiceAccountName = NameIsDNSSubdomain var ValidateEndpointsName = NameIsDNSSubdomain // ValidateClusterName can be used to check whether the given cluster name is valid. -var ValidateClusterName = NameIsDNS1035Label +var ValidateClusterName = genericvalidation.ValidateClusterName +// TODO update all references to these functions to point to the genericvalidation ones // NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. func NameIsDNSSubdomain(name string, prefix bool) []string { - if prefix { - name = maskTrailingDash(name) - } - return validation.IsDNS1123Subdomain(name) + return genericvalidation.NameIsDNSSubdomain(name, prefix) } // NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. func NameIsDNSLabel(name string, prefix bool) []string { - if prefix { - name = maskTrailingDash(name) - } - return validation.IsDNS1123Label(name) + return genericvalidation.NameIsDNSLabel(name, prefix) } // NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. func NameIsDNS1035Label(name string, prefix bool) []string { - if prefix { - name = maskTrailingDash(name) - } - return validation.IsDNS1035Label(name) + return genericvalidation.NameIsDNS1035Label(name, prefix) } // Validates that given value is not negative. func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if value < 0 { - allErrs = append(allErrs, field.Invalid(fldPath, value, isNegativeErrorMsg)) - } - return allErrs + return genericvalidation.ValidateNonnegativeField(value, fldPath) } // Validates that a Quantity is not negative @@ -338,11 +276,7 @@ func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) f } func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if !api.Semantic.DeepEqual(oldVal, newVal) { - allErrs = append(allErrs, field.Invalid(fldPath, newVal, fieldImmutableErrorMsg)) - } - return allErrs + return genericvalidation.ValidateImmutableField(newVal, oldVal, fldPath) } func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList { @@ -359,124 +293,16 @@ func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. // TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate. func ValidateObjectMeta(meta *api.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if len(meta.GenerateName) != 0 { - for _, msg := range nameFn(meta.GenerateName, true) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GenerateName, msg)) - } - } - // If the generated name validates, but the calculated value does not, it's a problem with generation, and we - // report it here. This may confuse users, but indicates a programming bug and still must be validated. - // If there are multiple fields out of which one is required then add an or as a separator - if len(meta.Name) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) - } else { - for _, msg := range nameFn(meta.Name, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.Name, msg)) - } - } - if requiresNamespace { - if len(meta.Namespace) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) - } else { - for _, msg := range ValidateNamespaceName(meta.Namespace, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.Namespace, msg)) - } - } - } else { - if len(meta.Namespace) != 0 { - allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) - } - } - if len(meta.ClusterName) != 0 { - for _, msg := range ValidateClusterName(meta.ClusterName, false) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.ClusterName, msg)) - } - } - allErrs = append(allErrs, ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...) - allErrs = append(allErrs, unversionedvalidation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...) - allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...) - allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...) - for _, finalizer := range meta.Finalizers { - allErrs = append(allErrs, validateFinalizerName(finalizer, fldPath.Child("finalizers"))...) - } - return allErrs + return genericvalidation.ValidateObjectMeta(meta, requiresNamespace, genericvalidation.ValidateNameFunc(nameFn), fldPath) } // ValidateObjectMetaUpdate validates an object's metadata when updated func ValidateObjectMetaUpdate(newMeta, oldMeta *api.ObjectMeta, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - if !RepairMalformedUpdates && newMeta.UID != oldMeta.UID { - allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), newMeta.UID, "field is immutable")) - } - // in the event it is left empty, set it, to allow clients more flexibility - // TODO: remove the following code that repairs the update request when we retire the clients that modify the immutable fields. - // Please do not copy this pattern elsewhere; validation functions should not be modifying the objects they are passed! - if RepairMalformedUpdates { - if len(newMeta.UID) == 0 { - newMeta.UID = oldMeta.UID - } - // ignore changes to timestamp - if oldMeta.CreationTimestamp.IsZero() { - oldMeta.CreationTimestamp = newMeta.CreationTimestamp - } else { - newMeta.CreationTimestamp = oldMeta.CreationTimestamp - } - // an object can never remove a deletion timestamp or clear/change grace period seconds - if !oldMeta.DeletionTimestamp.IsZero() { - newMeta.DeletionTimestamp = oldMeta.DeletionTimestamp - } - if oldMeta.DeletionGracePeriodSeconds != nil && newMeta.DeletionGracePeriodSeconds == nil { - newMeta.DeletionGracePeriodSeconds = oldMeta.DeletionGracePeriodSeconds - } - } - - // TODO: needs to check if newMeta==nil && oldMeta !=nil after the repair logic is removed. - if newMeta.DeletionGracePeriodSeconds != nil && (oldMeta.DeletionGracePeriodSeconds == nil || *newMeta.DeletionGracePeriodSeconds != *oldMeta.DeletionGracePeriodSeconds) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionGracePeriodSeconds"), newMeta.DeletionGracePeriodSeconds, "field is immutable; may only be changed via deletion")) - } - if newMeta.DeletionTimestamp != nil && (oldMeta.DeletionTimestamp == nil || !newMeta.DeletionTimestamp.Equal(*oldMeta.DeletionTimestamp)) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("deletionTimestamp"), newMeta.DeletionTimestamp, "field is immutable; may only be changed via deletion")) - } - - // Finalizers cannot be added if the object is already being deleted. - if oldMeta.DeletionTimestamp != nil { - allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.Finalizers, oldMeta.Finalizers, fldPath.Child("finalizers"))...) - } - - // Reject updates that don't specify a resource version - if len(newMeta.ResourceVersion) == 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.ResourceVersion, "must be specified for an update")) - } - - // Generation shouldn't be decremented - if newMeta.Generation < oldMeta.Generation { - allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.Generation, "must not be decremented")) - } - - allErrs = append(allErrs, ValidateImmutableField(newMeta.Name, oldMeta.Name, fldPath.Child("name"))...) - allErrs = append(allErrs, ValidateImmutableField(newMeta.Namespace, oldMeta.Namespace, fldPath.Child("namespace"))...) - allErrs = append(allErrs, ValidateImmutableField(newMeta.UID, oldMeta.UID, fldPath.Child("uid"))...) - allErrs = append(allErrs, ValidateImmutableField(newMeta.CreationTimestamp, oldMeta.CreationTimestamp, fldPath.Child("creationTimestamp"))...) - allErrs = append(allErrs, ValidateImmutableField(newMeta.ClusterName, oldMeta.ClusterName, fldPath.Child("clusterName"))...) - - allErrs = append(allErrs, unversionedvalidation.ValidateLabels(newMeta.Labels, fldPath.Child("labels"))...) - allErrs = append(allErrs, ValidateAnnotations(newMeta.Annotations, fldPath.Child("annotations"))...) - allErrs = append(allErrs, ValidateOwnerReferences(newMeta.OwnerReferences, fldPath.Child("ownerReferences"))...) - - return allErrs + return genericvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath) } func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { - const newFinalizersErrorMsg string = `no new finalizers can be added if the object is being deleted` - allErrs := field.ErrorList{} - extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) - if len(extra) != 0 { - allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) - } - return allErrs + return genericvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath) } func validateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) { @@ -3482,21 +3308,7 @@ func ValidateNamespace(namespace *api.Namespace) field.ErrorList { // Validate finalizer names func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - for _, msg := range validation.IsQualifiedName(stringValue) { - allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) - } - if len(allErrs) != 0 { - return allErrs - } - - if len(strings.Split(stringValue, "/")) == 1 { - if !api.IsStandardFinalizerName(stringValue) { - return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified")) - } - } - - return field.ErrorList{} + return genericvalidation.ValidateFinalizerName(stringValue, fldPath) } // ValidateNamespaceUpdate tests to make sure a namespace update can be applied. diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index c5acf5cb4f..47450668ac 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -17,11 +17,9 @@ limitations under the License. package validation import ( - "math/rand" "reflect" "strings" "testing" - "time" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" @@ -55,450 +53,6 @@ func expectPrefix(t *testing.T, prefix string, errs field.ErrorList) { } } -// Ensure custom name functions are allowed -func TestValidateObjectMetaCustomName(t *testing.T) { - errs := ValidateObjectMeta( - &api.ObjectMeta{Name: "test", GenerateName: "foo"}, - false, - func(s string, prefix bool) []string { - if s == "test" { - return nil - } - return []string{"name-gen"} - }, - field.NewPath("field")) - if len(errs) != 1 { - t.Fatalf("unexpected errors: %v", errs) - } - if !strings.Contains(errs[0].Error(), "name-gen") { - t.Errorf("unexpected error message: %v", errs) - } -} - -// Ensure namespace names follow dns label format -func TestValidateObjectMetaNamespaces(t *testing.T) { - errs := ValidateObjectMeta( - &api.ObjectMeta{Name: "test", Namespace: "foo.bar"}, - true, - func(s string, prefix bool) []string { - return nil - }, - field.NewPath("field")) - if len(errs) != 1 { - t.Fatalf("unexpected errors: %v", errs) - } - if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) { - t.Errorf("unexpected error message: %v", errs) - } - maxLength := 63 - letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - b := make([]rune, maxLength+1) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] - } - errs = ValidateObjectMeta( - &api.ObjectMeta{Name: "test", Namespace: string(b)}, - true, - func(s string, prefix bool) []string { - return nil - }, - field.NewPath("field")) - if len(errs) != 2 { - t.Fatalf("unexpected errors: %v", errs) - } - if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") { - t.Errorf("unexpected error message: %v", errs) - } -} - -func TestValidateObjectMetaOwnerReferences(t *testing.T) { - trueVar := true - falseVar := false - testCases := []struct { - description string - ownerReferences []metav1.OwnerReference - expectError bool - expectedErrorMessage string - }{ - { - description: "simple success - third party extension.", - ownerReferences: []metav1.OwnerReference{ - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "1", - }, - }, - expectError: false, - expectedErrorMessage: "", - }, - { - description: "simple failures - event shouldn't be set as an owner", - ownerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "Event", - Name: "name", - UID: "1", - }, - }, - expectError: true, - expectedErrorMessage: "is disallowed from being an owner", - }, - { - description: "simple controller ref success - one reference with Controller set", - ownerReferences: []metav1.OwnerReference{ - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "1", - Controller: &falseVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "2", - Controller: &trueVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "3", - Controller: &falseVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "4", - }, - }, - expectError: false, - expectedErrorMessage: "", - }, - { - description: "simple controller ref failure - two references with Controller set", - ownerReferences: []metav1.OwnerReference{ - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "1", - Controller: &falseVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "2", - Controller: &trueVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "3", - Controller: &trueVar, - }, - { - APIVersion: "thirdpartyVersion", - Kind: "thirdpartyKind", - Name: "name", - UID: "4", - }, - }, - expectError: true, - expectedErrorMessage: "Only one reference can have Controller set to true", - }, - } - - for _, tc := range testCases { - errs := ValidateObjectMeta( - &api.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences}, - true, - func(s string, prefix bool) []string { - return nil - }, - field.NewPath("field")) - if len(errs) != 0 && !tc.expectError { - t.Errorf("unexpected error: %v in test case %v", errs, tc.description) - } - if len(errs) == 0 && tc.expectError { - t.Errorf("expect error in test case %v", tc.description) - } - if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) { - t.Errorf("unexpected error message: %v in test case %v", errs, tc.description) - } - } -} - -func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) { - if errs := ValidateObjectMetaUpdate( - &api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, - field.NewPath("field"), - ); len(errs) != 0 { - t.Fatalf("unexpected errors: %v", errs) - } - if errs := ValidateObjectMetaUpdate( - &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, - &api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - field.NewPath("field"), - ); len(errs) != 0 { - t.Fatalf("unexpected errors: %v", errs) - } - if errs := ValidateObjectMetaUpdate( - &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, - &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))}, - field.NewPath("field"), - ); len(errs) != 0 { - t.Fatalf("unexpected errors: %v", errs) - } -} - -func TestValidateFinalizersUpdate(t *testing.T) { - testcases := map[string]struct { - Old api.ObjectMeta - New api.ObjectMeta - ExpectedErr string - }{ - "invalid adding finalizers": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, - ExpectedErr: "y/b", - }, - "invalid changing finalizers": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}}, - ExpectedErr: "x/b", - }, - "valid removing finalizers": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, - ExpectedErr: "", - }, - "valid adding finalizers for objects not being deleted": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}}, - ExpectedErr: "", - }, - } - for name, tc := range testcases { - errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) - if len(errs) == 0 { - if len(tc.ExpectedErr) != 0 { - t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) - } - } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { - t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) - } - } -} - -func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { - now := metav1.NewTime(time.Unix(1000, 0).UTC()) - later := metav1.NewTime(time.Unix(2000, 0).UTC()) - gracePeriodShort := int64(30) - gracePeriodLong := int64(40) - - testcases := map[string]struct { - Old api.ObjectMeta - New api.ObjectMeta - ExpectedNew api.ObjectMeta - ExpectedErrs []string - }{ - "valid without deletion fields": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - ExpectedErrs: []string{}, - }, - "valid with deletion fields": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, - ExpectedErrs: []string{}, - }, - - "invalid set deletionTimestamp": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable; may only be changed via deletion"}, - }, - "invalid clear deletionTimestamp": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - ExpectedErrs: []string{}, // no errors, validation copies the old value - }, - "invalid change deletionTimestamp": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, - ExpectedErrs: []string{}, // no errors, validation copies the old value - }, - - "invalid set deletionGracePeriodSeconds": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, - ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable; may only be changed via deletion"}, - }, - "invalid clear deletionGracePeriodSeconds": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1"}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, - ExpectedErrs: []string{}, // no errors, validation copies the old value - }, - "invalid change deletionGracePeriodSeconds": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, - ExpectedNew: api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, - ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable; may only be changed via deletion"}, - }, - } - - for k, tc := range testcases { - errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) - if len(errs) != len(tc.ExpectedErrs) { - t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) - t.Logf("%s: Got: %#v", k, errs) - t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) - continue - } - for i := range errs { - if errs[i].Error() != tc.ExpectedErrs[i] { - t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error()) - } - } - if !reflect.DeepEqual(tc.New, tc.ExpectedNew) { - t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New) - } - } -} - -func TestObjectMetaGenerationUpdate(t *testing.T) { - testcases := map[string]struct { - Old api.ObjectMeta - New api.ObjectMeta - ExpectedErrs []string - }{ - "invalid generation change - decremented": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4}, - ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"}, - }, - "valid generation change - incremented by one": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2}, - ExpectedErrs: []string{}, - }, - "valid generation field - not updated": { - Old: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, - New: api.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, - ExpectedErrs: []string{}, - }, - } - - for k, tc := range testcases { - errList := []string{} - errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) - if len(errs) != len(tc.ExpectedErrs) { - t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) - for _, err := range errs { - errList = append(errList, err.Error()) - } - t.Logf("%s: Got: %#v", k, errList) - t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) - continue - } - for i := range errList { - if errList[i] != tc.ExpectedErrs[i] { - t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i]) - } - } - } -} - -// Ensure trailing slash is allowed in generate name -func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) { - errs := ValidateObjectMeta( - &api.ObjectMeta{Name: "test", GenerateName: "foo-"}, - false, - NameIsDNSSubdomain, - field.NewPath("field")) - if len(errs) != 0 { - t.Fatalf("unexpected errors: %v", errs) - } -} - -func TestValidateAnnotations(t *testing.T) { - successCases := []map[string]string{ - {"simple": "bar"}, - {"now-with-dashes": "bar"}, - {"1-starts-with-num": "bar"}, - {"1234": "bar"}, - {"simple/simple": "bar"}, - {"now-with-dashes/simple": "bar"}, - {"now-with-dashes/now-with-dashes": "bar"}, - {"now.with.dots/simple": "bar"}, - {"now-with.dashes-and.dots/simple": "bar"}, - {"1-num.2-num/3-num": "bar"}, - {"1234/5678": "bar"}, - {"1.2.3.4/5678": "bar"}, - {"UpperCase123": "bar"}, - {"a": strings.Repeat("b", totalAnnotationSizeLimitB-1)}, - { - "a": strings.Repeat("b", totalAnnotationSizeLimitB/2-1), - "c": strings.Repeat("d", totalAnnotationSizeLimitB/2-1), - }, - } - for i := range successCases { - errs := ValidateAnnotations(successCases[i], field.NewPath("field")) - if len(errs) != 0 { - t.Errorf("case[%d] expected success, got %#v", i, errs) - } - } - - nameErrorCases := []struct { - annotations map[string]string - expect string - }{ - {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, - {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, - {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, - {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, - } - for i := range nameErrorCases { - errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field")) - if len(errs) != 1 { - t.Errorf("case[%d]: expected failure", i) - } else { - if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) { - t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail) - } - } - } - totalSizeErrorCases := []map[string]string{ - {"a": strings.Repeat("b", totalAnnotationSizeLimitB)}, - { - "a": strings.Repeat("b", totalAnnotationSizeLimitB/2), - "c": strings.Repeat("d", totalAnnotationSizeLimitB/2), - }, - } - for i := range totalSizeErrorCases { - errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field")) - if len(errs) != 1 { - t.Errorf("case[%d] expected failure", i) - } - } -} - func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume { objMeta := api.ObjectMeta{Name: name} if namespace != "" { diff --git a/pkg/serviceaccount/BUILD b/pkg/serviceaccount/BUILD index 198bd315dd..2458baf4db 100644 --- a/pkg/serviceaccount/BUILD +++ b/pkg/serviceaccount/BUILD @@ -18,7 +18,7 @@ go_library( deps = [ "//pkg/api:go_default_library", "//pkg/api/v1:go_default_library", - "//pkg/api/validation:go_default_library", + "//pkg/api/validation/genericvalidation:go_default_library", "//pkg/auth/authenticator:go_default_library", "//vendor:github.com/dgrijalva/jwt-go", "//vendor:github.com/golang/glog", diff --git a/pkg/serviceaccount/util.go b/pkg/serviceaccount/util.go index 9741fe424d..88cc423dbb 100644 --- a/pkg/serviceaccount/util.go +++ b/pkg/serviceaccount/util.go @@ -23,7 +23,7 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/api/validation/genericvalidation" ) const ( @@ -53,10 +53,10 @@ func SplitUsername(username string) (string, string, error) { return "", "", invalidUsernameErr } namespace, name := parts[0], parts[1] - if len(validation.ValidateNamespaceName(namespace, false)) != 0 { + if len(genericvalidation.ValidateNamespaceName(namespace, false)) != 0 { return "", "", invalidUsernameErr } - if len(validation.ValidateServiceAccountName(name, false)) != 0 { + if len(genericvalidation.ValidateServiceAccountName(name, false)) != 0 { return "", "", invalidUsernameErr } return namespace, name, nil