k8s.io/kubernetes@v1.29.3/pkg/controlplane/reconcilers/endpointsadapter_test.go (about) 1 /* 2 Copyright 2019 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 reconcilers 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 corev1 "k8s.io/api/core/v1" 25 discovery "k8s.io/api/discovery/v1" 26 apiequality "k8s.io/apimachinery/pkg/api/equality" 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/client-go/kubernetes/fake" 32 ) 33 34 func TestEndpointsAdapterGet(t *testing.T) { 35 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"}) 36 37 testCases := map[string]struct { 38 expectedError error 39 expectedEndpoints *corev1.Endpoints 40 initialState []runtime.Object 41 namespaceParam string 42 nameParam string 43 }{ 44 "single-existing-endpoints": { 45 expectedError: nil, 46 expectedEndpoints: endpoints1, 47 initialState: []runtime.Object{endpoints1, epSlice1}, 48 namespaceParam: "testing", 49 nameParam: "foo", 50 }, 51 "endpoints exists, endpointslice does not": { 52 expectedError: nil, 53 expectedEndpoints: endpoints1, 54 initialState: []runtime.Object{endpoints1}, 55 namespaceParam: "testing", 56 nameParam: "foo", 57 }, 58 "endpointslice exists, endpoints does not": { 59 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"), 60 expectedEndpoints: nil, 61 initialState: []runtime.Object{epSlice1}, 62 namespaceParam: "testing", 63 nameParam: "foo", 64 }, 65 "wrong-namespace": { 66 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"), 67 expectedEndpoints: nil, 68 initialState: []runtime.Object{endpoints1, epSlice1}, 69 namespaceParam: "foo", 70 nameParam: "foo", 71 }, 72 "wrong-name": { 73 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"), 74 expectedEndpoints: nil, 75 initialState: []runtime.Object{endpoints1, epSlice1}, 76 namespaceParam: "testing", 77 nameParam: "bar", 78 }, 79 } 80 81 for name, testCase := range testCases { 82 t.Run(name, func(t *testing.T) { 83 client := fake.NewSimpleClientset(testCase.initialState...) 84 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1()) 85 86 endpoints, err := epAdapter.Get(testCase.namespaceParam, testCase.nameParam, metav1.GetOptions{}) 87 88 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) { 89 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err) 90 } 91 92 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedEndpoints) { 93 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedEndpoints, endpoints) 94 } 95 }) 96 } 97 } 98 99 func TestEndpointsAdapterCreate(t *testing.T) { 100 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"}) 101 102 // even if an Endpoints resource includes an IPv6 address, it should not be 103 // included in the corresponding EndpointSlice. 104 endpoints2, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6", "1234::5678:0000:0000:9abc:def0"}) 105 _, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6"}) 106 107 // ensure that Endpoints with only IPv6 addresses result in EndpointSlice 108 // with an IPv6 address type. 109 endpoints3, epSlice3 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"1234::5678:0000:0000:9abc:def0"}) 110 epSlice3.AddressType = discovery.AddressTypeIPv6 111 112 testCases := map[string]struct { 113 expectedError error 114 expectedResult *corev1.Endpoints 115 expectCreate []runtime.Object 116 expectUpdate []runtime.Object 117 initialState []runtime.Object 118 namespaceParam string 119 endpointsParam *corev1.Endpoints 120 }{ 121 "single-endpoint": { 122 expectedError: nil, 123 expectedResult: endpoints1, 124 expectCreate: []runtime.Object{endpoints1, epSlice1}, 125 initialState: []runtime.Object{}, 126 namespaceParam: endpoints1.Namespace, 127 endpointsParam: endpoints1, 128 }, 129 "single-endpoint-partial-ipv6": { 130 expectedError: nil, 131 expectedResult: endpoints2, 132 expectCreate: []runtime.Object{endpoints2, epSlice2}, 133 initialState: []runtime.Object{}, 134 namespaceParam: endpoints2.Namespace, 135 endpointsParam: endpoints2, 136 }, 137 "single-endpoint-full-ipv6": { 138 expectedError: nil, 139 expectedResult: endpoints3, 140 expectCreate: []runtime.Object{endpoints3, epSlice3}, 141 initialState: []runtime.Object{}, 142 namespaceParam: endpoints3.Namespace, 143 endpointsParam: endpoints3, 144 }, 145 "existing-endpoints": { 146 expectedError: errors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"), 147 expectedResult: nil, 148 initialState: []runtime.Object{endpoints1, epSlice1}, 149 namespaceParam: endpoints1.Namespace, 150 endpointsParam: endpoints1, 151 152 // We expect the create to be attempted, we just also expect it to fail 153 expectCreate: []runtime.Object{endpoints1}, 154 }, 155 "existing-endpointslice-incorrect": { 156 // No error when we need to create the Endpoints but the correct 157 // EndpointSlice already exists 158 expectedError: nil, 159 expectedResult: endpoints1, 160 expectCreate: []runtime.Object{endpoints1}, 161 initialState: []runtime.Object{epSlice1}, 162 namespaceParam: endpoints1.Namespace, 163 endpointsParam: endpoints1, 164 }, 165 "existing-endpointslice-correct": { 166 // No error when we need to create the Endpoints but an incorrect 167 // EndpointSlice already exists 168 expectedError: nil, 169 expectedResult: endpoints2, 170 expectCreate: []runtime.Object{endpoints2}, 171 expectUpdate: []runtime.Object{epSlice2}, 172 initialState: []runtime.Object{epSlice1}, 173 namespaceParam: endpoints2.Namespace, 174 endpointsParam: endpoints2, 175 }, 176 } 177 178 for name, testCase := range testCases { 179 t.Run(name, func(t *testing.T) { 180 client := fake.NewSimpleClientset(testCase.initialState...) 181 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1()) 182 183 endpoints, err := epAdapter.Create(testCase.namespaceParam, testCase.endpointsParam) 184 185 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) { 186 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err) 187 } 188 189 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) { 190 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints) 191 } 192 193 err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate) 194 if err != nil { 195 t.Errorf("unexpected error in side effects: %v", err) 196 } 197 }) 198 } 199 } 200 201 func TestEndpointsAdapterUpdate(t *testing.T) { 202 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"}) 203 endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"}) 204 endpoints3, _ := generateEndpointsAndSlice("bar", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"}) 205 206 // ensure that EndpointSlice with deprecated IP address type is replaced 207 // with one that has an IPv4 address type. 208 endpoints4, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) 209 _, epSlice4IP := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) 210 // "IP" is a deprecated address type, ensuring that it is handled properly. 211 epSlice4IP.AddressType = discovery.AddressType("IP") 212 _, epSlice4IPv4 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"}) 213 214 testCases := map[string]struct { 215 expectedError error 216 expectedResult *corev1.Endpoints 217 expectCreate []runtime.Object 218 expectUpdate []runtime.Object 219 initialState []runtime.Object 220 namespaceParam string 221 endpointsParam *corev1.Endpoints 222 }{ 223 "single-existing-endpoints-no-change": { 224 expectedError: nil, 225 expectedResult: endpoints1, 226 initialState: []runtime.Object{endpoints1, epSlice1}, 227 namespaceParam: "testing", 228 endpointsParam: endpoints1, 229 230 // Even though there's no change, we still expect Update() to be 231 // called, because this unit test ALWAYS calls Update(). 232 expectUpdate: []runtime.Object{endpoints1}, 233 }, 234 "existing-endpointslice-replaced-with-updated-ipv4-address-type": { 235 expectedError: nil, 236 expectedResult: endpoints4, 237 initialState: []runtime.Object{endpoints4, epSlice4IP}, 238 namespaceParam: "testing", 239 endpointsParam: endpoints4, 240 241 // When AddressType changes, we Delete+Create the EndpointSlice, 242 // so that shows up in expectCreate, not expectUpdate. 243 expectUpdate: []runtime.Object{endpoints4}, 244 expectCreate: []runtime.Object{epSlice4IPv4}, 245 }, 246 "add-ports-and-ips": { 247 expectedError: nil, 248 expectedResult: endpoints2, 249 expectUpdate: []runtime.Object{endpoints2, epSlice2}, 250 initialState: []runtime.Object{endpoints1, epSlice1}, 251 namespaceParam: "testing", 252 endpointsParam: endpoints2, 253 }, 254 "endpoints-correct-endpointslice-wrong": { 255 expectedError: nil, 256 expectedResult: endpoints2, 257 expectUpdate: []runtime.Object{endpoints2, epSlice2}, 258 initialState: []runtime.Object{endpoints2, epSlice1}, 259 namespaceParam: "testing", 260 endpointsParam: endpoints2, 261 }, 262 "endpointslice-correct-endpoints-wrong": { 263 expectedError: nil, 264 expectedResult: endpoints2, 265 expectUpdate: []runtime.Object{endpoints2}, 266 initialState: []runtime.Object{endpoints1, epSlice2}, 267 namespaceParam: "testing", 268 endpointsParam: endpoints2, 269 }, 270 "wrong-endpoints": { 271 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"), 272 expectedResult: nil, 273 expectUpdate: []runtime.Object{endpoints3}, 274 initialState: []runtime.Object{endpoints1, epSlice1}, 275 namespaceParam: "testing", 276 endpointsParam: endpoints3, 277 }, 278 "missing-endpoints": { 279 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"), 280 expectedResult: nil, 281 initialState: []runtime.Object{endpoints1, epSlice1}, 282 namespaceParam: "testing", 283 endpointsParam: endpoints3, 284 285 // We expect the update to be attempted, we just also expect it to fail 286 expectUpdate: []runtime.Object{endpoints3}, 287 }, 288 "missing-endpointslice": { 289 // No error when we need to update the Endpoints but the 290 // EndpointSlice doesn't exist 291 expectedError: nil, 292 expectedResult: endpoints1, 293 expectUpdate: []runtime.Object{endpoints1}, 294 expectCreate: []runtime.Object{epSlice1}, 295 initialState: []runtime.Object{endpoints2}, 296 namespaceParam: "testing", 297 endpointsParam: endpoints1, 298 }, 299 } 300 301 for name, testCase := range testCases { 302 t.Run(name, func(t *testing.T) { 303 client := fake.NewSimpleClientset(testCase.initialState...) 304 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1()) 305 306 endpoints, err := epAdapter.Update(testCase.namespaceParam, testCase.endpointsParam) 307 308 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) { 309 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err) 310 } 311 312 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) { 313 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints) 314 } 315 316 err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate) 317 if err != nil { 318 t.Errorf("unexpected error in side effects: %v", err) 319 } 320 }) 321 } 322 } 323 324 func generateEndpointsAndSlice(name, namespace string, ports []int, addresses []string) (*corev1.Endpoints, *discovery.EndpointSlice) { 325 trueBool := true 326 327 epSlice := &discovery.EndpointSlice{ 328 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, 329 AddressType: discovery.AddressTypeIPv4, 330 } 331 epSlice.Labels = map[string]string{discovery.LabelServiceName: name} 332 subset := corev1.EndpointSubset{} 333 334 for i, port := range ports { 335 endpointPort := corev1.EndpointPort{ 336 Name: fmt.Sprintf("port-%d", i), 337 Port: int32(port), 338 Protocol: corev1.ProtocolTCP, 339 } 340 subset.Ports = append(subset.Ports, endpointPort) 341 epSlice.Ports = append(epSlice.Ports, discovery.EndpointPort{ 342 Name: &endpointPort.Name, 343 Port: &endpointPort.Port, 344 Protocol: &endpointPort.Protocol, 345 }) 346 } 347 348 for i, address := range addresses { 349 endpointAddress := corev1.EndpointAddress{ 350 IP: address, 351 TargetRef: &corev1.ObjectReference{ 352 Kind: "Pod", 353 Name: fmt.Sprintf("pod-%d", i), 354 }, 355 } 356 357 subset.Addresses = append(subset.Addresses, endpointAddress) 358 359 epSlice.Endpoints = append(epSlice.Endpoints, discovery.Endpoint{ 360 Addresses: []string{endpointAddress.IP}, 361 TargetRef: endpointAddress.TargetRef, 362 Conditions: discovery.EndpointConditions{Ready: &trueBool}, 363 }) 364 } 365 366 return &corev1.Endpoints{ 367 ObjectMeta: metav1.ObjectMeta{ 368 Name: name, 369 Namespace: namespace, 370 Labels: map[string]string{ 371 discovery.LabelSkipMirror: "true", 372 }, 373 }, 374 Subsets: []corev1.EndpointSubset{subset}, 375 }, epSlice 376 } 377 378 func TestEndpointManagerEnsureEndpointSliceFromEndpoints(t *testing.T) { 379 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"}) 380 endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"}) 381 382 testCases := map[string]struct { 383 expectedError error 384 expectedEndpointSlice *discovery.EndpointSlice 385 initialState []runtime.Object 386 namespaceParam string 387 endpointsParam *corev1.Endpoints 388 }{ 389 "existing-endpointslice-no-change": { 390 expectedError: nil, 391 expectedEndpointSlice: epSlice1, 392 initialState: []runtime.Object{epSlice1}, 393 namespaceParam: "testing", 394 endpointsParam: endpoints1, 395 }, 396 "existing-endpointslice-change": { 397 expectedError: nil, 398 expectedEndpointSlice: epSlice2, 399 initialState: []runtime.Object{epSlice1}, 400 namespaceParam: "testing", 401 endpointsParam: endpoints2, 402 }, 403 "missing-endpointslice": { 404 expectedError: nil, 405 expectedEndpointSlice: epSlice1, 406 initialState: []runtime.Object{}, 407 namespaceParam: "testing", 408 endpointsParam: endpoints1, 409 }, 410 } 411 412 for name, testCase := range testCases { 413 t.Run(name, func(t *testing.T) { 414 client := fake.NewSimpleClientset(testCase.initialState...) 415 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1()) 416 417 err := epAdapter.EnsureEndpointSliceFromEndpoints(testCase.namespaceParam, testCase.endpointsParam) 418 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) { 419 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err) 420 } 421 422 endpointSlice, err := client.DiscoveryV1().EndpointSlices(testCase.namespaceParam).Get(context.TODO(), testCase.endpointsParam.Name, metav1.GetOptions{}) 423 if err != nil && !errors.IsNotFound(err) { 424 t.Fatalf("Error getting Endpoint Slice: %v", err) 425 } 426 427 if !apiequality.Semantic.DeepEqual(endpointSlice, testCase.expectedEndpointSlice) { 428 t.Errorf("Expected Endpoint Slice: %v, got: %v", testCase.expectedEndpointSlice, endpointSlice) 429 } 430 }) 431 } 432 }