k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/namespace/autoprovision/admission_test.go (about) 1 /* 2 Copyright 2014 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 autoprovision 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/apiserver/pkg/admission" 31 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" 32 admissiontesting "k8s.io/apiserver/pkg/admission/testing" 33 "k8s.io/client-go/informers" 34 clientset "k8s.io/client-go/kubernetes" 35 "k8s.io/client-go/kubernetes/fake" 36 core "k8s.io/client-go/testing" 37 api "k8s.io/kubernetes/pkg/apis/core" 38 ) 39 40 // newHandlerForTest returns the admission controller configured for testing. 41 func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) { 42 f := informers.NewSharedInformerFactory(c, 5*time.Minute) 43 handler := NewProvision() 44 pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil) 45 pluginInitializer.Initialize(handler) 46 err := admission.ValidateInitialization(handler) 47 return handler, f, err 48 } 49 50 // newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces. 51 func newMockClientForTest(namespaces []string) *fake.Clientset { 52 mockClient := &fake.Clientset{} 53 mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 54 namespaceList := &corev1.NamespaceList{ 55 ListMeta: metav1.ListMeta{ 56 ResourceVersion: fmt.Sprintf("%d", len(namespaces)), 57 }, 58 } 59 for i, ns := range namespaces { 60 namespaceList.Items = append(namespaceList.Items, corev1.Namespace{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Name: ns, 63 ResourceVersion: fmt.Sprintf("%d", i), 64 }, 65 }) 66 } 67 return true, namespaceList, nil 68 }) 69 return mockClient 70 } 71 72 // newPod returns a new pod for the specified namespace 73 func newPod(namespace string) api.Pod { 74 return api.Pod{ 75 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, 76 Spec: api.PodSpec{ 77 Volumes: []api.Volume{{Name: "vol"}}, 78 Containers: []api.Container{{Name: "ctr", Image: "image"}}, 79 }, 80 } 81 } 82 83 // hasCreateNamespaceAction returns true if it has the create namespace action 84 func hasCreateNamespaceAction(mockClient *fake.Clientset) bool { 85 for _, action := range mockClient.Actions() { 86 if action.GetVerb() == "create" && action.GetResource().Resource == "namespaces" { 87 return true 88 } 89 } 90 return false 91 } 92 93 // TestAdmission verifies a namespace is created on create requests for namespace managed resources 94 func TestAdmission(t *testing.T) { 95 namespace := "test" 96 mockClient := newMockClientForTest([]string{}) 97 handler, informerFactory, err := newHandlerForTest(mockClient) 98 if err != nil { 99 t.Errorf("unexpected error initializing handler: %v", err) 100 } 101 informerFactory.Start(wait.NeverStop) 102 103 pod := newPod(namespace) 104 err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 105 if err != nil { 106 t.Errorf("unexpected error returned from admission handler") 107 } 108 if !hasCreateNamespaceAction(mockClient) { 109 t.Errorf("expected create namespace action") 110 } 111 } 112 113 // TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists 114 func TestAdmissionNamespaceExists(t *testing.T) { 115 namespace := "test" 116 mockClient := newMockClientForTest([]string{namespace}) 117 handler, informerFactory, err := newHandlerForTest(mockClient) 118 if err != nil { 119 t.Errorf("unexpected error initializing handler: %v", err) 120 } 121 informerFactory.Start(wait.NeverStop) 122 123 pod := newPod(namespace) 124 err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 125 if err != nil { 126 t.Errorf("unexpected error returned from admission handler") 127 } 128 if hasCreateNamespaceAction(mockClient) { 129 t.Errorf("unexpected create namespace action") 130 } 131 } 132 133 // TestAdmissionDryRun verifies that no client call is made on a dry run request 134 func TestAdmissionDryRun(t *testing.T) { 135 namespace := "test" 136 mockClient := newMockClientForTest([]string{}) 137 handler, informerFactory, err := newHandlerForTest(mockClient) 138 if err != nil { 139 t.Errorf("unexpected error initializing handler: %v", err) 140 } 141 informerFactory.Start(wait.NeverStop) 142 143 pod := newPod(namespace) 144 err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, true, nil), nil) 145 if err != nil { 146 t.Errorf("unexpected error returned from admission handler") 147 } 148 if hasCreateNamespaceAction(mockClient) { 149 t.Errorf("unexpected create namespace action") 150 } 151 } 152 153 // TestIgnoreAdmission validates that a request is ignored if its not a create 154 func TestIgnoreAdmission(t *testing.T) { 155 namespace := "test" 156 mockClient := newMockClientForTest([]string{}) 157 handler, informerFactory, err := newHandlerForTest(mockClient) 158 if err != nil { 159 t.Errorf("unexpected error initializing handler: %v", err) 160 } 161 informerFactory.Start(wait.NeverStop) 162 chainHandler := admissiontesting.WithReinvocationTesting(t, admission.NewChainHandler(handler)) 163 164 pod := newPod(namespace) 165 err = admissiontesting.WithReinvocationTesting(t, chainHandler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil) 166 if err != nil { 167 t.Errorf("unexpected error returned from admission handler") 168 } 169 if hasCreateNamespaceAction(mockClient) { 170 t.Errorf("unexpected create namespace action") 171 } 172 } 173 174 func TestAdmissionWithLatentCache(t *testing.T) { 175 namespace := "test" 176 mockClient := newMockClientForTest([]string{}) 177 mockClient.AddReactor("create", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 178 return true, nil, errors.NewAlreadyExists(api.Resource("namespaces"), namespace) 179 }) 180 handler, informerFactory, err := newHandlerForTest(mockClient) 181 if err != nil { 182 t.Errorf("unexpected error initializing handler: %v", err) 183 } 184 informerFactory.Start(wait.NeverStop) 185 186 pod := newPod(namespace) 187 err = admissiontesting.WithReinvocationTesting(t, handler).Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 188 if err != nil { 189 t.Errorf("unexpected error returned from admission handler") 190 } 191 192 if !hasCreateNamespaceAction(mockClient) { 193 t.Errorf("expected create namespace action") 194 } 195 }