istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/service_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/google/go-cmp/cmp/cmpopts"
    22  	fuzz "github.com/google/gofuzz"
    23  
    24  	"istio.io/istio/pkg/cluster"
    25  	"istio.io/istio/pkg/config/constants"
    26  	"istio.io/istio/pkg/config/host"
    27  	"istio.io/istio/pkg/config/labels"
    28  	"istio.io/istio/pkg/config/visibility"
    29  	"istio.io/istio/pkg/test/util/assert"
    30  )
    31  
    32  func TestGetByPort(t *testing.T) {
    33  	ports := PortList{{
    34  		Name: "http",
    35  		Port: 80,
    36  	}}
    37  
    38  	if port, exists := ports.GetByPort(80); !exists || port == nil || port.Name != "http" {
    39  		t.Errorf("GetByPort(80) => want http but got %v, %t", port, exists)
    40  	}
    41  	if port, exists := ports.GetByPort(88); exists || port != nil {
    42  		t.Errorf("GetByPort(88) => want none but got %v, %t", port, exists)
    43  	}
    44  }
    45  
    46  func BenchmarkParseSubsetKey(b *testing.B) {
    47  	for n := 0; n < b.N; n++ {
    48  		ParseSubsetKey("outbound|80|v1|example.com")
    49  		ParseSubsetKey("outbound_.8080_.v1_.foo.example.org")
    50  	}
    51  }
    52  
    53  func TestParseSubsetKey(t *testing.T) {
    54  	tests := []struct {
    55  		input      string
    56  		direction  TrafficDirection
    57  		subsetName string
    58  		hostname   host.Name
    59  		port       int
    60  	}{
    61  		{"outbound|80|v1|example.com", TrafficDirectionOutbound, "v1", "example.com", 80},
    62  		{"", "", "", "", 0},
    63  		{"|||", "", "", "", 0},
    64  		{"outbound_.8080_.v1_.foo.example.org", TrafficDirectionOutbound, "v1", "foo.example.org", 8080},
    65  		{"inbound_.8080_.v1_.foo.example.org", TrafficDirectionInbound, "v1", "foo.example.org", 8080},
    66  	}
    67  
    68  	for _, tt := range tests {
    69  		t.Run(tt.input, func(t *testing.T) {
    70  			d, s, h, p := ParseSubsetKey(tt.input)
    71  			if d != tt.direction {
    72  				t.Errorf("Expected direction %v got %v", tt.direction, d)
    73  			}
    74  			if s != tt.subsetName {
    75  				t.Errorf("Expected subset %v got %v", tt.subsetName, s)
    76  			}
    77  			if h != tt.hostname {
    78  				t.Errorf("Expected hostname %v got %v", tt.hostname, h)
    79  			}
    80  			if p != tt.port {
    81  				t.Errorf("Expected direction %v got %v", tt.port, p)
    82  			}
    83  		})
    84  	}
    85  }
    86  
    87  func TestIsValidSubsetKey(t *testing.T) {
    88  	cases := []struct {
    89  		subsetkey string
    90  		expectErr bool
    91  	}{
    92  		{
    93  			subsetkey: "outbound|80|subset|hostname",
    94  			expectErr: false,
    95  		},
    96  		{
    97  			subsetkey: "outbound|80||hostname",
    98  			expectErr: false,
    99  		},
   100  		{
   101  			subsetkey: "outbound|80|subset||hostname",
   102  			expectErr: true,
   103  		},
   104  		{
   105  			subsetkey: "",
   106  			expectErr: true,
   107  		},
   108  	}
   109  
   110  	for _, c := range cases {
   111  		err := IsValidSubsetKey(c.subsetkey)
   112  		if !err != c.expectErr {
   113  			t.Errorf("got %v but want %v\n", err, c.expectErr)
   114  		}
   115  	}
   116  }
   117  
   118  func TestWorkloadInstanceEqual(t *testing.T) {
   119  	exampleInstance := &WorkloadInstance{
   120  		Endpoint: &IstioEndpoint{
   121  			Labels:          labels.Instance{"app": "prod-app"},
   122  			Address:         "an-address",
   123  			ServicePortName: "service-port-name",
   124  			ServiceAccount:  "service-account",
   125  			Network:         "Network",
   126  			Locality: Locality{
   127  				ClusterID: "cluster-id",
   128  				Label:     "region1/zone1/subzone1",
   129  			},
   130  			EndpointPort: 22,
   131  			LbWeight:     100,
   132  			TLSMode:      "mutual",
   133  		},
   134  	}
   135  	differingAddr := exampleInstance.DeepCopy()
   136  	differingAddr.Endpoint.Address = "another-address"
   137  	differingNetwork := exampleInstance.DeepCopy()
   138  	differingNetwork.Endpoint.Network = "AnotherNetwork"
   139  	differingTLSMode := exampleInstance.DeepCopy()
   140  	differingTLSMode.Endpoint.TLSMode = "permitted"
   141  	differingLabels := exampleInstance.DeepCopy()
   142  	differingLabels.Endpoint.Labels = labels.Instance{
   143  		"app":         "prod-app",
   144  		"another-app": "blah",
   145  	}
   146  	differingServiceAccount := exampleInstance.DeepCopy()
   147  	differingServiceAccount.Endpoint.ServiceAccount = "service-account-two"
   148  	differingLocality := exampleInstance.DeepCopy()
   149  	differingLocality.Endpoint.Locality = Locality{
   150  		ClusterID: "cluster-id-two",
   151  		Label:     "region2/zone2/subzone2",
   152  	}
   153  	differingLbWeight := exampleInstance.DeepCopy()
   154  	differingLbWeight.Endpoint.LbWeight = 0
   155  
   156  	cases := []struct {
   157  		comparer *WorkloadInstance
   158  		comparee *WorkloadInstance
   159  		shouldEq bool
   160  		name     string
   161  	}{
   162  		{
   163  			comparer: &WorkloadInstance{},
   164  			comparee: &WorkloadInstance{},
   165  			shouldEq: true,
   166  			name:     "two null endpoints",
   167  		},
   168  		{
   169  			comparer: exampleInstance.DeepCopy(),
   170  			comparee: exampleInstance.DeepCopy(),
   171  			shouldEq: true,
   172  			name:     "exact same endpoints",
   173  		},
   174  		{
   175  			comparer: exampleInstance.DeepCopy(),
   176  			comparee: differingAddr.DeepCopy(),
   177  			shouldEq: false,
   178  			name:     "different Addresses",
   179  		},
   180  		{
   181  			comparer: exampleInstance.DeepCopy(),
   182  			comparee: differingNetwork.DeepCopy(),
   183  			shouldEq: false,
   184  			name:     "different Network",
   185  		},
   186  		{
   187  			comparer: exampleInstance.DeepCopy(),
   188  			comparee: differingTLSMode.DeepCopy(),
   189  			shouldEq: false,
   190  			name:     "different TLS Mode",
   191  		},
   192  		{
   193  			comparer: exampleInstance.DeepCopy(),
   194  			comparee: differingLabels.DeepCopy(),
   195  			shouldEq: false,
   196  			name:     "different Labels",
   197  		},
   198  		{
   199  			comparer: exampleInstance.DeepCopy(),
   200  			comparee: differingServiceAccount.DeepCopy(),
   201  			shouldEq: false,
   202  			name:     "different Service Account",
   203  		},
   204  		{
   205  			comparer: exampleInstance.DeepCopy(),
   206  			comparee: differingLocality.DeepCopy(),
   207  			shouldEq: false,
   208  			name:     "different Locality",
   209  		},
   210  		{
   211  			comparer: exampleInstance.DeepCopy(),
   212  			comparee: differingLbWeight.DeepCopy(),
   213  			shouldEq: false,
   214  			name:     "different LbWeight",
   215  		},
   216  	}
   217  
   218  	for _, testCase := range cases {
   219  		t.Run("WorkloadInstancesEqual: "+testCase.name, func(t *testing.T) {
   220  			isEq := WorkloadInstancesEqual(testCase.comparer, testCase.comparee)
   221  			isEqReverse := WorkloadInstancesEqual(testCase.comparee, testCase.comparer)
   222  
   223  			if isEq != isEqReverse {
   224  				t.Errorf(
   225  					"returned different for reversing arguments for structs: %v , and %v",
   226  					testCase.comparer,
   227  					testCase.comparee,
   228  				)
   229  			}
   230  			if isEq != testCase.shouldEq {
   231  				t.Errorf(
   232  					"equality of %v , and %v do not equal expected %t",
   233  					testCase.comparer,
   234  					testCase.comparee,
   235  					testCase.shouldEq,
   236  				)
   237  			}
   238  		})
   239  	}
   240  }
   241  
   242  func TestServicesEqual(t *testing.T) {
   243  	cases := []struct {
   244  		first    *Service
   245  		other    *Service
   246  		shouldEq bool
   247  		name     string
   248  	}{
   249  		{
   250  			first:    nil,
   251  			other:    &Service{},
   252  			shouldEq: false,
   253  			name:     "first nil services",
   254  		},
   255  		{
   256  			first:    &Service{},
   257  			other:    nil,
   258  			shouldEq: false,
   259  			name:     "other nil services",
   260  		},
   261  		{
   262  			first:    nil,
   263  			other:    nil,
   264  			shouldEq: true,
   265  			name:     "both nil services",
   266  		},
   267  		{
   268  			first:    &Service{},
   269  			other:    &Service{},
   270  			shouldEq: true,
   271  			name:     "two empty services",
   272  		},
   273  		{
   274  			first: &Service{
   275  				Ports: port7000,
   276  			},
   277  			other: &Service{
   278  				Ports: port7442,
   279  			},
   280  			shouldEq: false,
   281  			name:     "different ports",
   282  		},
   283  		{
   284  			first: &Service{
   285  				Ports: twoMatchingPorts,
   286  			},
   287  			other: &Service{
   288  				Ports: twoMatchingPorts,
   289  			},
   290  			shouldEq: true,
   291  			name:     "matching ports",
   292  		},
   293  		{
   294  			first: &Service{
   295  				ServiceAccounts: []string{"sa1"},
   296  			},
   297  			other: &Service{
   298  				ServiceAccounts: []string{"sa1"},
   299  			},
   300  			shouldEq: true,
   301  			name:     "matching service accounts",
   302  		},
   303  		{
   304  			first: &Service{
   305  				ServiceAccounts: []string{"sa1"},
   306  			},
   307  			other: &Service{
   308  				ServiceAccounts: []string{"sa2"},
   309  			},
   310  			shouldEq: false,
   311  			name:     "different service accounts",
   312  		},
   313  		{
   314  			first: &Service{
   315  				ClusterVIPs: AddressMap{
   316  					Addresses: map[cluster.ID][]string{
   317  						"cluster-1": {"c1-vip1,c1-vip2"},
   318  						"cluster-2": {"c2-vip1,c2-vip2"},
   319  					},
   320  				},
   321  			},
   322  			other: &Service{
   323  				ClusterVIPs: AddressMap{
   324  					Addresses: map[cluster.ID][]string{
   325  						"cluster-1": {"c1-vip1,c1-vip2"},
   326  						"cluster-2": {"c2-vip1,c2-vip2"},
   327  					},
   328  				},
   329  			},
   330  			shouldEq: true,
   331  			name:     "matching cluster VIPs",
   332  		},
   333  		{
   334  			first: &Service{
   335  				ClusterVIPs: AddressMap{
   336  					Addresses: map[cluster.ID][]string{
   337  						"cluster-1": {"c1-vip1,c1-vip2"},
   338  					},
   339  				},
   340  			},
   341  			other: &Service{
   342  				ClusterVIPs: AddressMap{
   343  					Addresses: map[cluster.ID][]string{
   344  						"cluster-1": {"c1-vip1,c1-vip2"},
   345  						"cluster-2": {"c2-vip1,c2-vip2"},
   346  					},
   347  				},
   348  			},
   349  			shouldEq: false,
   350  			name:     "different cluster VIPs",
   351  		},
   352  		{
   353  			first: &Service{
   354  				Attributes: ServiceAttributes{
   355  					Name:      "test",
   356  					Namespace: "testns",
   357  					Labels: map[string]string{
   358  						"label-1": "value-1",
   359  					},
   360  				},
   361  			},
   362  			other: &Service{
   363  				Attributes: ServiceAttributes{
   364  					Name:      "test",
   365  					Namespace: "testns",
   366  					Labels: map[string]string{
   367  						"label-1": "value-1",
   368  					},
   369  				},
   370  			},
   371  			shouldEq: true,
   372  			name:     "same service attributes",
   373  		},
   374  		{
   375  			first: &Service{
   376  				Attributes: ServiceAttributes{
   377  					Name:      "test",
   378  					Namespace: "testns",
   379  					Labels: map[string]string{
   380  						"label-1": "value-1",
   381  					},
   382  				},
   383  			},
   384  			other: &Service{
   385  				Attributes: ServiceAttributes{
   386  					Name:      "test",
   387  					Namespace: "testns",
   388  					Labels: map[string]string{
   389  						"label-1": "value-2",
   390  					},
   391  				},
   392  			},
   393  			shouldEq: false,
   394  			name:     "different service attributes",
   395  		},
   396  		{
   397  			first: &Service{
   398  				ClusterVIPs: AddressMap{
   399  					Addresses: map[cluster.ID][]string{
   400  						"cluster-1": {"c1-vip1,c1-vip2"},
   401  					},
   402  				},
   403  				ServiceAccounts: []string{"sa-1", "sa-2"},
   404  				Ports:           twoMatchingPorts,
   405  				Attributes: ServiceAttributes{
   406  					Name:      "test",
   407  					Namespace: "testns",
   408  					Labels: map[string]string{
   409  						"label-1": "value-1",
   410  					},
   411  				},
   412  			},
   413  			other: &Service{
   414  				ClusterVIPs: AddressMap{
   415  					Addresses: map[cluster.ID][]string{
   416  						"cluster-1": {"c1-vip1,c1-vip2"},
   417  						"cluster-2": {"c2-vip1,c2-vip2"},
   418  					},
   419  				},
   420  				Ports:           twoMatchingPorts,
   421  				ServiceAccounts: []string{"sa-1", "sa-2"},
   422  				Attributes: ServiceAttributes{
   423  					Name:      "test",
   424  					Namespace: "testns",
   425  					Labels: map[string]string{
   426  						"label-1": "value-2",
   427  					},
   428  				},
   429  			},
   430  			shouldEq: false,
   431  			name:     "service with just label change",
   432  		},
   433  		{
   434  			first: &Service{
   435  				Attributes: ServiceAttributes{
   436  					K8sAttributes: K8sAttributes{
   437  						Type: "ClusterIP",
   438  					},
   439  				},
   440  			},
   441  			other: &Service{
   442  				Attributes: ServiceAttributes{
   443  					K8sAttributes: K8sAttributes{
   444  						Type: "NodePort",
   445  					},
   446  				},
   447  			},
   448  			shouldEq: false,
   449  			name:     "different types",
   450  		},
   451  		{
   452  			first: &Service{
   453  				Attributes: ServiceAttributes{
   454  					K8sAttributes: K8sAttributes{
   455  						ExternalName: "foo.com",
   456  					},
   457  				},
   458  			},
   459  			other: &Service{
   460  				Attributes: ServiceAttributes{
   461  					K8sAttributes: K8sAttributes{
   462  						ExternalName: "bar.com",
   463  					},
   464  				},
   465  			},
   466  			shouldEq: false,
   467  			name:     "different external names",
   468  		},
   469  		{
   470  			first: &Service{
   471  				Attributes: ServiceAttributes{
   472  					K8sAttributes: K8sAttributes{
   473  						NodeLocal: false,
   474  					},
   475  				},
   476  			},
   477  			other: &Service{
   478  				Attributes: ServiceAttributes{
   479  					K8sAttributes: K8sAttributes{
   480  						NodeLocal: true,
   481  					},
   482  				},
   483  			},
   484  			shouldEq: false,
   485  			name:     "different internal traffic policies",
   486  		},
   487  		{
   488  			first:    &Service{Hostname: host.Name("foo.com")},
   489  			other:    &Service{Hostname: host.Name("foo1.com")},
   490  			shouldEq: false,
   491  			name:     "different hostname",
   492  		},
   493  		{
   494  			first:    &Service{DefaultAddress: constants.UnspecifiedIPv6},
   495  			other:    &Service{DefaultAddress: constants.UnspecifiedIP},
   496  			shouldEq: false,
   497  			name:     "different default address",
   498  		},
   499  		{
   500  			first:    &Service{AutoAllocatedIPv4Address: "240.240.0.100"},
   501  			other:    &Service{AutoAllocatedIPv4Address: "240.240.0.101"},
   502  			shouldEq: false,
   503  			name:     "different auto allocated IPv4 addresses",
   504  		},
   505  		{
   506  			first:    &Service{AutoAllocatedIPv6Address: "2001:2::f0f0:e351"},
   507  			other:    &Service{AutoAllocatedIPv6Address: "2001:2::f0f1:e351"},
   508  			shouldEq: false,
   509  			name:     "different auto allocated IPv6 addresses",
   510  		},
   511  		{
   512  			first:    &Service{Resolution: ClientSideLB},
   513  			other:    &Service{Resolution: Passthrough},
   514  			shouldEq: false,
   515  			name:     "different resolution",
   516  		},
   517  		{
   518  			first:    &Service{MeshExternal: true},
   519  			other:    &Service{MeshExternal: false},
   520  			shouldEq: false,
   521  			name:     "different mesh external setting",
   522  		},
   523  	}
   524  
   525  	for _, testCase := range cases {
   526  		t.Run("ServicesEqual: "+testCase.name, func(t *testing.T) {
   527  			isEq := testCase.first.Equals(testCase.other)
   528  			if isEq != testCase.shouldEq {
   529  				t.Errorf(
   530  					"equality of %v , and %v are not equal expected %t",
   531  					testCase.first,
   532  					testCase.other,
   533  					testCase.shouldEq,
   534  				)
   535  			}
   536  		})
   537  	}
   538  }
   539  
   540  func BenchmarkBuildSubsetKey(b *testing.B) {
   541  	for n := 0; n < b.N; n++ {
   542  		_ = BuildSubsetKey(TrafficDirectionInbound, "v1", "someHost", 80)
   543  	}
   544  }
   545  
   546  func BenchmarkServiceDeepCopy(b *testing.B) {
   547  	svc1 := buildHTTPService("test.com", visibility.Public, "10.10.0.1", "default", 80, 8080, 9090, 9999)
   548  	svc1.ServiceAccounts = []string{"sa1"}
   549  	svc1.ClusterVIPs = AddressMap{
   550  		Addresses: map[cluster.ID][]string{
   551  			"cluster1": {"10.10.0.1"},
   552  			"cluster2": {"10.10.0.2"},
   553  		},
   554  	}
   555  	for n := 0; n < b.N; n++ {
   556  		_ = svc1.DeepCopy()
   557  	}
   558  }
   559  
   560  func TestFuzzServiceDeepCopy(t *testing.T) {
   561  	fuzzer := fuzz.New()
   562  	originalSvc := &Service{}
   563  	fuzzer.Fuzz(originalSvc)
   564  	copied := originalSvc.DeepCopy()
   565  	opts := []cmp.Option{cmp.AllowUnexported(), cmpopts.IgnoreFields(AddressMap{}, "mutex")}
   566  	if !cmp.Equal(originalSvc, copied, opts...) {
   567  		diff := cmp.Diff(originalSvc, copied, opts...)
   568  		t.Errorf("unexpected diff %v", diff)
   569  	}
   570  }
   571  
   572  func TestParseSubsetKeyHostname(t *testing.T) {
   573  	tests := []struct {
   574  		in, out string
   575  	}{
   576  		{"outbound|80|subset|host.com", "host.com"},
   577  		{"outbound|80|subset|", ""},
   578  		{"|||", ""},
   579  		{"||||||", ""},
   580  		{"", ""},
   581  		{"outbound_.80_._.test.local", "test.local"},
   582  	}
   583  	for _, tt := range tests {
   584  		t.Run(tt.in, func(t *testing.T) {
   585  			assert.Equal(t, ParseSubsetKeyHostname(tt.in), tt.out)
   586  		})
   587  	}
   588  }