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