k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/podnodeselector/admission_test.go (about) 1 /* 2 Copyright 2016 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 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 // TestPodAdmission verifies various scenarios involving pod/namespace/global node label selectors 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 // newHandlerForTest returns the admission controller configured for testing. 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 }