k8s.io/apiserver@v0.31.1/pkg/admission/plugin/namespace/lifecycle/admission_test.go (about) 1 /* 2 Copyright 2015 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 lifecycle 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/util/sets" 33 "k8s.io/apimachinery/pkg/util/wait" 34 "k8s.io/apiserver/pkg/admission" 35 kubeadmission "k8s.io/apiserver/pkg/admission/initializer" 36 informers "k8s.io/client-go/informers" 37 clientset "k8s.io/client-go/kubernetes" 38 "k8s.io/client-go/kubernetes/fake" 39 core "k8s.io/client-go/testing" 40 "k8s.io/utils/clock" 41 testingclock "k8s.io/utils/clock/testing" 42 ) 43 44 // newHandlerForTest returns a configured handler for testing. 45 func newHandlerForTest(c clientset.Interface) (*Lifecycle, informers.SharedInformerFactory, error) { 46 return newHandlerForTestWithClock(c, clock.RealClock{}) 47 } 48 49 // newHandlerForTestWithClock returns a configured handler for testing. 50 func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (*Lifecycle, informers.SharedInformerFactory, error) { 51 f := informers.NewSharedInformerFactory(c, 5*time.Minute) 52 handler, err := newLifecycleWithClock(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem), cacheClock) 53 if err != nil { 54 return nil, f, err 55 } 56 pluginInitializer := kubeadmission.New(c, nil, f, nil, nil, nil, nil) 57 pluginInitializer.Initialize(handler) 58 err = admission.ValidateInitialization(handler) 59 return handler, f, err 60 } 61 62 // newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces with the specified phase. 63 func newMockClientForTest(namespaces map[string]v1.NamespacePhase) *fake.Clientset { 64 mockClient := &fake.Clientset{} 65 mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 66 namespaceList := &v1.NamespaceList{ 67 ListMeta: metav1.ListMeta{ 68 ResourceVersion: fmt.Sprintf("%d", len(namespaces)), 69 }, 70 } 71 index := 0 72 for name, phase := range namespaces { 73 namespaceList.Items = append(namespaceList.Items, v1.Namespace{ 74 ObjectMeta: metav1.ObjectMeta{ 75 Name: name, 76 ResourceVersion: fmt.Sprintf("%d", index), 77 }, 78 Status: v1.NamespaceStatus{ 79 Phase: phase, 80 }, 81 }) 82 index++ 83 } 84 return true, namespaceList, nil 85 }) 86 return mockClient 87 } 88 89 // newPod returns a new pod for the specified namespace 90 func newPod(namespace string) v1.Pod { 91 return v1.Pod{ 92 ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace}, 93 Spec: v1.PodSpec{ 94 Volumes: []v1.Volume{{Name: "vol"}}, 95 Containers: []v1.Container{{Name: "ctr", Image: "image"}}, 96 }, 97 } 98 } 99 100 func TestAccessReviewCheckOnMissingNamespace(t *testing.T) { 101 namespace := "test" 102 mockClient := newMockClientForTest(map[string]v1.NamespacePhase{}) 103 mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 104 return true, nil, fmt.Errorf("nope, out of luck") 105 }) 106 handler, informerFactory, err := newHandlerForTest(mockClient) 107 if err != nil { 108 t.Errorf("unexpected error initializing handler: %v", err) 109 } 110 111 stopCh := make(chan struct{}) 112 defer close(stopCh) 113 114 informerFactory.Start(stopCh) 115 116 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{Group: "authorization.k8s.io", Version: "v1", Kind: "LocalSubjectAccesReview"}, namespace, "", schema.GroupVersionResource{Group: "authorization.k8s.io", Version: "v1", Resource: "localsubjectaccessreviews"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 117 if err != nil { 118 t.Error(err) 119 } 120 } 121 122 // TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist. 123 func TestAdmissionNamespaceDoesNotExist(t *testing.T) { 124 namespace := "test" 125 mockClient := newMockClientForTest(map[string]v1.NamespacePhase{}) 126 mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 127 return true, nil, fmt.Errorf("nope, out of luck") 128 }) 129 handler, informerFactory, err := newHandlerForTest(mockClient) 130 if err != nil { 131 t.Errorf("unexpected error initializing handler: %v", err) 132 } 133 informerFactory.Start(wait.NeverStop) 134 135 pod := newPod(namespace) 136 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 137 if err == nil { 138 actions := "" 139 for _, action := range mockClient.Actions() { 140 actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", " 141 } 142 t.Errorf("expected error returned from admission handler: %v", actions) 143 } 144 145 // verify create operations in the namespace cause an error 146 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 147 if err == nil { 148 t.Errorf("Expected error rejecting creates in a namespace when it is missing") 149 } 150 151 // verify update operations in the namespace cause an error 152 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil) 153 if err == nil { 154 t.Errorf("Expected error rejecting updates in a namespace when it is missing") 155 } 156 157 // verify delete operations in the namespace can proceed 158 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil), nil) 159 if err != nil { 160 t.Errorf("Unexpected error returned from admission handler: %v", err) 161 } 162 } 163 164 // TestAdmissionNamespaceActive verifies a resource is admitted when the namespace is active. 165 func TestAdmissionNamespaceActive(t *testing.T) { 166 namespace := "test" 167 mockClient := newMockClientForTest(map[string]v1.NamespacePhase{ 168 namespace: v1.NamespaceActive, 169 }) 170 171 handler, informerFactory, err := newHandlerForTest(mockClient) 172 if err != nil { 173 t.Errorf("unexpected error initializing handler: %v", err) 174 } 175 informerFactory.Start(wait.NeverStop) 176 177 pod := newPod(namespace) 178 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 179 if err != nil { 180 t.Errorf("unexpected error returned from admission handler") 181 } 182 } 183 184 // TestAdmissionNamespaceTerminating verifies a resource is not created when the namespace is active. 185 func TestAdmissionNamespaceTerminating(t *testing.T) { 186 namespace := "test" 187 mockClient := newMockClientForTest(map[string]v1.NamespacePhase{ 188 namespace: v1.NamespaceTerminating, 189 }) 190 191 handler, informerFactory, err := newHandlerForTest(mockClient) 192 if err != nil { 193 t.Errorf("unexpected error initializing handler: %v", err) 194 } 195 informerFactory.Start(wait.NeverStop) 196 197 pod := newPod(namespace) 198 // verify create operations in the namespace cause an error 199 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 200 if err == nil { 201 t.Errorf("Expected error rejecting creates in a namespace when it is terminating") 202 } 203 expectedCause := metav1.StatusCause{ 204 Type: v1.NamespaceTerminatingCause, 205 Message: fmt.Sprintf("namespace %s is being terminated", namespace), 206 Field: "metadata.namespace", 207 } 208 if cause, ok := errors.StatusCause(err, v1.NamespaceTerminatingCause); !ok || !reflect.DeepEqual(expectedCause, cause) { 209 t.Errorf("Expected status cause indicating the namespace is terminating: %t %s", ok, cmp.Diff(expectedCause, cause)) 210 } 211 212 // verify update operations in the namespace can proceed 213 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil), nil) 214 if err != nil { 215 t.Errorf("Unexpected error returned from admission handler: %v", err) 216 } 217 218 // verify delete operations in the namespace can proceed 219 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil), nil) 220 if err != nil { 221 t.Errorf("Unexpected error returned from admission handler: %v", err) 222 } 223 224 // verify delete of namespace default can never proceed 225 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), "", metav1.NamespaceDefault, v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil), nil) 226 if err == nil { 227 t.Errorf("Expected an error that this namespace can never be deleted") 228 } 229 230 // verify delete of namespace other than default can proceed 231 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), "", "other", v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil), nil) 232 if err != nil { 233 t.Errorf("Did not expect an error %v", err) 234 } 235 } 236 237 // TestAdmissionNamespaceForceLiveLookup verifies live lookups are done after deleting a namespace 238 func TestAdmissionNamespaceForceLiveLookup(t *testing.T) { 239 namespace := "test" 240 getCalls := int64(0) 241 phases := map[string]v1.NamespacePhase{namespace: v1.NamespaceActive} 242 mockClient := newMockClientForTest(phases) 243 mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) { 244 getCalls++ 245 return true, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}, Status: v1.NamespaceStatus{Phase: phases[namespace]}}, nil 246 }) 247 248 fakeClock := testingclock.NewFakeClock(time.Now()) 249 250 handler, informerFactory, err := newHandlerForTestWithClock(mockClient, fakeClock) 251 if err != nil { 252 t.Errorf("unexpected error initializing handler: %v", err) 253 } 254 informerFactory.Start(wait.NeverStop) 255 256 pod := newPod(namespace) 257 // verify create operations in the namespace is allowed 258 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 259 if err != nil { 260 t.Errorf("Unexpected error rejecting creates in an active namespace") 261 } 262 if getCalls != 0 { 263 t.Errorf("Expected no live lookups of the namespace, got %d", getCalls) 264 } 265 getCalls = 0 266 267 // verify delete of namespace can proceed 268 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace").GroupKind().WithVersion("version"), namespace, namespace, v1.Resource("namespaces").WithVersion("version"), "", admission.Delete, &metav1.DeleteOptions{}, false, nil), nil) 269 if err != nil { 270 t.Errorf("Expected namespace deletion to be allowed") 271 } 272 if getCalls != 0 { 273 t.Errorf("Expected no live lookups of the namespace, got %d", getCalls) 274 } 275 getCalls = 0 276 277 // simulate the phase changing 278 phases[namespace] = v1.NamespaceTerminating 279 280 // verify create operations in the namespace cause an error 281 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 282 if err == nil { 283 t.Errorf("Expected error rejecting creates in a namespace right after deleting it") 284 } 285 if getCalls != 1 { 286 t.Errorf("Expected a live lookup of the namespace at t=0, got %d", getCalls) 287 } 288 getCalls = 0 289 290 // Ensure the live lookup is still forced up to forceLiveLookupTTL 291 fakeClock.Step(forceLiveLookupTTL) 292 293 // verify create operations in the namespace cause an error 294 err = handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 295 if err == nil { 296 t.Errorf("Expected error rejecting creates in a namespace right after deleting it") 297 } 298 if getCalls != 1 { 299 t.Errorf("Expected a live lookup of the namespace at t=forceLiveLookupTTL, got %d", getCalls) 300 } 301 getCalls = 0 302 303 // Ensure the live lookup expires 304 fakeClock.Step(time.Millisecond) 305 306 // verify create operations in the namespace don't force a live lookup after the timeout 307 handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, v1.SchemeGroupVersion.WithKind("Pod").GroupKind().WithVersion("version"), pod.Namespace, pod.Name, v1.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil) 308 if getCalls != 0 { 309 t.Errorf("Expected no live lookup of the namespace at t=forceLiveLookupTTL+1ms, got %d", getCalls) 310 } 311 getCalls = 0 312 }