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  }