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