From 548e01e457849eb52ce1bdd1b5a6e7d660c4f07f Mon Sep 17 00:00:00 2001 From: Ram Lavi Date: Wed, 29 May 2024 21:45:30 +0300 Subject: [PATCH] Add validation unit tests template currently fails with bazel visibility error, see issue https://github.com/google/cel-go/issues/947 tests need more scenarios to fit all validation rules but the gist is done. in order to run test with visibility check excluded: ``` hack/dockerized bazel test --test_output=errors --cache_test_results=no --check_visibility=false //pkg/virt-operator/... ``` Signed-off-by: Ram Lavi --- .../resource/generate/components/BUILD.bazel | 5 + .../validationaddmisionpolicy_test.go | 116 +++++++++++++++++- 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/pkg/virt-operator/resource/generate/components/BUILD.bazel b/pkg/virt-operator/resource/generate/components/BUILD.bazel index 075445866da2..dda7dc4d219a 100644 --- a/pkg/virt-operator/resource/generate/components/BUILD.bazel +++ b/pkg/virt-operator/resource/generate/components/BUILD.bazel @@ -84,6 +84,7 @@ go_test( "//staging/src/kubevirt.io/api/core/v1:go_default_library", "//staging/src/kubevirt.io/client-go/testutils:go_default_library", "//vendor/github.com/google/cel-go/cel:go_default_library", + "//vendor/github.com/google/cel-go/common/types:go_default_library", "//vendor/github.com/onsi/ginkgo/v2:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", "//vendor/github.com/openshift/api/security/v1:go_default_library", @@ -91,7 +92,11 @@ go_test( "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission/plugin/cel:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/cel:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/cel/environment:go_default_library", ], ) diff --git a/pkg/virt-operator/resource/generate/components/validationaddmisionpolicy_test.go b/pkg/virt-operator/resource/generate/components/validationaddmisionpolicy_test.go index 8c1b4fc083c6..5df10425135c 100644 --- a/pkg/virt-operator/resource/generate/components/validationaddmisionpolicy_test.go +++ b/pkg/virt-operator/resource/generate/components/validationaddmisionpolicy_test.go @@ -20,16 +20,25 @@ package components_test import ( + "context" "fmt" + "strings" - celgo "github.com/google/cel-go/cel" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apiserver/pkg/cel/environment" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission/plugin/cel" + celconfig "k8s.io/apiserver/pkg/apis/cel" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/cel/environment" + + celgo "github.com/google/cel-go/cel" + celtypes "github.com/google/cel-go/common/types" "kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components" ) @@ -45,7 +54,6 @@ var _ = Describe("Validation Admission Policy", func() { Expect(validatingAdmissionPolicyBinding.Kind).ToNot(BeEmpty()) }) }) - Context("ValidatingAdmissionPolicy", func() { It("should generate the expected policy", func() { const userName = "system:serviceaccount:kubevirt-ns:kubevirt-handler" @@ -55,7 +63,6 @@ var _ = Describe("Validation Admission Policy", func() { Expect(validatingAdmissionPolicy.Spec.MatchConditions[0].Expression).To(Equal(expectedMatchConditionExpression)) Expect(validatingAdmissionPolicy.Kind).ToNot(BeEmpty()) }) - Context("Validation Compile test", func() { var celCompiler *cel.CompositedCompiler BeforeEach(func() { @@ -82,6 +89,100 @@ var _ = Describe("Validation Admission Policy", func() { } }) }) + Context("Validation Filter test", func() { + var celCompiler *cel.CompositedCompiler + BeforeEach(func() { + compositionEnvTemplateWithoutStrictCost, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + Expect(err).ToNot(HaveOccurred()) + celCompiler = cel.NewCompositedCompilerFromTemplate(compositionEnvTemplateWithoutStrictCost) + }) + It("should fail patching the node with non-kubevirt label", func() { + const userName = "system:serviceaccount:kubevirt-ns:kubevirt-handler" + validatingAdmissionPolicy := components.NewHandlerV1ValidatingAdmissionPolicy(userName) + + //replace variables if all else fails + for idx, _ := range validatingAdmissionPolicy.Spec.Validations { + for _, variable := range validatingAdmissionPolicy.Spec.Variables { + validatingAdmissionPolicy.Spec.Validations[idx].Expression = strings.ReplaceAll(validatingAdmissionPolicy.Spec.Validations[idx].Expression, "variables."+variable.Name, variable.Expression) + } + } + options := cel.OptionalVariableDeclarations{ + HasParams: false, + HasAuthorizer: false, + } + mode := environment.NewExpressions + celCompiler.CompileAndStoreVariables(convertV1Variables(validatingAdmissionPolicy.Spec.Variables), options, mode) + + var expressions []cel.ExpressionAccessor + for _, validation := range validatingAdmissionPolicy.Spec.Validations { + expressions = append(expressions, convertV1Validation(validation)) + } + filterResults := celCompiler.FilterCompiler.Compile(expressions, options, mode) + Expect(filterResults.CompilationErrors()).To(HaveLen(0)) + + userInfo := &user.DefaultInfo{ + Name: userName, + } + const nodeName = "node01" + oldNode := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Labels: map[string]string{"label1": "val1"}, + Annotations: map[string]string{"annotations1": "val1"}, + }, + Spec: corev1.NodeSpec{ + Unschedulable: false, + }, + } + newNode := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Labels: map[string]string{"label1": "val1", "kubevirt.io/permittedLabel": ""}, + Annotations: map[string]string{"annotations1": "val1"}, + }, + Spec: corev1.NodeSpec{ + Unschedulable: false, + }, + } + nodeAttribiute := admission.NewAttributesRecord( + oldNode, + newNode, + corev1.SchemeGroupVersion.WithKind("Node"), + corev1.NamespaceAll, + nodeName, + corev1.SchemeGroupVersion.WithResource("nodes"), + "", + admission.Update, + &metav1.CreateOptions{}, + false, + userInfo, + ) + versionedAttr, err := admission.NewVersionedAttributes(nodeAttribiute, nodeAttribiute.GetKind(), newObjectInterfacesForTest()) + Expect(err).ToNot(HaveOccurred()) + + optionalVars := cel.OptionalVariableBindings{} + evalResults, _, err := filterResults.ForInput( + context.TODO(), + versionedAttr, + cel.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), + optionalVars, + nil, + celconfig.RuntimeCELCostBudget) + Expect(err).ToNot(HaveOccurred()) + + //varsPrettyVars, err := json.MarshalIndent(celCompiler.CompositionEnv.CompiledVariables, "", "\t") + //Expect(err).NotTo(HaveOccurred()) + //dataPrettyJSON, err := json.MarshalIndent(evalResults, "", "\t") + //Expect(err).NotTo(HaveOccurred()) + //fmt.Printf("\ncelCompiler.CompositionEnv.CompiledVariables = %v\nevalResults = \n%s\n\n", string(varsPrettyVars), string(dataPrettyJSON)) + + for _, result := range evalResults { + Expect(result.Error).To(BeNil(), fmt.Sprintf("validation policy failed: %q", result.ExpressionAccessor.GetExpression())) + Expect(result.EvalResult).To(Equal(celtypes.True), fmt.Sprintf("validation policy returned false value: %q", result.ExpressionAccessor.GetExpression())) + } + + }) + }) }) }) @@ -134,3 +235,10 @@ func convertV1Validation(validation admissionregistrationv1.Validation) cel.Expr Reason: validation.Reason, } } + +// newObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file. +func newObjectInterfacesForTest() admission.ObjectInterfaces { + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + return admission.NewObjectInterfacesFromScheme(scheme) +}