package validation import ( "fmt" "reflect" "strings" "testing" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" ktypes "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/validation/field" "github.com/openshift/origin/pkg/api" authorizationapi "github.com/openshift/origin/pkg/authorization/api" ) // Ensures that `nil` can be passed to validation functions validating top-level objects func TestNilPath(t *testing.T) { var nilPath *field.Path = nil if s := nilPath.String(); s != "" { t.Errorf("Unexpected nil path: %q", s) } child := nilPath.Child("child") if s := child.String(); s != "child" { t.Errorf("Unexpected child path: %q", s) } key := nilPath.Key("key") if s := key.String(); s != "[key]" { t.Errorf("Unexpected key path: %q", s) } index := nilPath.Index(1) if s := index.String(); s != "[1]" { t.Errorf("Unexpected index path: %q", s) } } func TestNameFunc(t *testing.T) { const nameRulesMessage = `must match the regex [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* (e.g. 'example.com')` for apiType, validationInfo := range Validator.typeToValidator { if !validationInfo.HasObjectMeta { continue } apiValue := reflect.New(apiType.Elem()) apiObjectMeta := apiValue.Elem().FieldByName("ObjectMeta") // check for illegal names for _, illegalName := range api.NameMayNotBe { apiObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: illegalName})) errList := validationInfo.Validator.Validate(apiValue.Interface().(runtime.Object)) reasons := api.MinimalNameRequirements(illegalName, false) requiredMessage := strings.Join(reasons, ", ") if len(errList) == 0 { t.Errorf("expected error for %v in %v not found amongst %v. You probably need to add api.MinimalNameRequirements to your name validator..", illegalName, apiType.Elem(), errList) continue } foundExpectedError := false for _, err := range errList { validationError := err if validationError.Type != field.ErrorTypeInvalid || validationError.Field != "metadata.name" { continue } if validationError.Detail == requiredMessage { foundExpectedError = true break } // this message is from a stock name validation method in kube that covers our requirements in MinimalNameRequirements if validationError.Detail == nameRulesMessage { foundExpectedError = true break } } if !foundExpectedError { t.Errorf("expected error for %v in %v not found amongst %v. You probably need to add api.MinimalNameRequirements to your name validator.", illegalName, apiType.Elem(), errList) } } // check for illegal contents for _, illegalContent := range api.NameMayNotContain { illegalName := "a" + illegalContent + "b" apiObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: illegalName})) errList := validationInfo.Validator.Validate(apiValue.Interface().(runtime.Object)) reasons := api.MinimalNameRequirements(illegalName, false) requiredMessage := strings.Join(reasons, ", ") if len(errList) == 0 { t.Errorf("expected error for %v in %v not found amongst %v. You probably need to add api.MinimalNameRequirements to your name validator.", illegalName, apiType.Elem(), errList) continue } foundExpectedError := false for _, err := range errList { validationError := err if validationError.Type != field.ErrorTypeInvalid || validationError.Field != "metadata.name" { continue } if validationError.Detail == requiredMessage { foundExpectedError = true break } // this message is from a stock name validation method in kube that covers our requirements in MinimalNameRequirements if validationError.Detail == nameRulesMessage { foundExpectedError = true break } } if !foundExpectedError { t.Errorf("expected error for %v in %v not found amongst %v. You probably need to add api.MinimalNameRequirements to your name validator.", illegalName, apiType.Elem(), errList) } } } } func TestObjectMeta(t *testing.T) { for apiType, validationInfo := range Validator.typeToValidator { if !validationInfo.HasObjectMeta { continue } apiValue := reflect.New(apiType.Elem()) apiObjectMeta := apiValue.Elem().FieldByName("ObjectMeta") if validationInfo.IsNamespaced { apiObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: getValidName(apiType)})) } else { apiObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: getValidName(apiType), Namespace: kapi.NamespaceDefault})) } errList := validationInfo.Validator.Validate(apiValue.Interface().(runtime.Object)) requiredErrors := validation.ValidateObjectMeta(apiObjectMeta.Addr().Interface().(*kapi.ObjectMeta), validationInfo.IsNamespaced, api.MinimalNameRequirements, field.NewPath("metadata")) if len(errList) == 0 { t.Errorf("expected errors %v in %v not found amongst %v. You probably need to call kube/validation.ValidateObjectMeta in your validator.", requiredErrors, apiType.Elem(), errList) continue } for _, requiredError := range requiredErrors { foundExpectedError := false for _, err := range errList { validationError := err if fmt.Sprintf("%v", validationError) == fmt.Sprintf("%v", requiredError) { foundExpectedError = true break } } if !foundExpectedError { t.Errorf("expected error %v in %v not found amongst %v. You probably need to call kube/validation.ValidateObjectMeta in your validator.", requiredError, apiType.Elem(), errList) } } } } func getValidName(apiType reflect.Type) string { apiValue := reflect.New(apiType.Elem()) obj := apiValue.Interface().(runtime.Object) switch obj.(type) { case *authorizationapi.ClusterPolicyBinding, *authorizationapi.PolicyBinding: return ":default" case *authorizationapi.ClusterPolicy, *authorizationapi.Policy: return "default" default: return "any-string" } } // TestObjectMetaUpdate checks for: // 1. missing ResourceVersion // 2. mismatched Name // 3. mismatched Namespace // 4. mismatched UID func TestObjectMetaUpdate(t *testing.T) { for apiType, validationInfo := range Validator.typeToValidator { if !validationInfo.HasObjectMeta { continue } if !validationInfo.UpdateAllowed { continue } oldAPIValue := reflect.New(apiType.Elem()) oldAPIObjectMeta := oldAPIValue.Elem().FieldByName("ObjectMeta") oldAPIObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: "first-name", Namespace: "first-namespace", UID: ktypes.UID("first-uid")})) oldObj := oldAPIValue.Interface().(runtime.Object) oldObjMeta := oldAPIObjectMeta.Addr().Interface().(*kapi.ObjectMeta) newAPIValue := reflect.New(apiType.Elem()) newAPIObjectMeta := newAPIValue.Elem().FieldByName("ObjectMeta") newAPIObjectMeta.Set(reflect.ValueOf(kapi.ObjectMeta{Name: "second-name", Namespace: "second-namespace", UID: ktypes.UID("second-uid")})) newObj := newAPIValue.Interface().(runtime.Object) newObjMeta := newAPIObjectMeta.Addr().Interface().(*kapi.ObjectMeta) errList := validationInfo.Validator.ValidateUpdate(newObj, oldObj) requiredErrors := validation.ValidateObjectMetaUpdate(newObjMeta, oldObjMeta, field.NewPath("metadata")) if len(errList) == 0 { t.Errorf("expected errors %v in %v not found amongst %v. You probably need to call kube/validation.ValidateObjectMetaUpdate in your validator.", requiredErrors, apiType.Elem(), errList) continue } for _, requiredError := range requiredErrors { foundExpectedError := false for _, err := range errList { validationError := err if fmt.Sprintf("%v", validationError) == fmt.Sprintf("%v", requiredError) { foundExpectedError = true break } } if !foundExpectedError { t.Errorf("expected error %v in %v not found amongst %v. You probably need to call kube/validation.ValidateObjectMetaUpdate in your validator.", requiredError, apiType.Elem(), errList) } } } } func TestPodSpecNodeSelectorUpdateDisallowed(t *testing.T) { oldPod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ ResourceVersion: "1", }, Spec: kapi.PodSpec{ NodeSelector: map[string]string{ "foo": "bar", }, }, } if errs := validation.ValidatePodUpdate(oldPod, oldPod); len(errs) != 0 { t.Fatal("expected no errors") } newPod := *oldPod // use a new map so it doesn't change oldPod's map too newPod.Spec.NodeSelector = map[string]string{"foo": "other"} errs := validation.ValidatePodUpdate(&newPod, oldPod) if len(errs) == 0 { t.Fatal("expected at least 1 error") } }