k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/antiaffinity/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 antiaffinity 18 19 import ( 20 "context" 21 "testing" 22 23 v1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apiserver/pkg/admission" 27 api "k8s.io/kubernetes/pkg/apis/core" 28 ) 29 30 // ensures the hard PodAntiAffinity is denied if it defines TopologyKey other than kubernetes.io/hostname. 31 // TODO: Add test case "invalid topologyKey in requiredDuringSchedulingRequiredDuringExecution then admission fails" 32 // after RequiredDuringSchedulingRequiredDuringExecution is implemented. 33 func TestInterPodAffinityAdmission(t *testing.T) { 34 handler := NewInterPodAntiAffinity() 35 pod := api.Pod{ 36 Spec: api.PodSpec{}, 37 } 38 tests := []struct { 39 affinity *api.Affinity 40 errorExpected bool 41 }{ 42 // empty affinity its success. 43 { 44 affinity: &api.Affinity{}, 45 errorExpected: false, 46 }, 47 // what ever topologyKey in preferredDuringSchedulingIgnoredDuringExecution, the admission should success. 48 { 49 affinity: &api.Affinity{ 50 PodAntiAffinity: &api.PodAntiAffinity{ 51 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 52 { 53 Weight: 5, 54 PodAffinityTerm: api.PodAffinityTerm{ 55 LabelSelector: &metav1.LabelSelector{ 56 MatchExpressions: []metav1.LabelSelectorRequirement{ 57 { 58 Key: "security", 59 Operator: metav1.LabelSelectorOpIn, 60 Values: []string{"S2"}, 61 }, 62 }, 63 }, 64 TopologyKey: "az", 65 }, 66 }, 67 }, 68 }, 69 }, 70 errorExpected: false, 71 }, 72 // valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution, 73 // plus any topologyKey in preferredDuringSchedulingIgnoredDuringExecution, then admission success. 74 { 75 affinity: &api.Affinity{ 76 PodAntiAffinity: &api.PodAntiAffinity{ 77 PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{ 78 { 79 Weight: 5, 80 PodAffinityTerm: api.PodAffinityTerm{ 81 LabelSelector: &metav1.LabelSelector{ 82 MatchExpressions: []metav1.LabelSelectorRequirement{ 83 { 84 Key: "security", 85 Operator: metav1.LabelSelectorOpIn, 86 Values: []string{"S2"}, 87 }, 88 }, 89 }, 90 TopologyKey: "az", 91 }, 92 }, 93 }, 94 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 95 { 96 LabelSelector: &metav1.LabelSelector{ 97 MatchExpressions: []metav1.LabelSelectorRequirement{ 98 { 99 Key: "security", 100 Operator: metav1.LabelSelectorOpIn, 101 Values: []string{"S2"}, 102 }, 103 }, 104 }, 105 TopologyKey: v1.LabelHostname, 106 }, 107 }, 108 }, 109 }, 110 errorExpected: false, 111 }, 112 // valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission success. 113 { 114 affinity: &api.Affinity{ 115 PodAntiAffinity: &api.PodAntiAffinity{ 116 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 117 { 118 LabelSelector: &metav1.LabelSelector{ 119 MatchExpressions: []metav1.LabelSelectorRequirement{ 120 { 121 Key: "security", 122 Operator: metav1.LabelSelectorOpIn, 123 Values: []string{"S2"}, 124 }, 125 }, 126 }, 127 TopologyKey: v1.LabelHostname, 128 }, 129 }, 130 }, 131 }, 132 errorExpected: false, 133 }, 134 // invalid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission fails. 135 { 136 affinity: &api.Affinity{ 137 PodAntiAffinity: &api.PodAntiAffinity{ 138 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 139 { 140 LabelSelector: &metav1.LabelSelector{ 141 MatchExpressions: []metav1.LabelSelectorRequirement{ 142 { 143 Key: "security", 144 Operator: metav1.LabelSelectorOpIn, 145 Values: []string{"S2"}, 146 }, 147 }, 148 }, 149 TopologyKey: " zone ", 150 }, 151 }, 152 }, 153 }, 154 errorExpected: true, 155 }, 156 // list of requiredDuringSchedulingIgnoredDuringExecution middle element topologyKey is not valid. 157 { 158 affinity: &api.Affinity{ 159 PodAntiAffinity: &api.PodAntiAffinity{ 160 RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{ 161 { 162 LabelSelector: &metav1.LabelSelector{ 163 MatchExpressions: []metav1.LabelSelectorRequirement{ 164 { 165 Key: "security", 166 Operator: metav1.LabelSelectorOpIn, 167 Values: []string{"S2"}, 168 }, 169 }, 170 }, 171 TopologyKey: v1.LabelHostname, 172 }, { 173 LabelSelector: &metav1.LabelSelector{ 174 MatchExpressions: []metav1.LabelSelectorRequirement{ 175 { 176 Key: "security", 177 Operator: metav1.LabelSelectorOpIn, 178 Values: []string{"S2"}, 179 }, 180 }, 181 }, 182 TopologyKey: " zone ", 183 }, { 184 LabelSelector: &metav1.LabelSelector{ 185 MatchExpressions: []metav1.LabelSelectorRequirement{ 186 { 187 Key: "security", 188 Operator: metav1.LabelSelectorOpIn, 189 Values: []string{"S2"}, 190 }, 191 }, 192 }, 193 TopologyKey: v1.LabelHostname, 194 }, 195 }, 196 }, 197 }, 198 errorExpected: true, 199 }, 200 } 201 for _, test := range tests { 202 pod.Spec.Affinity = test.affinity 203 err := handler.Validate(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil, false, nil), nil) 204 205 if test.errorExpected && err == nil { 206 t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity) 207 } 208 209 if !test.errorExpected && err != nil { 210 t.Errorf("Unexpected error %v for AntiAffinity %+v", err, test.affinity) 211 } 212 } 213 } 214 func TestHandles(t *testing.T) { 215 handler := NewInterPodAntiAffinity() 216 tests := map[admission.Operation]bool{ 217 admission.Update: true, 218 admission.Create: true, 219 admission.Delete: false, 220 admission.Connect: false, 221 } 222 for op, expected := range tests { 223 result := handler.Handles(op) 224 if result != expected { 225 t.Errorf("Unexpected result for operation %s: %v\n", op, result) 226 } 227 } 228 } 229 230 // TestOtherResources ensures that this admission controller is a no-op for other resources, 231 // subresources, and non-pods. 232 func TestOtherResources(t *testing.T) { 233 namespace := "testnamespace" 234 name := "testname" 235 pod := &api.Pod{ 236 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 237 } 238 tests := []struct { 239 name string 240 kind string 241 resource string 242 subresource string 243 object runtime.Object 244 expectError bool 245 }{ 246 { 247 name: "non-pod resource", 248 kind: "Foo", 249 resource: "foos", 250 object: pod, 251 }, 252 { 253 name: "pod subresource", 254 kind: "Pod", 255 resource: "pods", 256 subresource: "eviction", 257 object: pod, 258 }, 259 { 260 name: "non-pod object", 261 kind: "Pod", 262 resource: "pods", 263 object: &api.Service{}, 264 expectError: true, 265 }, 266 } 267 268 for _, tc := range tests { 269 handler := &Plugin{} 270 271 err := handler.Validate(context.TODO(), admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, &metav1.CreateOptions{}, false, nil), nil) 272 273 if tc.expectError { 274 if err == nil { 275 t.Errorf("%s: unexpected nil error", tc.name) 276 } 277 continue 278 } 279 280 if err != nil { 281 t.Errorf("%s: unexpected error: %v", tc.name, err) 282 continue 283 } 284 } 285 }