...

Source file src/k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus/controller_test.go

Documentation: k8s.io/kubernetes/pkg/controller/validatingadmissionpolicystatus

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validatingadmissionpolicystatus
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    26  	"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	validatingadmissionpolicy "k8s.io/apiserver/pkg/admission/plugin/policy/validating"
    30  	"k8s.io/apiserver/pkg/cel/openapi/resolver"
    31  	"k8s.io/client-go/informers"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	"k8s.io/client-go/kubernetes/scheme"
    34  	"k8s.io/kubernetes/pkg/generated/openapi"
    35  )
    36  
    37  func TestTypeChecking(t *testing.T) {
    38  	for _, tc := range []struct {
    39  		name           string
    40  		policy         *admissionregistrationv1.ValidatingAdmissionPolicy
    41  		assertFieldRef func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) // warning.fieldRef
    42  		assertWarnings func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) // warning.warning
    43  	}{
    44  		{
    45  			name: "deployment with correct expression",
    46  			policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1.Validation{
    47  				{
    48  					Expression: "object.spec.replicas > 1",
    49  				},
    50  			}, makePolicy("replicated-deployment"))),
    51  			assertFieldRef: toHaveLengthOf(0),
    52  			assertWarnings: toHaveLengthOf(0),
    53  		},
    54  		{
    55  			name: "deployment with type confusion",
    56  			policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1.Validation{
    57  				{
    58  					Expression: "object.spec.replicas < 100", // this one passes
    59  				},
    60  				{
    61  					Expression: "object.spec.replicas > '1'", // '1' should be int
    62  				},
    63  			}, makePolicy("confused-deployment"))),
    64  			assertFieldRef: toBe("spec.validations[1].expression"),
    65  			assertWarnings: toHaveSubstring(`found no matching overload for '_>_' applied to '(int, string)'`),
    66  		},
    67  		{
    68  			name: "two expressions different type checking errors",
    69  			policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1.Validation{
    70  				{
    71  					Expression: "object.spec.nonExistingFirst > 1",
    72  				},
    73  				{
    74  					Expression: "object.spec.replicas > '1'", // '1' should be int
    75  				},
    76  			}, makePolicy("confused-deployment"))),
    77  			assertFieldRef: toBe("spec.validations[0].expression", "spec.validations[1].expression"),
    78  			assertWarnings: toHaveSubstring(
    79  				"undefined field 'nonExistingFirst'",
    80  				`found no matching overload for '_>_' applied to '(int, string)'`,
    81  			),
    82  		},
    83  		{
    84  			name: "one expression, two warnings",
    85  			policy: withGVRMatch([]string{"apps"}, []string{"v1"}, []string{"deployments"}, withValidations([]admissionregistrationv1.Validation{
    86  				{
    87  					Expression: "object.spec.replicas < 100", // this one passes
    88  				},
    89  				{
    90  					Expression: "object.spec.replicas > '1' && object.spec.nonExisting == 1",
    91  				},
    92  			}, makePolicy("confused-deployment"))),
    93  			assertFieldRef: toBe("spec.validations[1].expression"),
    94  			assertWarnings: toHaveMultipleSubstrings([]string{"undefined field 'nonExisting'", `found no matching overload for '_>_' applied to '(int, string)'`}),
    95  		},
    96  	} {
    97  		t.Run(tc.name, func(t *testing.T) {
    98  			ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    99  			defer cancel()
   100  			policy := tc.policy.DeepCopy()
   101  			policy.ObjectMeta.Generation = 1 // fake storage does not do this automatically
   102  			client := fake.NewSimpleClientset(policy)
   103  			informerFactory := informers.NewSharedInformerFactory(client, 0)
   104  			typeChecker := &validatingadmissionpolicy.TypeChecker{
   105  				SchemaResolver: resolver.NewDefinitionsSchemaResolver(openapi.GetOpenAPIDefinitions, scheme.Scheme),
   106  				RestMapper:     testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme),
   107  			}
   108  			controller, err := NewController(
   109  				informerFactory.Admissionregistration().V1().ValidatingAdmissionPolicies(),
   110  				client.AdmissionregistrationV1().ValidatingAdmissionPolicies(),
   111  				typeChecker,
   112  			)
   113  			if err != nil {
   114  				t.Fatalf("cannot create controller: %v", err)
   115  			}
   116  			go informerFactory.Start(ctx.Done())
   117  			go controller.Run(ctx, 1)
   118  			err = wait.PollUntilContextCancel(ctx, time.Second, false, func(ctx context.Context) (done bool, err error) {
   119  				name := policy.Name
   120  				// wait until the typeChecking is set, which means the type checking
   121  				// is complete.
   122  				updated, err := client.AdmissionregistrationV1().ValidatingAdmissionPolicies().Get(ctx, name, metav1.GetOptions{})
   123  				if err != nil {
   124  					return false, err
   125  				}
   126  				if updated.Status.TypeChecking != nil {
   127  					policy = updated
   128  					return true, nil
   129  				}
   130  				return false, nil
   131  			})
   132  			if err != nil {
   133  				t.Fatal(err)
   134  			}
   135  			tc.assertFieldRef(policy.Status.TypeChecking.ExpressionWarnings, t)
   136  			tc.assertWarnings(policy.Status.TypeChecking.ExpressionWarnings, t)
   137  			if err != nil {
   138  				t.Fatalf("failed to initialize controller: %v", err)
   139  			}
   140  		})
   141  	}
   142  
   143  }
   144  
   145  func toBe(expected ...string) func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   146  	return func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   147  		if len(expected) != len(warnings) {
   148  			t.Fatalf("mismatched length, expect %d, got %d", len(expected), len(warnings))
   149  		}
   150  		for i := range expected {
   151  			if expected[i] != warnings[i].FieldRef {
   152  				t.Errorf("expected %q but got %q", expected[i], warnings[i].FieldRef)
   153  			}
   154  		}
   155  	}
   156  }
   157  
   158  func toHaveSubstring(substrings ...string) func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   159  	return func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   160  		if len(substrings) != len(warnings) {
   161  			t.Fatalf("mismatched length, expect %d, got %d", len(substrings), len(warnings))
   162  		}
   163  		for i := range substrings {
   164  			if !strings.Contains(warnings[i].Warning, substrings[i]) {
   165  				t.Errorf("missing expected substring %q in %v", substrings[i], warnings[i])
   166  			}
   167  		}
   168  	}
   169  }
   170  
   171  func toHaveMultipleSubstrings(substrings ...[]string) func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   172  	return func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   173  		if len(substrings) != len(warnings) {
   174  			t.Fatalf("mismatched length, expect %d, got %d", len(substrings), len(warnings))
   175  		}
   176  		for i, expectedSubstrings := range substrings {
   177  			for _, s := range expectedSubstrings {
   178  				if !strings.Contains(warnings[i].Warning, s) {
   179  					t.Errorf("missing expected substring %q in %v", substrings[i], warnings[i])
   180  				}
   181  			}
   182  		}
   183  	}
   184  }
   185  
   186  func toHaveLengthOf(n int) func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   187  	return func(warnings []admissionregistrationv1.ExpressionWarning, t *testing.T) {
   188  		if n != len(warnings) {
   189  			t.Fatalf("mismatched length, expect %d, got %d", n, len(warnings))
   190  		}
   191  	}
   192  }
   193  
   194  func withGVRMatch(groups []string, versions []string, resources []string, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
   195  	policy.Spec.MatchConstraints = &admissionregistrationv1.MatchResources{
   196  		ResourceRules: []admissionregistrationv1.NamedRuleWithOperations{
   197  			{
   198  				RuleWithOperations: admissionregistrationv1.RuleWithOperations{
   199  					Operations: []admissionregistrationv1.OperationType{
   200  						"*",
   201  					},
   202  					Rule: admissionregistrationv1.Rule{
   203  						APIGroups:   groups,
   204  						APIVersions: versions,
   205  						Resources:   resources,
   206  					},
   207  				},
   208  			},
   209  		},
   210  	}
   211  	return policy
   212  }
   213  
   214  func withValidations(validations []admissionregistrationv1.Validation, policy *admissionregistrationv1.ValidatingAdmissionPolicy) *admissionregistrationv1.ValidatingAdmissionPolicy {
   215  	policy.Spec.Validations = validations
   216  	return policy
   217  }
   218  
   219  func makePolicy(name string) *admissionregistrationv1.ValidatingAdmissionPolicy {
   220  	return &admissionregistrationv1.ValidatingAdmissionPolicy{
   221  		ObjectMeta: metav1.ObjectMeta{Name: name},
   222  	}
   223  }
   224  

View as plain text