From 3eed801adc4099ccc37b5cd67c62f569fb942c88 Mon Sep 17 00:00:00 2001 From: Kermit Alexander Date: Tue, 14 Jun 2022 16:08:47 +0000 Subject: [PATCH] Add k8s CEL library and runtime cost tests. --- test/e2e/apimachinery/crd_validation_rules.go | 124 +++++++++++++++++- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/test/e2e/apimachinery/crd_validation_rules.go b/test/e2e/apimachinery/crd_validation_rules.go index 4613c6a52b4..66267d47058 100644 --- a/test/e2e/apimachinery/crd_validation_rules.go +++ b/test/e2e/apimachinery/crd_validation_rules.go @@ -61,17 +61,26 @@ var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alp return &c } + // for all new CRD validation features that should be E2E-tested, add them + // into this schema and then add CR requests to the end of the the test + // below ("MUST NOT fail validation...") instead of writing a new and + // separate test var schemaWithValidationExpression = unmarshallSchema([]byte(`{ "type":"object", "properties":{ "spec":{ "type":"object", "x-kubernetes-validations":[ - { "rule":"self.x + self.y > 0" } + { "rule":"self.x + self.y > 0" }, + { "rule":"self.firstArray.isSorted() && self.secondArray.isSorted() && ((self.firstArray.sum() + self.secondArray.sum()) % 2 == 0)" }, + { "rule":"self.largeArray.all(x, self.largeArray.all(y, y == x))" } ], "properties":{ "x":{ "type":"integer" }, - "y":{ "type":"integer" } + "y":{ "type":"integer" }, + "firstArray":{ "type":"array", "maxItems": 1000, "items":{ "type": "integer"} }, + "secondArray":{ "type":"array", "maxItems": 1000, "items":{ "type": "integer"} }, + "largeArray":{ "type":"array", "maxItems": 725, "items":{ "type": "integer"} } } }, "status":{ @@ -85,7 +94,7 @@ var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alp } } }`)) - ginkgo.It("MUST NOT fail validation for create of a custom resource that satisfies the x-kubernetes-validator rules", func() { + ginkgo.It("MUST NOT fail validation for create of a custom resource that satisfies the x-kubernetes-validations rules", func() { ginkgo.By("Creating a custom resource definition with validation rules") crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false) crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) @@ -106,13 +115,16 @@ var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alp "namespace": f.Namespace.Name, }, "spec": map[string]interface{}{ - "x": int64(1), - "y": int64(0), + "x": int64(1), + "y": int64(0), + "firstArray": []int64{3, 4}, + "secondArray": []int64{5, 10}, + "largeArray": []int64{2, 2}, }, }}, metav1.CreateOptions{}) framework.ExpectNoError(err, "validation rules satisfied") }) - ginkgo.It("MUST fail validation for create of a custom resource that does not satisfy the x-kubernetes-validator rules", func() { + ginkgo.It("MUST fail validation for create of a custom resource that does not satisfy the x-kubernetes-validations rules", func() { ginkgo.By("Creating a custom resource definition with validation rules") crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false) crd, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) @@ -144,7 +156,7 @@ var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alp } }) - ginkgo.It("MUST fail create of a custom resource definition that contains a x-kubernetes-validator rule that refers to a property that do not exist", func() { + ginkgo.It("MUST fail create of a custom resource definition that contains a x-kubernetes-validations rule that refers to a property that do not exist", func() { ginkgo.By("Defining a custom resource definition with a validation rule that refers to a property that do not exist") var schemaWithInvalidValidationRule = unmarshallSchema([]byte(`{ "type":"object", @@ -223,4 +235,102 @@ var _ = SIGDescribe("CustomResourceValidationRules [Privileged:ClusterAdmin][Alp framework.Failf("expected error message to contain %q, got %q", expectedErrMsg, err.Error()) } }) + + ginkgo.It("MUST fail create of a custom resource that exceeds the runtime cost limit for x-kubernetes-validations rule execution", func() { + ginkgo.By("Defining a custom resource definition including an expensive rule on a large amount of data") + crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithValidationExpression, false) + _, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) + framework.ExpectNoError(err, "creating CustomResourceDefinition including an expensive rule on a large amount of data") + defer func() { + err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient) + framework.ExpectNoError(err, "deleting CustomResourceDefinition") + }() + ginkgo.By("Attempting to create a custom resource that will exceed the runtime cost limit") + crClient, gvr := customResourceClient(crd) + name1 := names.SimpleNameGenerator.GenerateName("cr-1") + _, err = crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": gvr.Group + "/" + gvr.Version, + "kind": crd.Spec.Names.Kind, + "metadata": map[string]interface{}{ + "name": name1, + "namespace": f.Namespace.Name, + }, + "spec": map[string]interface{}{ + "largeArray": genLargeArray(725, 20), + }, + }}, metav1.CreateOptions{}) + framework.ExpectError(err, "custom resource creation should be prohibited by runtime cost limit") + expectedErrMsg := "call cost exceeds limit" + if !strings.Contains(err.Error(), expectedErrMsg) { + framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error()) + } + }) + + ginkgo.It("MUST fail update of a custom resource that does not satisfy a x-kubernetes-validations transition rule", func() { + ginkgo.By("Defining a custom resource definition with a x-kubernetes-validations transition rule") + var schemaWithTransitionRule = unmarshallSchema([]byte(`{ + "type":"object", + "properties":{ + "spec":{ + "type":"object", + "properties":{ + "num":{ + "type":"integer", + "x-kubernetes-validations":[ + { "rule":"self > oldSelf" } + ] + } + } + } + } + }`)) + crd := fixtures.NewRandomNameV1CustomResourceDefinitionWithSchema(v1.NamespaceScoped, schemaWithTransitionRule, false) + _, err := fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient) + framework.ExpectNoError(err, "creating CustomResourceDefinition including an x-kubernetes-validations transition rule") + defer func() { + err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient) + framework.ExpectNoError(err, "deleting CustomResourceDefinition") + }() + ginkgo.By("Attempting to create a custom resource") + crClient, gvr := customResourceClient(crd) + name1 := names.SimpleNameGenerator.GenerateName("cr-1") + unstruct, err := crClient.Namespace(f.Namespace.Name).Create(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": gvr.Group + "/" + gvr.Version, + "kind": crd.Spec.Names.Kind, + "metadata": map[string]interface{}{ + "name": name1, + "namespace": f.Namespace.Name, + }, + "spec": map[string]interface{}{ + "num": int64(10), + }, + }}, metav1.CreateOptions{}) + framework.ExpectNoError(err, "transition rules do not apply to create operations") + ginkgo.By("Updating a custom resource with a value that does not satisfy an x-kubernetes-validations transition rule") + _, err = crClient.Namespace(f.Namespace.Name).Update(context.TODO(), &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": gvr.Group + "/" + gvr.Version, + "kind": crd.Spec.Names.Kind, + "metadata": map[string]interface{}{ + "name": name1, + "namespace": f.Namespace.Name, + "resourceVersion": unstruct.GetResourceVersion(), + }, + "spec": map[string]interface{}{ + "num": int64(9), + }, + }}, metav1.UpdateOptions{}) + framework.ExpectError(err, "custom resource update should be prohibited by transition rule") + expectedErrMsg := "failed rule" + if !strings.Contains(err.Error(), expectedErrMsg) { + framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error()) + } + }) }) + +func genLargeArray(n, x int64) []int64 { + arr := make([]int64, n) + for i := int64(0); i < n; i++ { + arr[i] = x + } + return arr +}