...

Source file src/k8s.io/kubernetes/pkg/registry/core/service/strategy_test.go

Documentation: k8s.io/kubernetes/pkg/registry/core/service

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

View as plain text