k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/generic/webhook_test.go (about) 1 /* 2 Copyright 2019 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 generic 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "strings" 24 "testing" 25 26 v1 "k8s.io/api/admissionregistration/v1" 27 corev1 "k8s.io/api/core/v1" 28 k8serrors "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apiserver/pkg/admission" 34 "k8s.io/apiserver/pkg/admission/plugin/cel" 35 "k8s.io/apiserver/pkg/admission/plugin/webhook" 36 "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" 37 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" 38 "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" 39 "k8s.io/apiserver/pkg/authorization/authorizer" 40 ) 41 42 func gvr(group, version, resource string) schema.GroupVersionResource { 43 return schema.GroupVersionResource{Group: group, Version: version, Resource: resource} 44 } 45 46 func gvk(group, version, kind string) schema.GroupVersionKind { 47 return schema.GroupVersionKind{Group: group, Version: version, Kind: kind} 48 } 49 50 var _ matchconditions.Matcher = &fakeMatcher{} 51 52 type fakeMatcher struct { 53 throwError error 54 matchResult bool 55 } 56 57 func (f *fakeMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) matchconditions.MatchResult { 58 if f.throwError != nil { 59 return matchconditions.MatchResult{ 60 Matches: true, 61 FailedConditionName: "", 62 Error: f.throwError, 63 } 64 } 65 return matchconditions.MatchResult{ 66 Matches: f.matchResult, 67 FailedConditionName: "", 68 } 69 } 70 71 var _ webhook.WebhookAccessor = &fakeWebhookAccessor{} 72 73 type fakeWebhookAccessor struct { 74 webhook.WebhookAccessor 75 throwError error 76 matchResult bool 77 } 78 79 func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher { 80 return &fakeMatcher{ 81 throwError: f.throwError, 82 matchResult: f.matchResult, 83 } 84 } 85 86 var _ VersionedAttributeAccessor = &fakeVersionedAttributeAccessor{} 87 88 type fakeVersionedAttributeAccessor struct{} 89 90 func (v *fakeVersionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) { 91 return nil, nil 92 } 93 94 func TestShouldCallHook(t *testing.T) { 95 a := &Webhook{ 96 namespaceMatcher: &namespace.Matcher{}, 97 objectMatcher: &object.Matcher{}, 98 } 99 100 allScopes := v1.AllScopes 101 exactMatch := v1.Exact 102 equivalentMatch := v1.Equivalent 103 104 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 105 if resource.Resource == "deployments" { 106 // co-locate deployments in all API groups 107 return "/deployments" 108 } 109 return "" 110 }) 111 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 112 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 113 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 114 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 115 116 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 117 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 118 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 119 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 120 121 // register invalid kinds to trigger an error 122 mapper.RegisterKindFor(gvr("example.com", "v1", "widgets"), "", gvk("", "", "")) 123 mapper.RegisterKindFor(gvr("example.com", "v2", "widgets"), "", gvk("", "", "")) 124 125 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 126 127 testcases := []struct { 128 name string 129 130 webhook *v1.ValidatingWebhook 131 attrs admission.Attributes 132 133 expectCall bool 134 expectErr string 135 expectCallResource schema.GroupVersionResource 136 expectCallSubresource string 137 expectCallKind schema.GroupVersionKind 138 matchError error 139 matchResult bool 140 }{ 141 { 142 name: "no rules (just write)", 143 webhook: &v1.ValidatingWebhook{NamespaceSelector: &metav1.LabelSelector{}, Rules: []v1.RuleWithOperations{}}, 144 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 145 expectCall: false, 146 matchResult: true, 147 }, 148 { 149 name: "invalid kind lookup", 150 webhook: &v1.ValidatingWebhook{ 151 NamespaceSelector: &metav1.LabelSelector{}, 152 ObjectSelector: &metav1.LabelSelector{}, 153 MatchPolicy: &equivalentMatch, 154 Rules: []v1.RuleWithOperations{{ 155 Operations: []v1.OperationType{"*"}, 156 Rule: v1.Rule{APIGroups: []string{"example.com"}, APIVersions: []string{"v1"}, Resources: []string{"widgets"}, Scope: &allScopes}, 157 }}}, 158 attrs: admission.NewAttributesRecord(nil, nil, gvk("example.com", "v2", "Widget"), "ns", "name", gvr("example.com", "v2", "widgets"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 159 expectCall: false, 160 expectErr: "unknown kind", 161 matchResult: true, 162 }, 163 { 164 name: "wildcard rule, match as requested", 165 webhook: &v1.ValidatingWebhook{ 166 NamespaceSelector: &metav1.LabelSelector{}, 167 ObjectSelector: &metav1.LabelSelector{}, 168 Rules: []v1.RuleWithOperations{{ 169 Operations: []v1.OperationType{"*"}, 170 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 171 }}}, 172 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 173 expectCall: true, 174 expectCallKind: gvk("apps", "v1", "Deployment"), 175 expectCallResource: gvr("apps", "v1", "deployments"), 176 expectCallSubresource: "", 177 matchResult: true, 178 }, 179 { 180 name: "specific rules, prefer exact match", 181 webhook: &v1.ValidatingWebhook{ 182 NamespaceSelector: &metav1.LabelSelector{}, 183 ObjectSelector: &metav1.LabelSelector{}, 184 Rules: []v1.RuleWithOperations{{ 185 Operations: []v1.OperationType{"*"}, 186 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 187 }, { 188 Operations: []v1.OperationType{"*"}, 189 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 190 }, { 191 Operations: []v1.OperationType{"*"}, 192 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 193 }}}, 194 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 195 expectCall: true, 196 expectCallKind: gvk("apps", "v1", "Deployment"), 197 expectCallResource: gvr("apps", "v1", "deployments"), 198 expectCallSubresource: "", 199 matchResult: true, 200 }, 201 { 202 name: "specific rules, match miss", 203 webhook: &v1.ValidatingWebhook{ 204 NamespaceSelector: &metav1.LabelSelector{}, 205 ObjectSelector: &metav1.LabelSelector{}, 206 Rules: []v1.RuleWithOperations{{ 207 Operations: []v1.OperationType{"*"}, 208 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 209 }, { 210 Operations: []v1.OperationType{"*"}, 211 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 212 }}}, 213 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 214 expectCall: false, 215 matchResult: true, 216 }, 217 { 218 name: "specific rules, exact match miss", 219 webhook: &v1.ValidatingWebhook{ 220 MatchPolicy: &exactMatch, 221 NamespaceSelector: &metav1.LabelSelector{}, 222 ObjectSelector: &metav1.LabelSelector{}, 223 Rules: []v1.RuleWithOperations{{ 224 Operations: []v1.OperationType{"*"}, 225 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 226 }, { 227 Operations: []v1.OperationType{"*"}, 228 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 229 }}}, 230 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 231 expectCall: false, 232 matchResult: true, 233 }, 234 { 235 name: "specific rules, equivalent match, prefer extensions", 236 webhook: &v1.ValidatingWebhook{ 237 MatchPolicy: &equivalentMatch, 238 NamespaceSelector: &metav1.LabelSelector{}, 239 ObjectSelector: &metav1.LabelSelector{}, 240 Rules: []v1.RuleWithOperations{{ 241 Operations: []v1.OperationType{"*"}, 242 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 243 }, { 244 Operations: []v1.OperationType{"*"}, 245 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 246 }}}, 247 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 248 expectCall: true, 249 expectCallKind: gvk("extensions", "v1beta1", "Deployment"), 250 expectCallResource: gvr("extensions", "v1beta1", "deployments"), 251 expectCallSubresource: "", 252 matchResult: true, 253 }, 254 { 255 name: "specific rules, equivalent match, prefer apps", 256 webhook: &v1.ValidatingWebhook{ 257 MatchPolicy: &equivalentMatch, 258 NamespaceSelector: &metav1.LabelSelector{}, 259 ObjectSelector: &metav1.LabelSelector{}, 260 Rules: []v1.RuleWithOperations{{ 261 Operations: []v1.OperationType{"*"}, 262 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 263 }, { 264 Operations: []v1.OperationType{"*"}, 265 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, 266 }}}, 267 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 268 expectCall: true, 269 expectCallKind: gvk("apps", "v1beta1", "Deployment"), 270 expectCallResource: gvr("apps", "v1beta1", "deployments"), 271 expectCallSubresource: "", 272 matchResult: true, 273 }, 274 275 { 276 name: "specific rules, subresource prefer exact match", 277 webhook: &v1.ValidatingWebhook{ 278 NamespaceSelector: &metav1.LabelSelector{}, 279 ObjectSelector: &metav1.LabelSelector{}, 280 Rules: []v1.RuleWithOperations{{ 281 Operations: []v1.OperationType{"*"}, 282 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 283 }, { 284 Operations: []v1.OperationType{"*"}, 285 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 286 }, { 287 Operations: []v1.OperationType{"*"}, 288 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 289 }}}, 290 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 291 expectCall: true, 292 expectCallKind: gvk("autoscaling", "v1", "Scale"), 293 expectCallResource: gvr("apps", "v1", "deployments"), 294 expectCallSubresource: "scale", 295 matchResult: true, 296 }, 297 { 298 name: "specific rules, subresource match miss", 299 webhook: &v1.ValidatingWebhook{ 300 NamespaceSelector: &metav1.LabelSelector{}, 301 ObjectSelector: &metav1.LabelSelector{}, 302 Rules: []v1.RuleWithOperations{{ 303 Operations: []v1.OperationType{"*"}, 304 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 305 }, { 306 Operations: []v1.OperationType{"*"}, 307 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 308 }}}, 309 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 310 expectCall: false, 311 matchResult: true, 312 }, 313 { 314 name: "specific rules, subresource exact match miss", 315 webhook: &v1.ValidatingWebhook{ 316 MatchPolicy: &exactMatch, 317 NamespaceSelector: &metav1.LabelSelector{}, 318 ObjectSelector: &metav1.LabelSelector{}, 319 Rules: []v1.RuleWithOperations{{ 320 Operations: []v1.OperationType{"*"}, 321 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 322 }, { 323 Operations: []v1.OperationType{"*"}, 324 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 325 }}}, 326 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 327 expectCall: false, 328 matchResult: true, 329 }, 330 { 331 name: "specific rules, subresource equivalent match, prefer extensions", 332 webhook: &v1.ValidatingWebhook{ 333 MatchPolicy: &equivalentMatch, 334 NamespaceSelector: &metav1.LabelSelector{}, 335 ObjectSelector: &metav1.LabelSelector{}, 336 Rules: []v1.RuleWithOperations{{ 337 Operations: []v1.OperationType{"*"}, 338 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 339 }, { 340 Operations: []v1.OperationType{"*"}, 341 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 342 }}}, 343 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 344 expectCall: true, 345 expectCallKind: gvk("extensions", "v1beta1", "Scale"), 346 expectCallResource: gvr("extensions", "v1beta1", "deployments"), 347 expectCallSubresource: "scale", 348 matchResult: true, 349 }, 350 { 351 name: "specific rules, subresource equivalent match, prefer apps", 352 webhook: &v1.ValidatingWebhook{ 353 MatchPolicy: &equivalentMatch, 354 NamespaceSelector: &metav1.LabelSelector{}, 355 ObjectSelector: &metav1.LabelSelector{}, 356 Rules: []v1.RuleWithOperations{{ 357 Operations: []v1.OperationType{"*"}, 358 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 359 }, { 360 Operations: []v1.OperationType{"*"}, 361 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 362 }}}, 363 attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil), 364 expectCall: true, 365 expectCallKind: gvk("apps", "v1beta1", "Scale"), 366 expectCallResource: gvr("apps", "v1beta1", "deployments"), 367 expectCallSubresource: "scale", 368 matchResult: true, 369 }, 370 { 371 name: "wildcard rule, match conditions also match", 372 webhook: &v1.ValidatingWebhook{ 373 NamespaceSelector: &metav1.LabelSelector{}, 374 ObjectSelector: &metav1.LabelSelector{}, 375 Rules: []v1.RuleWithOperations{{ 376 Operations: []v1.OperationType{"*"}, 377 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 378 }}, 379 MatchConditions: []v1.MatchCondition{ 380 { 381 Name: "test1", 382 Expression: "test expression", 383 }, 384 }, 385 }, 386 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 387 expectCall: true, 388 expectCallKind: gvk("apps", "v1", "Deployment"), 389 expectCallResource: gvr("apps", "v1", "deployments"), 390 expectCallSubresource: "", 391 matchResult: true, 392 }, 393 { 394 name: "wildcard rule, match conditions do not match", 395 webhook: &v1.ValidatingWebhook{ 396 NamespaceSelector: &metav1.LabelSelector{}, 397 ObjectSelector: &metav1.LabelSelector{}, 398 Rules: []v1.RuleWithOperations{{ 399 Operations: []v1.OperationType{"*"}, 400 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 401 }}, 402 MatchConditions: []v1.MatchCondition{ 403 { 404 Name: "test1", 405 Expression: "test expression", 406 }, 407 }, 408 }, 409 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 410 expectCall: false, 411 matchResult: false, 412 }, 413 { 414 name: "wildcard rule, match conditions error", 415 webhook: &v1.ValidatingWebhook{ 416 NamespaceSelector: &metav1.LabelSelector{}, 417 ObjectSelector: &metav1.LabelSelector{}, 418 Rules: []v1.RuleWithOperations{{ 419 Operations: []v1.OperationType{"*"}, 420 Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, 421 }}, 422 MatchConditions: []v1.MatchCondition{ 423 { 424 Name: "test1", 425 Expression: "test expression", 426 }, 427 }, 428 }, 429 attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil), 430 expectCall: false, 431 expectErr: "deployments.apps \"name\" is forbidden: test error", 432 expectCallKind: gvk("apps", "v1", "Deployment"), 433 expectCallResource: gvr("apps", "v1", "deployments"), 434 expectCallSubresource: "", 435 matchError: errors.New("test error"), 436 }, 437 } 438 439 for i, testcase := range testcases { 440 t.Run(testcase.name, func(t *testing.T) { 441 fakeWebhook := &fakeWebhookAccessor{ 442 WebhookAccessor: webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), fmt.Sprintf("webhook-cfg-%d", i), testcase.webhook), 443 matchResult: testcase.matchResult, 444 throwError: testcase.matchError, 445 } 446 447 invocation, err := a.ShouldCallHook(context.TODO(), fakeWebhook, testcase.attrs, interfaces, &fakeVersionedAttributeAccessor{}) 448 if err != nil { 449 if len(testcase.expectErr) == 0 { 450 t.Fatal(err) 451 } 452 if !strings.Contains(err.Error(), testcase.expectErr) { 453 t.Fatalf("expected error containing %q, got %s", testcase.expectErr, err.Error()) 454 } 455 return 456 } else if len(testcase.expectErr) > 0 { 457 t.Fatalf("expected error %q, got no error and %#v", testcase.expectErr, invocation) 458 } 459 460 if invocation == nil { 461 if testcase.expectCall { 462 t.Fatal("expected invocation, got nil") 463 } 464 return 465 } 466 467 if !testcase.expectCall { 468 t.Fatal("unexpected invocation") 469 } 470 471 if invocation.Kind != testcase.expectCallKind { 472 t.Fatalf("expected %#v, got %#v", testcase.expectCallKind, invocation.Kind) 473 } 474 if invocation.Resource != testcase.expectCallResource { 475 t.Fatalf("expected %#v, got %#v", testcase.expectCallResource, invocation.Resource) 476 } 477 if invocation.Subresource != testcase.expectCallSubresource { 478 t.Fatalf("expected %#v, got %#v", testcase.expectCallSubresource, invocation.Subresource) 479 } 480 }) 481 } 482 } 483 484 type fakeNamespaceLister struct { 485 namespaces map[string]*corev1.Namespace 486 } 487 488 func (f fakeNamespaceLister) List(selector labels.Selector) (ret []*corev1.Namespace, err error) { 489 return nil, nil 490 } 491 func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) { 492 ns, ok := f.namespaces[name] 493 if ok { 494 return ns, nil 495 } 496 return nil, k8serrors.NewNotFound(corev1.Resource("namespaces"), name) 497 } 498 499 func BenchmarkShouldCallHookWithComplexSelector(b *testing.B) { 500 allScopes := v1.AllScopes 501 equivalentMatch := v1.Equivalent 502 503 namespace1Labels := map[string]string{"ns": "ns1"} 504 namespace1 := corev1.Namespace{ 505 ObjectMeta: metav1.ObjectMeta{ 506 Name: "ns1", 507 Labels: namespace1Labels, 508 }, 509 } 510 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 511 512 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 513 if resource.Resource == "deployments" { 514 // co-locate deployments in all API groups 515 return "/deployments" 516 } 517 return "" 518 }) 519 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 520 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 521 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 522 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 523 524 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 525 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 526 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 527 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 528 529 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 530 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 531 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 532 533 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 534 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 535 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 536 537 nsSelector := make(map[string]string) 538 for i := 0; i < 100; i++ { 539 nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) 540 } 541 542 wb := &v1.ValidatingWebhook{ 543 MatchPolicy: &equivalentMatch, 544 NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, 545 ObjectSelector: &metav1.LabelSelector{}, 546 Rules: []v1.RuleWithOperations{ 547 { 548 Operations: []v1.OperationType{"*"}, 549 Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 550 }, 551 { 552 Operations: []v1.OperationType{"*"}, 553 Rule: v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, 554 }, 555 }, 556 } 557 558 wbAccessor := &fakeWebhookAccessor{ 559 WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb), 560 matchResult: true, 561 } 562 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 563 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 564 a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 565 566 for i := 0; i < b.N; i++ { 567 a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, nil) 568 } 569 } 570 571 func BenchmarkShouldCallHookWithComplexRule(b *testing.B) { 572 allScopes := v1.AllScopes 573 equivalentMatch := v1.Equivalent 574 575 namespace1Labels := map[string]string{"ns": "ns1"} 576 namespace1 := corev1.Namespace{ 577 ObjectMeta: metav1.ObjectMeta{ 578 Name: "ns1", 579 Labels: namespace1Labels, 580 }, 581 } 582 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 583 584 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 585 if resource.Resource == "deployments" { 586 // co-locate deployments in all API groups 587 return "/deployments" 588 } 589 return "" 590 }) 591 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 592 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 593 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 594 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 595 596 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 597 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 598 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 599 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 600 601 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 602 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 603 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 604 605 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 606 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 607 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 608 609 wb := &v1.ValidatingWebhook{ 610 MatchPolicy: &equivalentMatch, 611 NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 612 ObjectSelector: &metav1.LabelSelector{}, 613 Rules: []v1.RuleWithOperations{}, 614 } 615 616 for i := 0; i < 100; i++ { 617 rule := v1.RuleWithOperations{ 618 Operations: []v1.OperationType{"*"}, 619 Rule: v1.Rule{ 620 APIGroups: []string{fmt.Sprintf("app-%d", i)}, 621 APIVersions: []string{fmt.Sprintf("v%d", i)}, 622 Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, 623 Scope: &allScopes, 624 }, 625 } 626 wb.Rules = append(wb.Rules, rule) 627 } 628 629 wbAccessor := &fakeWebhookAccessor{ 630 WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb), 631 matchResult: true, 632 } 633 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 634 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 635 a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 636 637 for i := 0; i < b.N; i++ { 638 a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, &fakeVersionedAttributeAccessor{}) 639 } 640 } 641 642 func BenchmarkShouldCallHookWithComplexSelectorAndRule(b *testing.B) { 643 allScopes := v1.AllScopes 644 equivalentMatch := v1.Equivalent 645 646 namespace1Labels := map[string]string{"ns": "ns1"} 647 namespace1 := corev1.Namespace{ 648 ObjectMeta: metav1.ObjectMeta{ 649 Name: "ns1", 650 Labels: namespace1Labels, 651 }, 652 } 653 namespaceLister := fakeNamespaceLister{map[string]*corev1.Namespace{"ns": &namespace1}} 654 655 mapper := runtime.NewEquivalentResourceRegistryWithIdentity(func(resource schema.GroupResource) string { 656 if resource.Resource == "deployments" { 657 // co-locate deployments in all API groups 658 return "/deployments" 659 } 660 return "" 661 }) 662 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")) 663 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")) 664 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")) 665 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")) 666 667 mapper.RegisterKindFor(gvr("extensions", "v1beta1", "deployments"), "scale", gvk("extensions", "v1beta1", "Scale")) 668 mapper.RegisterKindFor(gvr("apps", "v1", "deployments"), "scale", gvk("autoscaling", "v1", "Scale")) 669 mapper.RegisterKindFor(gvr("apps", "v1beta1", "deployments"), "scale", gvk("apps", "v1beta1", "Scale")) 670 mapper.RegisterKindFor(gvr("apps", "v1alpha1", "deployments"), "scale", gvk("apps", "v1alpha1", "Scale")) 671 672 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "", gvk("apps", "v1", "StatefulSet")) 673 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "", gvk("apps", "v1beta1", "StatefulSet")) 674 mapper.RegisterKindFor(gvr("apps", "v1beta2", "statefulset"), "", gvk("apps", "v1beta2", "StatefulSet")) 675 676 mapper.RegisterKindFor(gvr("apps", "v1", "statefulset"), "scale", gvk("apps", "v1", "Scale")) 677 mapper.RegisterKindFor(gvr("apps", "v1beta1", "statefulset"), "scale", gvk("apps", "v1beta1", "Scale")) 678 mapper.RegisterKindFor(gvr("apps", "v1alpha2", "statefulset"), "scale", gvk("apps", "v1beta2", "Scale")) 679 680 nsSelector := make(map[string]string) 681 for i := 0; i < 100; i++ { 682 nsSelector[fmt.Sprintf("key-%d", i)] = fmt.Sprintf("val-%d", i) 683 } 684 685 wb := &v1.ValidatingWebhook{ 686 MatchPolicy: &equivalentMatch, 687 NamespaceSelector: &metav1.LabelSelector{MatchLabels: nsSelector}, 688 ObjectSelector: &metav1.LabelSelector{}, 689 Rules: []v1.RuleWithOperations{}, 690 } 691 692 for i := 0; i < 100; i++ { 693 rule := v1.RuleWithOperations{ 694 Operations: []v1.OperationType{"*"}, 695 Rule: v1.Rule{ 696 APIGroups: []string{fmt.Sprintf("app-%d", i)}, 697 APIVersions: []string{fmt.Sprintf("v%d", i)}, 698 Resources: []string{fmt.Sprintf("resource%d", i), fmt.Sprintf("resource%d/scale", i)}, 699 Scope: &allScopes, 700 }, 701 } 702 wb.Rules = append(wb.Rules, rule) 703 } 704 705 wbAccessor := &fakeWebhookAccessor{ 706 WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb), 707 matchResult: true, 708 } 709 attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil) 710 interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper} 711 a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}} 712 713 for i := 0; i < b.N; i++ { 714 a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, nil) 715 } 716 }