1
16
17 package podnodeselector
18
19 import (
20 "context"
21 "testing"
22 "time"
23
24 corev1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/labels"
27 "k8s.io/apiserver/pkg/admission"
28 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
29 "k8s.io/client-go/informers"
30 "k8s.io/client-go/kubernetes"
31 "k8s.io/client-go/kubernetes/fake"
32 api "k8s.io/kubernetes/pkg/apis/core"
33 )
34
35
36 func TestPodAdmission(t *testing.T) {
37 namespace := &corev1.Namespace{
38 ObjectMeta: metav1.ObjectMeta{
39 Name: "testNamespace",
40 Namespace: "",
41 },
42 }
43
44 mockClient := fake.NewSimpleClientset(namespace)
45 handler, informerFactory, err := newHandlerForTest(mockClient)
46 if err != nil {
47 t.Errorf("unexpected error initializing handler: %v", err)
48 }
49 stopCh := make(chan struct{})
50 defer close(stopCh)
51 informerFactory.Start(stopCh)
52
53 pod := &api.Pod{
54 ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
55 }
56
57 tests := []struct {
58 defaultNodeSelector string
59 namespaceNodeSelector string
60 whitelist string
61 podNodeSelector map[string]string
62 mergedNodeSelector labels.Set
63 ignoreTestNamespaceNodeSelector bool
64 admit bool
65 testName string
66 }{
67 {
68 defaultNodeSelector: "",
69 podNodeSelector: map[string]string{},
70 mergedNodeSelector: labels.Set{},
71 ignoreTestNamespaceNodeSelector: true,
72 admit: true,
73 testName: "No node selectors",
74 },
75 {
76 defaultNodeSelector: "infra = false",
77 podNodeSelector: map[string]string{},
78 mergedNodeSelector: labels.Set{"infra": "false"},
79 ignoreTestNamespaceNodeSelector: true,
80 admit: true,
81 testName: "Default node selector and no conflicts",
82 },
83 {
84 defaultNodeSelector: "",
85 namespaceNodeSelector: " infra = false ",
86 podNodeSelector: map[string]string{},
87 mergedNodeSelector: labels.Set{"infra": "false"},
88 admit: true,
89 testName: "TestNamespace node selector with whitespaces and no conflicts",
90 },
91 {
92 defaultNodeSelector: "infra = false",
93 namespaceNodeSelector: "infra=true",
94 podNodeSelector: map[string]string{},
95 mergedNodeSelector: labels.Set{"infra": "true"},
96 admit: true,
97 testName: "Default and namespace node selector, no conflicts",
98 },
99 {
100 defaultNodeSelector: "infra = false",
101 namespaceNodeSelector: "",
102 podNodeSelector: map[string]string{},
103 mergedNodeSelector: labels.Set{},
104 admit: true,
105 testName: "Empty namespace node selector and no conflicts",
106 },
107 {
108 defaultNodeSelector: "infra = false",
109 namespaceNodeSelector: "infra=true",
110 podNodeSelector: map[string]string{"env": "test"},
111 mergedNodeSelector: labels.Set{"infra": "true", "env": "test"},
112 admit: true,
113 testName: "TestNamespace and pod node selector, no conflicts",
114 },
115 {
116 defaultNodeSelector: "env = test",
117 namespaceNodeSelector: "infra=true",
118 podNodeSelector: map[string]string{"infra": "false"},
119 admit: false,
120 testName: "Conflicting pod and namespace node selector, one label",
121 },
122 {
123 defaultNodeSelector: "env=dev",
124 namespaceNodeSelector: "infra=false, env = test",
125 podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
126 admit: false,
127 testName: "Conflicting pod and namespace node selector, multiple labels",
128 },
129 {
130 defaultNodeSelector: "env=dev",
131 namespaceNodeSelector: "infra=false, env = dev",
132 whitelist: "env=dev, infra=false, color=blue",
133 podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
134 mergedNodeSelector: labels.Set{"infra": "false", "env": "dev", "color": "blue"},
135 admit: true,
136 testName: "Merged pod node selectors satisfy the whitelist",
137 },
138 {
139 defaultNodeSelector: "env=dev",
140 namespaceNodeSelector: "infra=false, env = dev",
141 whitelist: "env=dev, infra=true, color=blue",
142 podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
143 admit: false,
144 testName: "Merged pod node selectors conflict with the whitelist",
145 },
146 {
147 defaultNodeSelector: "env=dev",
148 ignoreTestNamespaceNodeSelector: true,
149 whitelist: "env=prd",
150 podNodeSelector: map[string]string{},
151 admit: false,
152 testName: "Default node selector conflict with the whitelist",
153 },
154 }
155 for _, test := range tests {
156 if !test.ignoreTestNamespaceNodeSelector {
157 namespace.ObjectMeta.Annotations = map[string]string{"scheduler.alpha.kubernetes.io/node-selector": test.namespaceNodeSelector}
158 informerFactory.Core().V1().Namespaces().Informer().GetStore().Update(namespace)
159 }
160 handler.clusterNodeSelectors = make(map[string]string)
161 handler.clusterNodeSelectors["clusterDefaultNodeSelector"] = test.defaultNodeSelector
162 handler.clusterNodeSelectors[namespace.Name] = test.whitelist
163 pod.Spec = api.PodSpec{NodeSelector: test.podNodeSelector}
164
165 err := handler.Admit(context.TODO(), admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
166 if test.admit && err != nil {
167 t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
168 } else if !test.admit && err == nil {
169 t.Errorf("Test: %s, expected an error", test.testName)
170 }
171 if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
172 t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
173 }
174 err = handler.Validate(context.TODO(), admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
175 if test.admit && err != nil {
176 t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
177 } else if !test.admit && err == nil {
178 t.Errorf("Test: %s, expected an error", test.testName)
179 }
180 }
181 }
182
183 func TestHandles(t *testing.T) {
184 for op, shouldHandle := range map[admission.Operation]bool{
185 admission.Create: true,
186 admission.Update: false,
187 admission.Connect: false,
188 admission.Delete: false,
189 } {
190 nodeEnvionment := NewPodNodeSelector(nil)
191 if e, a := shouldHandle, nodeEnvionment.Handles(op); e != a {
192 t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a)
193 }
194 }
195 }
196
197
198 func newHandlerForTest(c kubernetes.Interface) (*Plugin, informers.SharedInformerFactory, error) {
199 f := informers.NewSharedInformerFactory(c, 5*time.Minute)
200 handler := NewPodNodeSelector(nil)
201 pluginInitializer := genericadmissioninitializer.New(c, nil, f, nil, nil, nil, nil)
202 pluginInitializer.Initialize(handler)
203 err := admission.ValidateInitialization(handler)
204 return handler, f, err
205 }
206
View as plain text