k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/service/strategy_test.go (about) 1 /* 2 Copyright 2014 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 service 18 19 import ( 20 "reflect" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/fields" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/util/intstr" 29 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 30 "k8s.io/apiserver/pkg/registry/rest" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 featuregatetesting "k8s.io/component-base/featuregate/testing" 33 api "k8s.io/kubernetes/pkg/apis/core" 34 _ "k8s.io/kubernetes/pkg/apis/core/install" 35 "k8s.io/kubernetes/pkg/features" 36 "k8s.io/utils/ptr" 37 ) 38 39 func TestCheckGeneratedNameError(t *testing.T) { 40 ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{ 41 Resource: "foos", 42 }) 43 44 expect := errors.NewNotFound(api.Resource("foos"), "bar") 45 if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{}); err != expect { 46 t.Errorf("NotFoundError should be ignored: %v", err) 47 } 48 49 expect = errors.NewAlreadyExists(api.Resource("foos"), "bar") 50 if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{}); err != expect { 51 t.Errorf("AlreadyExists should be returned when no GenerateName field: %v", err) 52 } 53 54 expect = errors.NewAlreadyExists(api.Resource("foos"), "bar") 55 if err := rest.CheckGeneratedNameError(ctx, Strategy, expect, &api.Service{ObjectMeta: metav1.ObjectMeta{GenerateName: "foo"}}); err == nil || !errors.IsAlreadyExists(err) { 56 t.Errorf("expected try again later error: %v", err) 57 } 58 } 59 60 func makeValidService() *api.Service { 61 preferDual := api.IPFamilyPolicyPreferDualStack 62 clusterInternalTrafficPolicy := api.ServiceInternalTrafficPolicyCluster 63 64 return &api.Service{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: "valid", 67 Namespace: "default", 68 Labels: map[string]string{}, 69 Annotations: map[string]string{}, 70 ResourceVersion: "1", 71 }, 72 Spec: api.ServiceSpec{ 73 Selector: map[string]string{"key": "val"}, 74 SessionAffinity: "None", 75 Type: api.ServiceTypeClusterIP, 76 Ports: []api.ServicePort{ 77 makeValidServicePort("p", "TCP", 8675), 78 makeValidServicePort("q", "TCP", 309), 79 }, 80 ClusterIP: "1.2.3.4", 81 ClusterIPs: []string{"1.2.3.4", "5:6:7::8"}, 82 IPFamilyPolicy: &preferDual, 83 IPFamilies: []api.IPFamily{"IPv4", "IPv6"}, 84 InternalTrafficPolicy: &clusterInternalTrafficPolicy, 85 }, 86 } 87 } 88 89 func makeValidServicePort(name string, proto api.Protocol, port int32) api.ServicePort { 90 return api.ServicePort{ 91 Name: name, 92 Protocol: proto, 93 Port: port, 94 TargetPort: intstr.FromInt32(port), 95 } 96 } 97 98 func makeValidServiceCustom(tweaks ...func(svc *api.Service)) *api.Service { 99 svc := makeValidService() 100 for _, fn := range tweaks { 101 fn(svc) 102 } 103 return svc 104 } 105 106 func TestServiceStatusStrategy(t *testing.T) { 107 ctx := genericapirequest.NewDefaultContext() 108 if !StatusStrategy.NamespaceScoped() { 109 t.Errorf("Service must be namespace scoped") 110 } 111 oldService := makeValidService() 112 oldService.Spec.Type = api.ServiceTypeLoadBalancer 113 oldService.ResourceVersion = "4" 114 oldService.Spec.SessionAffinity = "None" 115 newService := oldService.DeepCopy() 116 newService.Spec.SessionAffinity = "ClientIP" 117 newService.Status = api.ServiceStatus{ 118 LoadBalancer: api.LoadBalancerStatus{ 119 Ingress: []api.LoadBalancerIngress{ 120 { 121 IP: "127.0.0.2", 122 IPMode: ptr.To(api.LoadBalancerIPModeVIP), 123 }, 124 }, 125 }, 126 } 127 StatusStrategy.PrepareForUpdate(ctx, newService, oldService) 128 if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" { 129 t.Errorf("Service status updates should allow change of status fields") 130 } 131 if newService.Spec.SessionAffinity != "None" { 132 t.Errorf("PrepareForUpdate should have preserved old spec") 133 } 134 errs := StatusStrategy.ValidateUpdate(ctx, newService, oldService) 135 if len(errs) != 0 { 136 t.Errorf("Unexpected error %v", errs) 137 } 138 } 139 140 func makeServiceWithConditions(conditions []metav1.Condition) *api.Service { 141 return &api.Service{ 142 Status: api.ServiceStatus{ 143 Conditions: conditions, 144 }, 145 } 146 } 147 148 func makeServiceWithPorts(ports []api.PortStatus) *api.Service { 149 return &api.Service{ 150 Status: api.ServiceStatus{ 151 LoadBalancer: api.LoadBalancerStatus{ 152 Ingress: []api.LoadBalancerIngress{ 153 { 154 Ports: ports, 155 }, 156 }, 157 }, 158 }, 159 } 160 } 161 162 func TestDropDisabledField(t *testing.T) { 163 testCases := []struct { 164 name string 165 svc *api.Service 166 oldSvc *api.Service 167 compareSvc *api.Service 168 }{ 169 /* svc.Status.Conditions */ 170 { 171 name: "mixed protocol enabled, field not used in old, not used in new", 172 svc: makeServiceWithConditions(nil), 173 oldSvc: makeServiceWithConditions(nil), 174 compareSvc: makeServiceWithConditions(nil), 175 }, 176 { 177 name: "mixed protocol enabled, field used in old and in new", 178 svc: makeServiceWithConditions([]metav1.Condition{}), 179 oldSvc: makeServiceWithConditions([]metav1.Condition{}), 180 compareSvc: makeServiceWithConditions([]metav1.Condition{}), 181 }, 182 { 183 name: "mixed protocol enabled, field not used in old, used in new", 184 svc: makeServiceWithConditions([]metav1.Condition{}), 185 oldSvc: makeServiceWithConditions(nil), 186 compareSvc: makeServiceWithConditions([]metav1.Condition{}), 187 }, 188 { 189 name: "mixed protocol enabled, field used in old, not used in new", 190 svc: makeServiceWithConditions(nil), 191 oldSvc: makeServiceWithConditions([]metav1.Condition{}), 192 compareSvc: makeServiceWithConditions(nil), 193 }, 194 /* svc.Status.LoadBalancer.Ingress.Ports */ 195 { 196 name: "mixed protocol enabled, field not used in old, not used in new", 197 svc: makeServiceWithPorts(nil), 198 oldSvc: makeServiceWithPorts(nil), 199 compareSvc: makeServiceWithPorts(nil), 200 }, 201 { 202 name: "mixed protocol enabled, field used in old and in new", 203 svc: makeServiceWithPorts([]api.PortStatus{}), 204 oldSvc: makeServiceWithPorts([]api.PortStatus{}), 205 compareSvc: makeServiceWithPorts([]api.PortStatus{}), 206 }, 207 { 208 name: "mixed protocol enabled, field not used in old, used in new", 209 svc: makeServiceWithPorts([]api.PortStatus{}), 210 oldSvc: makeServiceWithPorts(nil), 211 compareSvc: makeServiceWithPorts([]api.PortStatus{}), 212 }, 213 { 214 name: "mixed protocol enabled, field used in old, not used in new", 215 svc: makeServiceWithPorts(nil), 216 oldSvc: makeServiceWithPorts([]api.PortStatus{}), 217 compareSvc: makeServiceWithPorts(nil), 218 }, 219 /* add more tests for other dropped fields as needed */ 220 } 221 for _, tc := range testCases { 222 func() { 223 old := tc.oldSvc.DeepCopy() 224 225 // to test against user using IPFamily not set on cluster 226 dropServiceDisabledFields(tc.svc, tc.oldSvc) 227 228 // old node should never be changed 229 if !reflect.DeepEqual(tc.oldSvc, old) { 230 t.Errorf("%v: old svc changed: %v", tc.name, cmp.Diff(tc.oldSvc, old)) 231 } 232 233 if !reflect.DeepEqual(tc.svc, tc.compareSvc) { 234 t.Errorf("%v: unexpected svc spec: %v", tc.name, cmp.Diff(tc.svc, tc.compareSvc)) 235 } 236 }() 237 } 238 239 } 240 241 func TestDropServiceStatusDisabledFields(t *testing.T) { 242 ipModeVIP := api.LoadBalancerIPModeVIP 243 ipModeProxy := api.LoadBalancerIPModeProxy 244 245 testCases := []struct { 246 name string 247 ipModeEnabled bool 248 svc *api.Service 249 oldSvc *api.Service 250 compareSvc *api.Service 251 }{ 252 /*LoadBalancerIPMode disabled*/ 253 { 254 name: "LoadBalancerIPMode disabled, ipMode not used in old, not used in new", 255 ipModeEnabled: false, 256 svc: makeValidServiceCustom(func(svc *api.Service) { 257 svc.Spec.Type = api.ServiceTypeLoadBalancer 258 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 259 Ingress: []api.LoadBalancerIngress{{ 260 IP: "1.2.3.4", 261 }}, 262 } 263 }), 264 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 265 svc.Spec.Type = api.ServiceTypeLoadBalancer 266 svc.Status.LoadBalancer = api.LoadBalancerStatus{} 267 }), 268 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 269 svc.Spec.Type = api.ServiceTypeLoadBalancer 270 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 271 Ingress: []api.LoadBalancerIngress{{ 272 IP: "1.2.3.4", 273 }}, 274 } 275 }), 276 }, { 277 name: "LoadBalancerIPMode disabled, ipMode used in old and in new", 278 ipModeEnabled: false, 279 svc: makeValidServiceCustom(func(svc *api.Service) { 280 svc.Spec.Type = api.ServiceTypeLoadBalancer 281 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 282 Ingress: []api.LoadBalancerIngress{{ 283 IP: "1.2.3.4", 284 IPMode: &ipModeProxy, 285 }}, 286 } 287 }), 288 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 289 svc.Spec.Type = api.ServiceTypeLoadBalancer 290 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 291 Ingress: []api.LoadBalancerIngress{{ 292 IP: "1.2.3.4", 293 IPMode: &ipModeVIP, 294 }}, 295 } 296 }), 297 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 298 svc.Spec.Type = api.ServiceTypeLoadBalancer 299 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 300 Ingress: []api.LoadBalancerIngress{{ 301 IP: "1.2.3.4", 302 IPMode: &ipModeProxy, 303 }}, 304 } 305 }), 306 }, { 307 name: "LoadBalancerIPMode disabled, ipMode not used in old, used in new", 308 ipModeEnabled: false, 309 svc: makeValidServiceCustom(func(svc *api.Service) { 310 svc.Spec.Type = api.ServiceTypeLoadBalancer 311 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 312 Ingress: []api.LoadBalancerIngress{{ 313 IP: "1.2.3.4", 314 IPMode: &ipModeVIP, 315 }}, 316 } 317 }), 318 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 319 svc.Spec.Type = api.ServiceTypeLoadBalancer 320 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 321 Ingress: []api.LoadBalancerIngress{{ 322 IP: "1.2.3.4", 323 }}, 324 } 325 }), 326 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 327 svc.Spec.Type = api.ServiceTypeLoadBalancer 328 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 329 Ingress: []api.LoadBalancerIngress{{ 330 IP: "1.2.3.4", 331 }}, 332 } 333 }), 334 }, { 335 name: "LoadBalancerIPMode disabled, ipMode used in old, not used in new", 336 ipModeEnabled: false, 337 svc: makeValidServiceCustom(func(svc *api.Service) { 338 svc.Spec.Type = api.ServiceTypeLoadBalancer 339 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 340 Ingress: []api.LoadBalancerIngress{{ 341 IP: "1.2.3.4", 342 }}, 343 } 344 }), 345 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 346 svc.Spec.Type = api.ServiceTypeLoadBalancer 347 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 348 Ingress: []api.LoadBalancerIngress{{ 349 IP: "1.2.3.4", 350 IPMode: &ipModeProxy, 351 }}, 352 } 353 }), 354 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 355 svc.Spec.Type = api.ServiceTypeLoadBalancer 356 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 357 Ingress: []api.LoadBalancerIngress{{ 358 IP: "1.2.3.4", 359 }}, 360 } 361 }), 362 }, 363 /*LoadBalancerIPMode enabled*/ 364 { 365 name: "LoadBalancerIPMode enabled, ipMode not used in old, not used in new", 366 ipModeEnabled: true, 367 svc: makeValidServiceCustom(func(svc *api.Service) { 368 svc.Spec.Type = api.ServiceTypeLoadBalancer 369 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 370 Ingress: []api.LoadBalancerIngress{{ 371 IP: "1.2.3.4", 372 }}, 373 } 374 }), 375 oldSvc: nil, 376 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 377 svc.Spec.Type = api.ServiceTypeLoadBalancer 378 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 379 Ingress: []api.LoadBalancerIngress{{ 380 IP: "1.2.3.4", 381 }}, 382 } 383 }), 384 }, { 385 name: "LoadBalancerIPMode enabled, ipMode used in old and in new", 386 ipModeEnabled: true, 387 svc: makeValidServiceCustom(func(svc *api.Service) { 388 svc.Spec.Type = api.ServiceTypeLoadBalancer 389 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 390 Ingress: []api.LoadBalancerIngress{{ 391 IP: "1.2.3.4", 392 IPMode: &ipModeProxy, 393 }}, 394 } 395 }), 396 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 397 svc.Spec.Type = api.ServiceTypeLoadBalancer 398 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 399 Ingress: []api.LoadBalancerIngress{{ 400 IP: "1.2.3.4", 401 IPMode: &ipModeVIP, 402 }}, 403 } 404 }), 405 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 406 svc.Spec.Type = api.ServiceTypeLoadBalancer 407 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 408 Ingress: []api.LoadBalancerIngress{{ 409 IP: "1.2.3.4", 410 IPMode: &ipModeProxy, 411 }}, 412 } 413 }), 414 }, { 415 name: "LoadBalancerIPMode enabled, ipMode not used in old, used in new", 416 ipModeEnabled: true, 417 svc: makeValidServiceCustom(func(svc *api.Service) { 418 svc.Spec.Type = api.ServiceTypeLoadBalancer 419 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 420 Ingress: []api.LoadBalancerIngress{{ 421 IP: "1.2.3.4", 422 IPMode: &ipModeVIP, 423 }}, 424 } 425 }), 426 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 427 svc.Spec.Type = api.ServiceTypeLoadBalancer 428 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 429 Ingress: []api.LoadBalancerIngress{{ 430 IP: "1.2.3.4", 431 }}, 432 } 433 }), 434 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 435 svc.Spec.Type = api.ServiceTypeLoadBalancer 436 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 437 Ingress: []api.LoadBalancerIngress{{ 438 IP: "1.2.3.4", 439 IPMode: &ipModeVIP, 440 }}, 441 } 442 }), 443 }, { 444 name: "LoadBalancerIPMode enabled, ipMode used in old, not used in new", 445 ipModeEnabled: true, 446 svc: makeValidServiceCustom(func(svc *api.Service) { 447 svc.Spec.Type = api.ServiceTypeLoadBalancer 448 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 449 Ingress: []api.LoadBalancerIngress{{ 450 IP: "1.2.3.4", 451 }}, 452 } 453 }), 454 oldSvc: makeValidServiceCustom(func(svc *api.Service) { 455 svc.Spec.Type = api.ServiceTypeLoadBalancer 456 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 457 Ingress: []api.LoadBalancerIngress{{ 458 IP: "1.2.3.4", 459 IPMode: &ipModeProxy, 460 }}, 461 } 462 }), 463 compareSvc: makeValidServiceCustom(func(svc *api.Service) { 464 svc.Spec.Type = api.ServiceTypeLoadBalancer 465 svc.Status.LoadBalancer = api.LoadBalancerStatus{ 466 Ingress: []api.LoadBalancerIngress{{ 467 IP: "1.2.3.4", 468 }}, 469 } 470 }), 471 }, 472 } 473 474 for _, tc := range testCases { 475 t.Run(tc.name, func(t *testing.T) { 476 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled) 477 dropServiceStatusDisabledFields(tc.svc, tc.oldSvc) 478 479 if !reflect.DeepEqual(tc.svc, tc.compareSvc) { 480 t.Errorf("%v: unexpected svc spec: %v", tc.name, cmp.Diff(tc.svc, tc.compareSvc)) 481 } 482 }) 483 } 484 } 485 486 func TestDropTypeDependentFields(t *testing.T) { 487 // Tweaks used below. 488 setTypeExternalName := func(svc *api.Service) { 489 svc.Spec.Type = api.ServiceTypeExternalName 490 } 491 setTypeNodePort := func(svc *api.Service) { 492 svc.Spec.Type = api.ServiceTypeNodePort 493 } 494 setTypeClusterIP := func(svc *api.Service) { 495 svc.Spec.Type = api.ServiceTypeClusterIP 496 } 497 setTypeLoadBalancer := func(svc *api.Service) { 498 svc.Spec.Type = api.ServiceTypeLoadBalancer 499 } 500 clearClusterIPs := func(svc *api.Service) { 501 svc.Spec.ClusterIP = "" 502 svc.Spec.ClusterIPs = nil 503 } 504 changeClusterIPs := func(svc *api.Service) { 505 svc.Spec.ClusterIP += "0" 506 svc.Spec.ClusterIPs[0] += "0" 507 } 508 setNodePorts := func(svc *api.Service) { 509 for i := range svc.Spec.Ports { 510 svc.Spec.Ports[i].NodePort = int32(30000 + i) 511 } 512 } 513 changeNodePorts := func(svc *api.Service) { 514 for i := range svc.Spec.Ports { 515 svc.Spec.Ports[i].NodePort += 100 516 } 517 } 518 setExternalIPs := func(svc *api.Service) { 519 svc.Spec.ExternalIPs = []string{"1.1.1.1"} 520 } 521 clearExternalIPs := func(svc *api.Service) { 522 svc.Spec.ExternalIPs = nil 523 } 524 setExternalTrafficPolicyCluster := func(svc *api.Service) { 525 svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyCluster 526 } 527 clearExternalTrafficPolicy := func(svc *api.Service) { 528 svc.Spec.ExternalTrafficPolicy = "" 529 } 530 clearIPFamilies := func(svc *api.Service) { 531 svc.Spec.IPFamilies = nil 532 } 533 changeIPFamilies := func(svc *api.Service) { 534 svc.Spec.IPFamilies[0] = svc.Spec.IPFamilies[1] 535 } 536 clearIPFamilyPolicy := func(svc *api.Service) { 537 svc.Spec.IPFamilyPolicy = nil 538 } 539 changeIPFamilyPolicy := func(svc *api.Service) { 540 single := api.IPFamilyPolicySingleStack 541 svc.Spec.IPFamilyPolicy = &single 542 } 543 addPort := func(svc *api.Service) { 544 svc.Spec.Ports = append(svc.Spec.Ports, makeValidServicePort("new", "TCP", 0)) 545 } 546 delPort := func(svc *api.Service) { 547 svc.Spec.Ports = svc.Spec.Ports[0 : len(svc.Spec.Ports)-1] 548 } 549 changePort := func(svc *api.Service) { 550 svc.Spec.Ports[0].Port += 100 551 svc.Spec.Ports[0].Protocol = "UDP" 552 } 553 setHCNodePort := func(svc *api.Service) { 554 svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyLocal 555 svc.Spec.HealthCheckNodePort = int32(32000) 556 } 557 changeHCNodePort := func(svc *api.Service) { 558 svc.Spec.HealthCheckNodePort += 100 559 } 560 patches := func(fns ...func(svc *api.Service)) func(svc *api.Service) { 561 return func(svc *api.Service) { 562 for _, fn := range fns { 563 fn(svc) 564 } 565 } 566 } 567 setAllocateLoadBalancerNodePortsTrue := func(svc *api.Service) { 568 svc.Spec.AllocateLoadBalancerNodePorts = ptr.To(true) 569 } 570 setAllocateLoadBalancerNodePortsFalse := func(svc *api.Service) { 571 svc.Spec.AllocateLoadBalancerNodePorts = ptr.To(false) 572 } 573 clearAllocateLoadBalancerNodePorts := func(svc *api.Service) { 574 svc.Spec.AllocateLoadBalancerNodePorts = nil 575 } 576 setLoadBalancerClass := func(svc *api.Service) { 577 svc.Spec.LoadBalancerClass = ptr.To("test-load-balancer-class") 578 } 579 clearLoadBalancerClass := func(svc *api.Service) { 580 svc.Spec.LoadBalancerClass = nil 581 } 582 changeLoadBalancerClass := func(svc *api.Service) { 583 svc.Spec.LoadBalancerClass = ptr.To("test-load-balancer-class-changed") 584 } 585 586 testCases := []struct { 587 name string 588 svc *api.Service 589 patch func(svc *api.Service) 590 expect *api.Service 591 }{ 592 { // clusterIP cases 593 name: "don't clear clusterIP et al", 594 svc: makeValidService(), 595 patch: nil, 596 expect: makeValidService(), 597 }, { 598 name: "clear clusterIP et al", 599 svc: makeValidService(), 600 patch: setTypeExternalName, 601 expect: makeValidServiceCustom(setTypeExternalName, clearClusterIPs, clearIPFamilies, clearIPFamilyPolicy), 602 }, { 603 name: "don't clear changed clusterIP", 604 svc: makeValidService(), 605 patch: patches(setTypeExternalName, changeClusterIPs), 606 expect: makeValidServiceCustom(setTypeExternalName, changeClusterIPs, clearIPFamilies, clearIPFamilyPolicy), 607 }, { 608 name: "don't clear changed ipFamilies", 609 svc: makeValidService(), 610 patch: patches(setTypeExternalName, changeIPFamilies), 611 expect: makeValidServiceCustom(setTypeExternalName, clearClusterIPs, changeIPFamilies, clearIPFamilyPolicy), 612 }, { 613 name: "don't clear changed ipFamilyPolicy", 614 svc: makeValidService(), 615 patch: patches(setTypeExternalName, changeIPFamilyPolicy), 616 expect: makeValidServiceCustom(setTypeExternalName, clearClusterIPs, clearIPFamilies, changeIPFamilyPolicy), 617 }, { // nodePort cases 618 name: "don't clear nodePorts for type=NodePort", 619 svc: makeValidServiceCustom(setTypeNodePort, setNodePorts), 620 patch: nil, 621 expect: makeValidServiceCustom(setTypeNodePort, setNodePorts), 622 }, { 623 name: "don't clear nodePorts for type=LoadBalancer", 624 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 625 patch: nil, 626 expect: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 627 }, { 628 name: "clear nodePorts", 629 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 630 patch: setTypeClusterIP, 631 expect: makeValidService(), 632 }, { 633 name: "don't clear changed nodePorts", 634 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 635 patch: patches(setTypeClusterIP, changeNodePorts), 636 expect: makeValidServiceCustom(setNodePorts, changeNodePorts), 637 }, { 638 name: "clear nodePorts when adding a port", 639 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 640 patch: patches(setTypeClusterIP, addPort), 641 expect: makeValidServiceCustom(addPort), 642 }, { 643 name: "don't clear nodePorts when adding a port with NodePort", 644 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 645 patch: patches(setTypeClusterIP, addPort, setNodePorts), 646 expect: makeValidServiceCustom(addPort, setNodePorts), 647 }, { 648 name: "clear nodePorts when removing a port", 649 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 650 patch: patches(setTypeClusterIP, delPort), 651 expect: makeValidServiceCustom(delPort), 652 }, { 653 name: "clear nodePorts when changing a port", 654 svc: makeValidServiceCustom(setTypeLoadBalancer, setNodePorts), 655 patch: patches(setTypeClusterIP, changePort), 656 expect: makeValidServiceCustom(changePort), 657 }, { // healthCheckNodePort cases 658 name: "don't clear healthCheckNodePort for type=LoadBalancer", 659 svc: makeValidServiceCustom(setTypeLoadBalancer, setHCNodePort), 660 patch: nil, 661 expect: makeValidServiceCustom(setTypeLoadBalancer, setHCNodePort), 662 }, { 663 name: "clear healthCheckNodePort", 664 svc: makeValidServiceCustom(setTypeLoadBalancer, setHCNodePort), 665 patch: setTypeClusterIP, 666 expect: makeValidService(), 667 }, { 668 name: "don't clear changed healthCheckNodePort", 669 svc: makeValidServiceCustom(setTypeLoadBalancer, setHCNodePort), 670 patch: patches(setTypeClusterIP, changeHCNodePort), 671 expect: makeValidServiceCustom(setHCNodePort, changeHCNodePort, clearExternalTrafficPolicy), 672 }, { // allocatedLoadBalancerNodePorts cases 673 name: "clear allocatedLoadBalancerNodePorts true -> true", 674 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsTrue), 675 patch: setTypeNodePort, 676 expect: makeValidServiceCustom(setTypeNodePort), 677 }, { 678 name: "clear allocatedLoadBalancerNodePorts false -> false", 679 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsFalse), 680 patch: setTypeNodePort, 681 expect: makeValidServiceCustom(setTypeNodePort), 682 }, { 683 name: "set allocatedLoadBalancerNodePorts nil -> true", 684 svc: makeValidServiceCustom(setTypeLoadBalancer), 685 patch: patches(setTypeNodePort, setAllocateLoadBalancerNodePortsTrue), 686 expect: makeValidServiceCustom(setTypeNodePort, setAllocateLoadBalancerNodePortsTrue), 687 }, { 688 name: "set allocatedLoadBalancerNodePorts nil -> false", 689 svc: makeValidServiceCustom(setTypeLoadBalancer), 690 patch: patches(setTypeNodePort, setAllocateLoadBalancerNodePortsFalse), 691 expect: makeValidServiceCustom(setTypeNodePort, setAllocateLoadBalancerNodePortsFalse), 692 }, { 693 name: "set allocatedLoadBalancerNodePorts true -> nil", 694 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsTrue), 695 patch: patches(setTypeNodePort, clearAllocateLoadBalancerNodePorts), 696 expect: makeValidServiceCustom(setTypeNodePort), 697 }, { 698 name: "set allocatedLoadBalancerNodePorts false -> nil", 699 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsFalse), 700 patch: patches(setTypeNodePort, clearAllocateLoadBalancerNodePorts), 701 expect: makeValidServiceCustom(setTypeNodePort), 702 }, { 703 name: "set allocatedLoadBalancerNodePorts true -> false", 704 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsTrue), 705 patch: patches(setTypeNodePort, setAllocateLoadBalancerNodePortsFalse), 706 expect: makeValidServiceCustom(setTypeNodePort, setAllocateLoadBalancerNodePortsFalse), 707 }, { 708 name: "set allocatedLoadBalancerNodePorts false -> true", 709 svc: makeValidServiceCustom(setTypeLoadBalancer, setAllocateLoadBalancerNodePortsFalse), 710 patch: patches(setTypeNodePort, setAllocateLoadBalancerNodePortsTrue), 711 expect: makeValidServiceCustom(setTypeNodePort, setAllocateLoadBalancerNodePortsTrue), 712 }, { // loadBalancerClass cases 713 name: "clear loadBalancerClass when set Service type LoadBalancer -> non LoadBalancer", 714 svc: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 715 patch: setTypeClusterIP, 716 expect: makeValidServiceCustom(setTypeClusterIP, clearLoadBalancerClass), 717 }, { 718 name: "update loadBalancerClass load balancer class name", 719 svc: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 720 patch: changeLoadBalancerClass, 721 expect: makeValidServiceCustom(setTypeLoadBalancer, changeLoadBalancerClass), 722 }, { 723 name: "clear load balancer class name", 724 svc: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 725 patch: clearLoadBalancerClass, 726 expect: makeValidServiceCustom(setTypeLoadBalancer, clearLoadBalancerClass), 727 }, { 728 name: "change service type and load balancer class", 729 svc: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 730 patch: patches(setTypeClusterIP, changeLoadBalancerClass), 731 expect: makeValidServiceCustom(setTypeClusterIP, changeLoadBalancerClass), 732 }, { 733 name: "change service type to load balancer and set load balancer class", 734 svc: makeValidServiceCustom(setTypeClusterIP), 735 patch: patches(setTypeLoadBalancer, setLoadBalancerClass), 736 expect: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 737 }, { 738 name: "don't clear load balancer class for Type=LoadBalancer", 739 svc: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 740 patch: nil, 741 expect: makeValidServiceCustom(setTypeLoadBalancer, setLoadBalancerClass), 742 }, { 743 name: "clear externalTrafficPolicy when removing externalIPs for Type=ClusterIP", 744 svc: makeValidServiceCustom(setTypeClusterIP, setExternalIPs, setExternalTrafficPolicyCluster), 745 patch: patches(clearExternalIPs), 746 expect: makeValidServiceCustom(setTypeClusterIP, clearExternalTrafficPolicy), 747 }} 748 749 for _, tc := range testCases { 750 t.Run(tc.name, func(t *testing.T) { 751 result := tc.svc.DeepCopy() 752 if tc.patch != nil { 753 tc.patch(result) 754 } 755 dropTypeDependentFields(result, tc.svc) 756 if result.Spec.ClusterIP != tc.expect.Spec.ClusterIP { 757 t.Errorf("expected clusterIP %q, got %q", tc.expect.Spec.ClusterIP, result.Spec.ClusterIP) 758 } 759 if !reflect.DeepEqual(result.Spec.ClusterIPs, tc.expect.Spec.ClusterIPs) { 760 t.Errorf("expected clusterIPs %q, got %q", tc.expect.Spec.ClusterIP, result.Spec.ClusterIP) 761 } 762 if !reflect.DeepEqual(result.Spec.IPFamilies, tc.expect.Spec.IPFamilies) { 763 t.Errorf("expected ipFamilies %q, got %q", tc.expect.Spec.IPFamilies, result.Spec.IPFamilies) 764 } 765 if !reflect.DeepEqual(result.Spec.IPFamilyPolicy, tc.expect.Spec.IPFamilyPolicy) { 766 t.Errorf("expected ipFamilyPolicy %q, got %q", getIPFamilyPolicy(tc.expect), getIPFamilyPolicy(result)) 767 } 768 for i := range result.Spec.Ports { 769 resultPort := result.Spec.Ports[i].NodePort 770 expectPort := tc.expect.Spec.Ports[i].NodePort 771 if resultPort != expectPort { 772 t.Errorf("failed %q: expected Ports[%d].NodePort %d, got %d", tc.name, i, expectPort, resultPort) 773 } 774 } 775 if result.Spec.HealthCheckNodePort != tc.expect.Spec.HealthCheckNodePort { 776 t.Errorf("failed %q: expected healthCheckNodePort %d, got %d", tc.name, tc.expect.Spec.HealthCheckNodePort, result.Spec.HealthCheckNodePort) 777 } 778 if !reflect.DeepEqual(result.Spec.AllocateLoadBalancerNodePorts, tc.expect.Spec.AllocateLoadBalancerNodePorts) { 779 t.Errorf("failed %q: expected AllocateLoadBalancerNodePorts %v, got %v", tc.name, tc.expect.Spec.AllocateLoadBalancerNodePorts, result.Spec.AllocateLoadBalancerNodePorts) 780 } 781 if !reflect.DeepEqual(result.Spec.LoadBalancerClass, tc.expect.Spec.LoadBalancerClass) { 782 t.Errorf("failed %q: expected LoadBalancerClass %v, got %v", tc.name, tc.expect.Spec.LoadBalancerClass, result.Spec.LoadBalancerClass) 783 } 784 if !reflect.DeepEqual(result.Spec.ExternalTrafficPolicy, tc.expect.Spec.ExternalTrafficPolicy) { 785 t.Errorf("failed %q: expected ExternalTrafficPolicy %v, got %v", tc.name, tc.expect.Spec.ExternalTrafficPolicy, result.Spec.ExternalTrafficPolicy) 786 } 787 }) 788 } 789 } 790 791 func TestMatchService(t *testing.T) { 792 testCases := []struct { 793 name string 794 in *api.Service 795 fieldSelector fields.Selector 796 expectMatch bool 797 }{ 798 { 799 name: "match on name", 800 in: &api.Service{ 801 ObjectMeta: metav1.ObjectMeta{ 802 Name: "test", 803 Namespace: "testns", 804 }, 805 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 806 }, 807 fieldSelector: fields.ParseSelectorOrDie("metadata.name=test"), 808 expectMatch: true, 809 }, 810 { 811 name: "match on namespace", 812 in: &api.Service{ 813 ObjectMeta: metav1.ObjectMeta{ 814 Name: "test", 815 Namespace: "testns", 816 }, 817 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 818 }, 819 fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=testns"), 820 expectMatch: true, 821 }, 822 { 823 name: "no match on name", 824 in: &api.Service{ 825 ObjectMeta: metav1.ObjectMeta{ 826 Name: "test", 827 Namespace: "testns", 828 }, 829 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 830 }, 831 fieldSelector: fields.ParseSelectorOrDie("metadata.name=nomatch"), 832 expectMatch: false, 833 }, 834 { 835 name: "no match on namespace", 836 in: &api.Service{ 837 ObjectMeta: metav1.ObjectMeta{ 838 Name: "test", 839 Namespace: "testns", 840 }, 841 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 842 }, 843 fieldSelector: fields.ParseSelectorOrDie("metadata.namespace=nomatch"), 844 expectMatch: false, 845 }, 846 { 847 name: "match on loadbalancer type service", 848 in: &api.Service{ 849 Spec: api.ServiceSpec{Type: api.ServiceTypeLoadBalancer}, 850 }, 851 fieldSelector: fields.ParseSelectorOrDie("spec.type=LoadBalancer"), 852 expectMatch: true, 853 }, 854 { 855 name: "no match on nodeport type service", 856 in: &api.Service{ 857 Spec: api.ServiceSpec{Type: api.ServiceTypeNodePort}, 858 }, 859 fieldSelector: fields.ParseSelectorOrDie("spec.type=LoadBalancer"), 860 expectMatch: false, 861 }, 862 { 863 name: "match on headless service", 864 in: &api.Service{ 865 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 866 }, 867 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"), 868 expectMatch: true, 869 }, 870 { 871 name: "no match on clusterIP service", 872 in: &api.Service{ 873 Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, 874 }, 875 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"), 876 expectMatch: false, 877 }, 878 { 879 name: "match on clusterIP service", 880 in: &api.Service{ 881 Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, 882 }, 883 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=192.168.1.1"), 884 expectMatch: true, 885 }, 886 { 887 name: "match on non-headless service", 888 in: &api.Service{ 889 Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, 890 }, 891 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP!=None"), 892 expectMatch: true, 893 }, 894 { 895 name: "match on any ClusterIP set service", 896 in: &api.Service{ 897 Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, 898 }, 899 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP!=\"\""), 900 expectMatch: true, 901 }, 902 { 903 name: "match on clusterIP IPv6 service", 904 in: &api.Service{ 905 Spec: api.ServiceSpec{ClusterIP: "2001:db2::1"}, 906 }, 907 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=2001:db2::1"), 908 expectMatch: true, 909 }, 910 { 911 name: "no match on headless service", 912 in: &api.Service{ 913 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 914 }, 915 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=192.168.1.1"), 916 expectMatch: false, 917 }, 918 { 919 name: "no match on headless service", 920 in: &api.Service{ 921 Spec: api.ServiceSpec{ClusterIP: api.ClusterIPNone}, 922 }, 923 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=2001:db2::1"), 924 expectMatch: false, 925 }, 926 { 927 name: "no match on empty service", 928 in: &api.Service{}, 929 fieldSelector: fields.ParseSelectorOrDie("spec.clusterIP=None"), 930 expectMatch: false, 931 }, 932 } 933 for _, testCase := range testCases { 934 t.Run(testCase.name, func(t *testing.T) { 935 m := Matcher(labels.Everything(), testCase.fieldSelector) 936 result, err := m.Matches(testCase.in) 937 if err != nil { 938 t.Errorf("Unexpected error %v", err) 939 } 940 if result != testCase.expectMatch { 941 t.Errorf("Result %v, Expected %v, Selector: %v, Service: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in) 942 } 943 }) 944 } 945 }