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