istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/util/util_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 util
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    23  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    24  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    25  	statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3"
    26  	cookiev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3"
    27  	httpv3 "github.com/envoyproxy/go-control-plane/envoy/type/http/v3"
    28  	"github.com/google/go-cmp/cmp"
    29  	"google.golang.org/protobuf/testing/protocmp"
    30  	structpb "google.golang.org/protobuf/types/known/structpb"
    31  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    32  
    33  	networking "istio.io/api/networking/v1alpha3"
    34  	"istio.io/istio/pilot/pkg/features"
    35  	"istio.io/istio/pilot/pkg/model"
    36  	"istio.io/istio/pilot/pkg/util/protoconv"
    37  	"istio.io/istio/pkg/config"
    38  	"istio.io/istio/pkg/config/labels"
    39  	"istio.io/istio/pkg/config/schema/gvk"
    40  	"istio.io/istio/pkg/test"
    41  	"istio.io/istio/pkg/test/util/assert"
    42  	xdsutil "istio.io/istio/pkg/wellknown"
    43  )
    44  
    45  var testCla = &endpoint.ClusterLoadAssignment{
    46  	ClusterName: "cluster",
    47  	Endpoints: []*endpoint.LocalityLbEndpoints{{
    48  		Locality: &core.Locality{Region: "foo", Zone: "bar"},
    49  		LbEndpoints: []*endpoint.LbEndpoint{
    50  			{
    51  				HostIdentifier:      &endpoint.LbEndpoint_Endpoint{Endpoint: &endpoint.Endpoint{Hostname: "foo", Address: BuildAddress("1.1.1.1", 80)}},
    52  				LoadBalancingWeight: &wrappers.UInt32Value{Value: 100},
    53  			},
    54  			{
    55  				HostIdentifier:      &endpoint.LbEndpoint_Endpoint{Endpoint: &endpoint.Endpoint{Hostname: "foo", Address: BuildAddress("1.1.1.1", 80)}},
    56  				LoadBalancingWeight: &wrappers.UInt32Value{Value: 100},
    57  			},
    58  		},
    59  		LoadBalancingWeight: &wrappers.UInt32Value{Value: 50},
    60  		Priority:            2,
    61  	}},
    62  }
    63  
    64  func BenchmarkCloneClusterLoadAssignment(b *testing.B) {
    65  	for i := 0; i < b.N; i++ {
    66  		cpy := CloneClusterLoadAssignment(testCla)
    67  		_ = cpy
    68  	}
    69  }
    70  
    71  func TestCloneClusterLoadAssignment(t *testing.T) {
    72  	cloned := CloneClusterLoadAssignment(testCla)
    73  	cloned2 := CloneClusterLoadAssignment(testCla)
    74  	if !cmp.Equal(testCla, cloned, protocmp.Transform()) {
    75  		t.Fatalf("expected %v to be the same as %v", testCla, cloned)
    76  	}
    77  	cloned.ClusterName = "foo"
    78  	cloned.Endpoints[0].LbEndpoints[0].LoadBalancingWeight.Value = 5
    79  	if cmp.Equal(testCla, cloned, protocmp.Transform()) {
    80  		t.Fatalf("expected %v to be the different from %v", testCla, cloned)
    81  	}
    82  	if !cmp.Equal(testCla, cloned2, protocmp.Transform()) {
    83  		t.Fatalf("expected %v to be the same as %v", testCla, cloned)
    84  	}
    85  }
    86  
    87  func TestConvertAddressToCidr(t *testing.T) {
    88  	tests := []struct {
    89  		name string
    90  		addr string
    91  		want *core.CidrRange
    92  	}{
    93  		{
    94  			"return nil when the address is empty",
    95  			"",
    96  			nil,
    97  		},
    98  		{
    99  			"cidr with two /",
   100  			"192.168.0.0//16",
   101  			nil,
   102  		},
   103  		{
   104  			"cidr with invalid prefix length",
   105  			"192.168.0.0/ab",
   106  			nil,
   107  		},
   108  		{
   109  			"cidr with negative prefix length",
   110  			"192.168.0.0/-16",
   111  			nil,
   112  		},
   113  		{
   114  			"invalid ip address",
   115  			"19216800",
   116  			nil,
   117  		},
   118  		{
   119  			"valid ipv6 address",
   120  			"2001:abcd:85a3::8a2e:370:1234",
   121  			&core.CidrRange{
   122  				AddressPrefix: "2001:abcd:85a3::8a2e:370:1234",
   123  				PrefixLen:     &wrappers.UInt32Value{Value: 128},
   124  			},
   125  		},
   126  		{
   127  			"success case with no PrefixLen",
   128  			"1.2.3.4",
   129  			&core.CidrRange{
   130  				AddressPrefix: "1.2.3.4",
   131  				PrefixLen: &wrappers.UInt32Value{
   132  					Value: 32,
   133  				},
   134  			},
   135  		},
   136  		{
   137  			"success case with PrefixLen",
   138  			"1.2.3.4/16",
   139  			&core.CidrRange{
   140  				AddressPrefix: "1.2.3.4",
   141  				PrefixLen: &wrappers.UInt32Value{
   142  					Value: 16,
   143  				},
   144  			},
   145  		},
   146  		{
   147  			"ipv6",
   148  			"2001:db8::",
   149  			&core.CidrRange{
   150  				AddressPrefix: "2001:db8::",
   151  				PrefixLen: &wrappers.UInt32Value{
   152  					Value: 128,
   153  				},
   154  			},
   155  		},
   156  		{
   157  			"ipv6 with prefix",
   158  			"2001:db8::/64",
   159  			&core.CidrRange{
   160  				AddressPrefix: "2001:db8::",
   161  				PrefixLen: &wrappers.UInt32Value{
   162  					Value: 64,
   163  				},
   164  			},
   165  		},
   166  	}
   167  	for _, tt := range tests {
   168  		t.Run(tt.name, func(t *testing.T) {
   169  			if got := ConvertAddressToCidr(tt.addr); !reflect.DeepEqual(got, tt.want) {
   170  				t.Errorf("ConvertAddressToCidr() = %v, want %v", got, tt.want)
   171  			}
   172  		})
   173  	}
   174  }
   175  
   176  func TestConvertLocality(t *testing.T) {
   177  	tests := []struct {
   178  		name     string
   179  		locality string
   180  		want     *core.Locality
   181  		reverse  string
   182  	}{
   183  		{
   184  			name:     "nil locality",
   185  			locality: "",
   186  			want:     &core.Locality{},
   187  		},
   188  		{
   189  			name:     "locality with only region",
   190  			locality: "region",
   191  			want: &core.Locality{
   192  				Region: "region",
   193  			},
   194  		},
   195  		{
   196  			name:     "locality with region and zone",
   197  			locality: "region/zone",
   198  			want: &core.Locality{
   199  				Region: "region",
   200  				Zone:   "zone",
   201  			},
   202  		},
   203  		{
   204  			name:     "locality with region zone and subzone",
   205  			locality: "region/zone/subzone",
   206  			want: &core.Locality{
   207  				Region:  "region",
   208  				Zone:    "zone",
   209  				SubZone: "subzone",
   210  			},
   211  		},
   212  		{
   213  			name:     "locality with region zone subzone and rack",
   214  			locality: "region/zone/subzone/rack",
   215  			want: &core.Locality{
   216  				Region:  "region",
   217  				Zone:    "zone",
   218  				SubZone: "subzone",
   219  			},
   220  			reverse: "region/zone/subzone",
   221  		},
   222  	}
   223  
   224  	for _, tt := range tests {
   225  		t.Run(tt.name, func(t *testing.T) {
   226  			got := ConvertLocality(tt.locality)
   227  			if !reflect.DeepEqual(got, tt.want) {
   228  				t.Errorf("Expected locality %#v, but got %#v", tt.want, got)
   229  			}
   230  			// Verify we can reverse the conversion back to the original input
   231  			reverse := LocalityToString(got)
   232  			if tt.reverse != "" {
   233  				// Special case, reverse lookup is different than original input
   234  				if tt.reverse != reverse {
   235  					t.Errorf("Expected locality string %s, got %v", tt.reverse, reverse)
   236  				}
   237  			} else if tt.locality != reverse {
   238  				t.Errorf("Expected locality string %s, got %v", tt.locality, reverse)
   239  			}
   240  		})
   241  	}
   242  }
   243  
   244  func TestLocalityMatch(t *testing.T) {
   245  	tests := []struct {
   246  		name     string
   247  		locality *core.Locality
   248  		rule     string
   249  		match    bool
   250  	}{
   251  		{
   252  			name: "wildcard matching",
   253  			locality: &core.Locality{
   254  				Region:  "region1",
   255  				Zone:    "zone1",
   256  				SubZone: "subzone1",
   257  			},
   258  			rule:  "*",
   259  			match: true,
   260  		},
   261  		{
   262  			name: "wildcard matching",
   263  			locality: &core.Locality{
   264  				Region:  "region1",
   265  				Zone:    "zone1",
   266  				SubZone: "subzone1",
   267  			},
   268  			rule:  "region1/*",
   269  			match: true,
   270  		},
   271  		{
   272  			name: "wildcard matching",
   273  			locality: &core.Locality{
   274  				Region:  "region1",
   275  				Zone:    "zone1",
   276  				SubZone: "subzone1",
   277  			},
   278  			rule:  "region1/zone1/*",
   279  			match: true,
   280  		},
   281  		{
   282  			name: "wildcard not matching",
   283  			locality: &core.Locality{
   284  				Region:  "region1",
   285  				Zone:    "zone1",
   286  				SubZone: "subzone1",
   287  			},
   288  			rule:  "region1/zone2/*",
   289  			match: false,
   290  		},
   291  		{
   292  			name: "region matching",
   293  			locality: &core.Locality{
   294  				Region:  "region1",
   295  				Zone:    "zone1",
   296  				SubZone: "subzone1",
   297  			},
   298  			rule:  "region1",
   299  			match: true,
   300  		},
   301  		{
   302  			name: "region and zone matching",
   303  			locality: &core.Locality{
   304  				Region:  "region1",
   305  				Zone:    "zone1",
   306  				SubZone: "subzone1",
   307  			},
   308  			rule:  "region1/zone1",
   309  			match: true,
   310  		},
   311  		{
   312  			name: "zubzone wildcard matching",
   313  			locality: &core.Locality{
   314  				Region: "region1",
   315  				Zone:   "zone1",
   316  			},
   317  			rule:  "region1/zone1",
   318  			match: true,
   319  		},
   320  		{
   321  			name: "subzone mismatching",
   322  			locality: &core.Locality{
   323  				Region: "region1",
   324  				Zone:   "zone1",
   325  			},
   326  			rule:  "region1/zone1/subzone2",
   327  			match: false,
   328  		},
   329  	}
   330  
   331  	for _, tt := range tests {
   332  		t.Run(tt.name, func(t *testing.T) {
   333  			match := LocalityMatch(tt.locality, tt.rule)
   334  			if match != tt.match {
   335  				t.Errorf("Expected matching result %v, but got %v", tt.match, match)
   336  			}
   337  		})
   338  	}
   339  }
   340  
   341  func TestIsLocalityEmpty(t *testing.T) {
   342  	tests := []struct {
   343  		name     string
   344  		locality *core.Locality
   345  		want     bool
   346  	}{
   347  		{
   348  			"non empty locality",
   349  			&core.Locality{
   350  				Region: "region",
   351  			},
   352  			false,
   353  		},
   354  		{
   355  			"empty locality",
   356  			&core.Locality{
   357  				Region: "",
   358  			},
   359  			true,
   360  		},
   361  		{
   362  			"nil locality",
   363  			nil,
   364  			true,
   365  		},
   366  	}
   367  
   368  	for _, tt := range tests {
   369  		t.Run(tt.name, func(t *testing.T) {
   370  			got := IsLocalityEmpty(tt.locality)
   371  			if !reflect.DeepEqual(got, tt.want) {
   372  				t.Errorf("Expected locality empty result %#v, but got %#v", tt.want, got)
   373  			}
   374  		})
   375  	}
   376  }
   377  
   378  func TestBuildConfigInfoMetadata(t *testing.T) {
   379  	cases := []struct {
   380  		name string
   381  		in   config.Meta
   382  		want *core.Metadata
   383  	}{
   384  		{
   385  			"destination-rule",
   386  			config.Meta{
   387  				Name:             "svcA",
   388  				Namespace:        "default",
   389  				Domain:           "svc.cluster.local",
   390  				GroupVersionKind: gvk.DestinationRule,
   391  			},
   392  			&core.Metadata{
   393  				FilterMetadata: map[string]*structpb.Struct{
   394  					IstioMetadataKey: {
   395  						Fields: map[string]*structpb.Value{
   396  							"config": {
   397  								Kind: &structpb.Value_StringValue{
   398  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   399  								},
   400  							},
   401  						},
   402  					},
   403  				},
   404  			},
   405  		},
   406  	}
   407  
   408  	for _, v := range cases {
   409  		t.Run(v.name, func(tt *testing.T) {
   410  			got := BuildConfigInfoMetadata(v.in)
   411  			if diff := cmp.Diff(got, v.want, protocmp.Transform()); diff != "" {
   412  				tt.Errorf("BuildConfigInfoMetadata(%v) produced incorrect result:\ngot: %v\nwant: %v\nDiff: %s", v.in, got, v.want, diff)
   413  			}
   414  		})
   415  	}
   416  }
   417  
   418  func TestAddConfigInfoMetadata(t *testing.T) {
   419  	cases := []struct {
   420  		name string
   421  		in   config.Meta
   422  		meta *core.Metadata
   423  		want *core.Metadata
   424  	}{
   425  		{
   426  			"nil metadata",
   427  			config.Meta{
   428  				Name:             "svcA",
   429  				Namespace:        "default",
   430  				Domain:           "svc.cluster.local",
   431  				GroupVersionKind: gvk.DestinationRule,
   432  			},
   433  			nil,
   434  			&core.Metadata{
   435  				FilterMetadata: map[string]*structpb.Struct{
   436  					IstioMetadataKey: {
   437  						Fields: map[string]*structpb.Value{
   438  							"config": {
   439  								Kind: &structpb.Value_StringValue{
   440  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   441  								},
   442  							},
   443  						},
   444  					},
   445  				},
   446  			},
   447  		},
   448  		{
   449  			"empty metadata",
   450  			config.Meta{
   451  				Name:             "svcA",
   452  				Namespace:        "default",
   453  				Domain:           "svc.cluster.local",
   454  				GroupVersionKind: gvk.DestinationRule,
   455  			},
   456  			&core.Metadata{
   457  				FilterMetadata: map[string]*structpb.Struct{},
   458  			},
   459  			&core.Metadata{
   460  				FilterMetadata: map[string]*structpb.Struct{
   461  					IstioMetadataKey: {
   462  						Fields: map[string]*structpb.Value{
   463  							"config": {
   464  								Kind: &structpb.Value_StringValue{
   465  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   466  								},
   467  							},
   468  						},
   469  					},
   470  				},
   471  			},
   472  		},
   473  		{
   474  			"existing istio metadata",
   475  			config.Meta{
   476  				Name:             "svcA",
   477  				Namespace:        "default",
   478  				Domain:           "svc.cluster.local",
   479  				GroupVersionKind: gvk.DestinationRule,
   480  			},
   481  			&core.Metadata{
   482  				FilterMetadata: map[string]*structpb.Struct{
   483  					IstioMetadataKey: {
   484  						Fields: map[string]*structpb.Value{
   485  							"other-config": {
   486  								Kind: &structpb.Value_StringValue{
   487  									StringValue: "other-config",
   488  								},
   489  							},
   490  						},
   491  					},
   492  				},
   493  			},
   494  			&core.Metadata{
   495  				FilterMetadata: map[string]*structpb.Struct{
   496  					IstioMetadataKey: {
   497  						Fields: map[string]*structpb.Value{
   498  							"other-config": {
   499  								Kind: &structpb.Value_StringValue{
   500  									StringValue: "other-config",
   501  								},
   502  							},
   503  							"config": {
   504  								Kind: &structpb.Value_StringValue{
   505  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   506  								},
   507  							},
   508  						},
   509  					},
   510  				},
   511  			},
   512  		},
   513  		{
   514  			"existing non-istio metadata",
   515  			config.Meta{
   516  				Name:             "svcA",
   517  				Namespace:        "default",
   518  				Domain:           "svc.cluster.local",
   519  				GroupVersionKind: gvk.DestinationRule,
   520  			},
   521  			&core.Metadata{
   522  				FilterMetadata: map[string]*structpb.Struct{
   523  					"other-metadata": {
   524  						Fields: map[string]*structpb.Value{
   525  							"other-config": {
   526  								Kind: &structpb.Value_StringValue{
   527  									StringValue: "other-config",
   528  								},
   529  							},
   530  						},
   531  					},
   532  				},
   533  			},
   534  			&core.Metadata{
   535  				FilterMetadata: map[string]*structpb.Struct{
   536  					"other-metadata": {
   537  						Fields: map[string]*structpb.Value{
   538  							"other-config": {
   539  								Kind: &structpb.Value_StringValue{
   540  									StringValue: "other-config",
   541  								},
   542  							},
   543  						},
   544  					},
   545  					IstioMetadataKey: {
   546  						Fields: map[string]*structpb.Value{
   547  							"config": {
   548  								Kind: &structpb.Value_StringValue{
   549  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   550  								},
   551  							},
   552  						},
   553  					},
   554  				},
   555  			},
   556  		},
   557  	}
   558  
   559  	for _, v := range cases {
   560  		t.Run(v.name, func(tt *testing.T) {
   561  			got := AddConfigInfoMetadata(v.meta, v.in)
   562  			if diff := cmp.Diff(got, v.want, protocmp.Transform()); diff != "" {
   563  				tt.Errorf("AddConfigInfoMetadata(%v) produced incorrect result:\ngot: %v\nwant: %v\nDiff: %s", v.in, got, v.want, diff)
   564  			}
   565  		})
   566  	}
   567  }
   568  
   569  func TestAddSubsetToMetadata(t *testing.T) {
   570  	cases := []struct {
   571  		name   string
   572  		in     *core.Metadata
   573  		subset string
   574  		want   *core.Metadata
   575  	}{
   576  		{
   577  			"simple subset",
   578  			&core.Metadata{
   579  				FilterMetadata: map[string]*structpb.Struct{
   580  					IstioMetadataKey: {
   581  						Fields: map[string]*structpb.Value{
   582  							"config": {
   583  								Kind: &structpb.Value_StringValue{
   584  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   585  								},
   586  							},
   587  						},
   588  					},
   589  				},
   590  			},
   591  			"test-subset",
   592  			&core.Metadata{
   593  				FilterMetadata: map[string]*structpb.Struct{
   594  					IstioMetadataKey: {
   595  						Fields: map[string]*structpb.Value{
   596  							"config": {
   597  								Kind: &structpb.Value_StringValue{
   598  									StringValue: "/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/svcA",
   599  								},
   600  							},
   601  							"subset": {
   602  								Kind: &structpb.Value_StringValue{
   603  									StringValue: "test-subset",
   604  								},
   605  							},
   606  						},
   607  					},
   608  				},
   609  			},
   610  		},
   611  		{
   612  			"no metadata",
   613  			&core.Metadata{},
   614  			"test-subset",
   615  			&core.Metadata{},
   616  		},
   617  	}
   618  
   619  	for _, v := range cases {
   620  		t.Run(v.name, func(tt *testing.T) {
   621  			AddSubsetToMetadata(v.in, v.subset)
   622  			got := v.in
   623  			if diff := cmp.Diff(got, v.want, protocmp.Transform()); diff != "" {
   624  				tt.Errorf("AddSubsetToMetadata(%v, %s) produced incorrect result:\ngot: %v\nwant: %v\nDiff: %s", v.in, v.subset, got, v.want, diff)
   625  			}
   626  		})
   627  	}
   628  }
   629  
   630  func TestAddALPNOverrideToMetadata(t *testing.T) {
   631  	alpnOverrideFalse := &core.Metadata{
   632  		FilterMetadata: map[string]*structpb.Struct{
   633  			IstioMetadataKey: {
   634  				Fields: map[string]*structpb.Value{
   635  					AlpnOverrideMetadataKey: {
   636  						Kind: &structpb.Value_StringValue{
   637  							StringValue: "false",
   638  						},
   639  					},
   640  				},
   641  			},
   642  		},
   643  	}
   644  
   645  	cases := []struct {
   646  		name    string
   647  		tlsMode networking.ClientTLSSettings_TLSmode
   648  		meta    *core.Metadata
   649  		want    *core.Metadata
   650  	}{
   651  		{
   652  			name:    "ISTIO_MUTUAL TLS",
   653  			tlsMode: networking.ClientTLSSettings_ISTIO_MUTUAL,
   654  			meta:    nil,
   655  			want:    nil,
   656  		},
   657  		{
   658  			name:    "DISABLED TLS",
   659  			tlsMode: networking.ClientTLSSettings_DISABLE,
   660  			meta:    nil,
   661  			want:    nil,
   662  		},
   663  		{
   664  			name:    "SIMPLE TLS and nil metadata",
   665  			tlsMode: networking.ClientTLSSettings_SIMPLE,
   666  			meta:    nil,
   667  			want:    alpnOverrideFalse,
   668  		},
   669  		{
   670  			name:    "MUTUAL TLS and nil metadata",
   671  			tlsMode: networking.ClientTLSSettings_SIMPLE,
   672  			meta:    nil,
   673  			want:    alpnOverrideFalse,
   674  		},
   675  		{
   676  			name:    "SIMPLE TLS and empty metadata",
   677  			tlsMode: networking.ClientTLSSettings_SIMPLE,
   678  			meta: &core.Metadata{
   679  				FilterMetadata: map[string]*structpb.Struct{},
   680  			},
   681  			want: alpnOverrideFalse,
   682  		},
   683  		{
   684  			name:    "SIMPLE TLS and existing istio metadata",
   685  			tlsMode: networking.ClientTLSSettings_SIMPLE,
   686  			meta: &core.Metadata{
   687  				FilterMetadata: map[string]*structpb.Struct{
   688  					IstioMetadataKey: {
   689  						Fields: map[string]*structpb.Value{
   690  							"other-config": {
   691  								Kind: &structpb.Value_StringValue{
   692  									StringValue: "other-config",
   693  								},
   694  							},
   695  						},
   696  					},
   697  				},
   698  			},
   699  			want: &core.Metadata{
   700  				FilterMetadata: map[string]*structpb.Struct{
   701  					IstioMetadataKey: {
   702  						Fields: map[string]*structpb.Value{
   703  							"other-config": {
   704  								Kind: &structpb.Value_StringValue{
   705  									StringValue: "other-config",
   706  								},
   707  							},
   708  							AlpnOverrideMetadataKey: {
   709  								Kind: &structpb.Value_StringValue{
   710  									StringValue: "false",
   711  								},
   712  							},
   713  						},
   714  					},
   715  				},
   716  			},
   717  		},
   718  		{
   719  			name:    "SIMPLE TLS and existing non-istio metadata",
   720  			tlsMode: networking.ClientTLSSettings_SIMPLE,
   721  			meta: &core.Metadata{
   722  				FilterMetadata: map[string]*structpb.Struct{
   723  					"other-metadata": {
   724  						Fields: map[string]*structpb.Value{
   725  							"other-config": {
   726  								Kind: &structpb.Value_StringValue{
   727  									StringValue: "other-config",
   728  								},
   729  							},
   730  						},
   731  					},
   732  				},
   733  			},
   734  			want: &core.Metadata{
   735  				FilterMetadata: map[string]*structpb.Struct{
   736  					"other-metadata": {
   737  						Fields: map[string]*structpb.Value{
   738  							"other-config": {
   739  								Kind: &structpb.Value_StringValue{
   740  									StringValue: "other-config",
   741  								},
   742  							},
   743  						},
   744  					},
   745  					IstioMetadataKey: {
   746  						Fields: map[string]*structpb.Value{
   747  							AlpnOverrideMetadataKey: {
   748  								Kind: &structpb.Value_StringValue{
   749  									StringValue: "false",
   750  								},
   751  							},
   752  						},
   753  					},
   754  				},
   755  			},
   756  		},
   757  	}
   758  
   759  	for _, v := range cases {
   760  		t.Run(v.name, func(tt *testing.T) {
   761  			got := AddALPNOverrideToMetadata(v.meta, v.tlsMode)
   762  			if diff := cmp.Diff(got, v.want, protocmp.Transform()); diff != "" {
   763  				tt.Errorf("AddALPNOverrideToMetadata produced incorrect result:\ngot: %v\nwant: %v\nDiff: %s", got, v.want, diff)
   764  			}
   765  		})
   766  	}
   767  }
   768  
   769  func TestIsHTTPFilterChain(t *testing.T) {
   770  	httpFilterChain := &listener.FilterChain{
   771  		Filters: []*listener.Filter{
   772  			{
   773  				Name: xdsutil.HTTPConnectionManager,
   774  			},
   775  		},
   776  	}
   777  
   778  	tcpFilterChain := &listener.FilterChain{
   779  		Filters: []*listener.Filter{
   780  			{
   781  				Name: xdsutil.TCPProxy,
   782  			},
   783  		},
   784  	}
   785  
   786  	if !IsHTTPFilterChain(httpFilterChain) {
   787  		t.Errorf("http Filter chain not detected properly")
   788  	}
   789  
   790  	if IsHTTPFilterChain(tcpFilterChain) {
   791  		t.Errorf("tcp filter chain detected as http filter chain")
   792  	}
   793  }
   794  
   795  func TestIsAllowAnyOutbound(t *testing.T) {
   796  	tests := []struct {
   797  		name   string
   798  		node   *model.Proxy
   799  		result bool
   800  	}{
   801  		{
   802  			name:   "NilSidecarScope",
   803  			node:   &model.Proxy{},
   804  			result: false,
   805  		},
   806  		{
   807  			name: "NilOutboundTrafficPolicy",
   808  			node: &model.Proxy{
   809  				SidecarScope: &model.SidecarScope{},
   810  			},
   811  			result: false,
   812  		},
   813  		{
   814  			name: "OutboundTrafficPolicyRegistryOnly",
   815  			node: &model.Proxy{
   816  				SidecarScope: &model.SidecarScope{
   817  					OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
   818  						Mode: networking.OutboundTrafficPolicy_REGISTRY_ONLY,
   819  					},
   820  				},
   821  			},
   822  			result: false,
   823  		},
   824  		{
   825  			name: "OutboundTrafficPolicyAllowAny",
   826  			node: &model.Proxy{
   827  				SidecarScope: &model.SidecarScope{
   828  					OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
   829  						Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
   830  					},
   831  				},
   832  			},
   833  			result: true,
   834  		},
   835  	}
   836  	for i := range tests {
   837  		t.Run(tests[i].name, func(t *testing.T) {
   838  			out := IsAllowAnyOutbound(tests[i].node)
   839  			if out != tests[i].result {
   840  				t.Errorf("Expected %t but got %t for test case: %v\n", tests[i].result, out, tests[i].node)
   841  			}
   842  		})
   843  	}
   844  }
   845  
   846  func TestBuildAddress(t *testing.T) {
   847  	testCases := []struct {
   848  		name     string
   849  		addr     string
   850  		port     uint32
   851  		expected *core.Address
   852  	}{
   853  		{
   854  			name: "ipv4",
   855  			addr: "172.10.10.1",
   856  			port: 8080,
   857  			expected: &core.Address{
   858  				Address: &core.Address_SocketAddress{
   859  					SocketAddress: &core.SocketAddress{
   860  						Address: "172.10.10.1",
   861  						PortSpecifier: &core.SocketAddress_PortValue{
   862  							PortValue: 8080,
   863  						},
   864  					},
   865  				},
   866  			},
   867  		},
   868  		{
   869  			name: "ipv6",
   870  			addr: "fe80::10e7:52ff:fecd:198b",
   871  			port: 8080,
   872  			expected: &core.Address{
   873  				Address: &core.Address_SocketAddress{
   874  					SocketAddress: &core.SocketAddress{
   875  						Address: "fe80::10e7:52ff:fecd:198b",
   876  						PortSpecifier: &core.SocketAddress_PortValue{
   877  							PortValue: 8080,
   878  						},
   879  					},
   880  				},
   881  			},
   882  		},
   883  		{
   884  			name: "uds",
   885  			addr: "/var/run/test/socket",
   886  			port: 0,
   887  			expected: &core.Address{
   888  				Address: &core.Address_Pipe{
   889  					Pipe: &core.Pipe{
   890  						Path: "/var/run/test/socket",
   891  					},
   892  				},
   893  			},
   894  		},
   895  		{
   896  			name: "uds with unix prefix",
   897  			addr: "unix:///var/run/test/socket",
   898  			port: 0,
   899  			expected: &core.Address{
   900  				Address: &core.Address_Pipe{
   901  					Pipe: &core.Pipe{
   902  						Path: "/var/run/test/socket",
   903  					},
   904  				},
   905  			},
   906  		},
   907  	}
   908  
   909  	for _, test := range testCases {
   910  		t.Run(test.name, func(t *testing.T) {
   911  			addr := BuildAddress(test.addr, test.port)
   912  			if !reflect.DeepEqual(addr, test.expected) {
   913  				t.Errorf("expected add %v, but got %v", test.expected, addr)
   914  			}
   915  		})
   916  	}
   917  }
   918  
   919  func TestCidrRangeSliceEqual(t *testing.T) {
   920  	tests := []struct {
   921  		name   string
   922  		first  []*core.CidrRange
   923  		second []*core.CidrRange
   924  		want   bool
   925  	}{
   926  		{
   927  			"both nil",
   928  			nil,
   929  			nil,
   930  			true,
   931  		},
   932  		{
   933  			"unequal length",
   934  			[]*core.CidrRange{
   935  				{
   936  					AddressPrefix: "1.2.3.4",
   937  					PrefixLen: &wrappers.UInt32Value{
   938  						Value: 32,
   939  					},
   940  				},
   941  				{
   942  					AddressPrefix: "1.2.3.5",
   943  					PrefixLen: &wrappers.UInt32Value{
   944  						Value: 32,
   945  					},
   946  				},
   947  			},
   948  			[]*core.CidrRange{
   949  				{
   950  					AddressPrefix: "1.2.3.4",
   951  					PrefixLen: &wrappers.UInt32Value{
   952  						Value: 32,
   953  					},
   954  				},
   955  			},
   956  			false,
   957  		},
   958  		{
   959  			"equal cidr",
   960  			[]*core.CidrRange{
   961  				{
   962  					AddressPrefix: "1.2.3.4",
   963  					PrefixLen: &wrappers.UInt32Value{
   964  						Value: 32,
   965  					},
   966  				},
   967  			},
   968  			[]*core.CidrRange{
   969  				{
   970  					AddressPrefix: "1.2.3.4",
   971  					PrefixLen: &wrappers.UInt32Value{
   972  						Value: 32,
   973  					},
   974  				},
   975  			},
   976  			true,
   977  		},
   978  		{
   979  			"equal cidr with different insignificant bits",
   980  			[]*core.CidrRange{
   981  				{
   982  					AddressPrefix: "1.2.3.4",
   983  					PrefixLen: &wrappers.UInt32Value{
   984  						Value: 24,
   985  					},
   986  				},
   987  			},
   988  			[]*core.CidrRange{
   989  				{
   990  					AddressPrefix: "1.2.3.5",
   991  					PrefixLen: &wrappers.UInt32Value{
   992  						Value: 24,
   993  					},
   994  				},
   995  			},
   996  			true,
   997  		},
   998  		{
   999  			"unequal cidr address prefix mismatch",
  1000  			[]*core.CidrRange{
  1001  				{
  1002  					AddressPrefix: "1.2.3.4",
  1003  					PrefixLen: &wrappers.UInt32Value{
  1004  						Value: 32,
  1005  					},
  1006  				},
  1007  			},
  1008  			[]*core.CidrRange{
  1009  				{
  1010  					AddressPrefix: "1.2.3.5",
  1011  					PrefixLen: &wrappers.UInt32Value{
  1012  						Value: 32,
  1013  					},
  1014  				},
  1015  			},
  1016  			false,
  1017  		},
  1018  		{
  1019  			"unequal cidr prefixlen mismatch",
  1020  			[]*core.CidrRange{
  1021  				{
  1022  					AddressPrefix: "1.2.3.4",
  1023  					PrefixLen: &wrappers.UInt32Value{
  1024  						Value: 32,
  1025  					},
  1026  				},
  1027  			},
  1028  			[]*core.CidrRange{
  1029  				{
  1030  					AddressPrefix: "1.2.3.4",
  1031  					PrefixLen: &wrappers.UInt32Value{
  1032  						Value: 16,
  1033  					},
  1034  				},
  1035  			},
  1036  			false,
  1037  		},
  1038  	}
  1039  	for _, tt := range tests {
  1040  		t.Run(tt.name, func(t *testing.T) {
  1041  			if got := CidrRangeSliceEqual(tt.first, tt.second); got != tt.want {
  1042  				t.Errorf("Unexpected CidrRangeSliceEqual() = %v, want %v", got, tt.want)
  1043  			}
  1044  		})
  1045  	}
  1046  }
  1047  
  1048  func TestEndpointMetadata(t *testing.T) {
  1049  	test.SetForTest(t, &features.EndpointTelemetryLabel, true)
  1050  	cases := []struct {
  1051  		name     string
  1052  		metadata *model.EndpointMetadata
  1053  		want     *core.Metadata
  1054  	}{
  1055  		{
  1056  			name: "all empty",
  1057  			metadata: &model.EndpointMetadata{
  1058  				TLSMode:      model.DisabledTLSModeLabel,
  1059  				Network:      "",
  1060  				WorkloadName: "",
  1061  				ClusterID:    "",
  1062  			},
  1063  			want: &core.Metadata{
  1064  				FilterMetadata: map[string]*structpb.Struct{
  1065  					IstioMetadataKey: {
  1066  						Fields: map[string]*structpb.Value{
  1067  							"workload": {
  1068  								Kind: &structpb.Value_StringValue{
  1069  									StringValue: ";;;;",
  1070  								},
  1071  							},
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  		},
  1077  		{
  1078  			name: "tls mode",
  1079  			metadata: &model.EndpointMetadata{
  1080  				TLSMode:      model.IstioMutualTLSModeLabel,
  1081  				Network:      "",
  1082  				WorkloadName: "",
  1083  				ClusterID:    "",
  1084  			},
  1085  			want: &core.Metadata{
  1086  				FilterMetadata: map[string]*structpb.Struct{
  1087  					EnvoyTransportSocketMetadataKey: {
  1088  						Fields: map[string]*structpb.Value{
  1089  							model.TLSModeLabelShortname: {
  1090  								Kind: &structpb.Value_StringValue{
  1091  									StringValue: model.IstioMutualTLSModeLabel,
  1092  								},
  1093  							},
  1094  						},
  1095  					},
  1096  					IstioMetadataKey: {
  1097  						Fields: map[string]*structpb.Value{
  1098  							"workload": {
  1099  								Kind: &structpb.Value_StringValue{
  1100  									StringValue: ";;;;",
  1101  								},
  1102  							},
  1103  						},
  1104  					},
  1105  				},
  1106  			},
  1107  		},
  1108  		{
  1109  			name: "network and tls mode",
  1110  			metadata: &model.EndpointMetadata{
  1111  				TLSMode:      model.IstioMutualTLSModeLabel,
  1112  				Network:      "network",
  1113  				WorkloadName: "",
  1114  				ClusterID:    "",
  1115  			},
  1116  			want: &core.Metadata{
  1117  				FilterMetadata: map[string]*structpb.Struct{
  1118  					EnvoyTransportSocketMetadataKey: {
  1119  						Fields: map[string]*structpb.Value{
  1120  							model.TLSModeLabelShortname: {
  1121  								Kind: &structpb.Value_StringValue{
  1122  									StringValue: model.IstioMutualTLSModeLabel,
  1123  								},
  1124  							},
  1125  						},
  1126  					},
  1127  					IstioMetadataKey: {
  1128  						Fields: map[string]*structpb.Value{
  1129  							"workload": {
  1130  								Kind: &structpb.Value_StringValue{
  1131  									StringValue: ";;;;",
  1132  								},
  1133  							},
  1134  						},
  1135  					},
  1136  				},
  1137  			},
  1138  		},
  1139  		{
  1140  			name: "all label",
  1141  			metadata: &model.EndpointMetadata{
  1142  				TLSMode:      model.IstioMutualTLSModeLabel,
  1143  				Network:      "network",
  1144  				WorkloadName: "workload",
  1145  				ClusterID:    "cluster",
  1146  				Namespace:    "default",
  1147  				Labels: labels.Instance{
  1148  					model.IstioCanonicalServiceLabelName:         "service",
  1149  					model.IstioCanonicalServiceRevisionLabelName: "v1",
  1150  				},
  1151  			},
  1152  			want: &core.Metadata{
  1153  				FilterMetadata: map[string]*structpb.Struct{
  1154  					EnvoyTransportSocketMetadataKey: {
  1155  						Fields: map[string]*structpb.Value{
  1156  							model.TLSModeLabelShortname: {
  1157  								Kind: &structpb.Value_StringValue{
  1158  									StringValue: model.IstioMutualTLSModeLabel,
  1159  								},
  1160  							},
  1161  						},
  1162  					},
  1163  					IstioMetadataKey: {
  1164  						Fields: map[string]*structpb.Value{
  1165  							"workload": {
  1166  								Kind: &structpb.Value_StringValue{
  1167  									StringValue: "workload;default;service;v1;cluster",
  1168  								},
  1169  							},
  1170  						},
  1171  					},
  1172  				},
  1173  			},
  1174  		},
  1175  		{
  1176  			name: "miss pod label",
  1177  			metadata: &model.EndpointMetadata{
  1178  				TLSMode:      model.IstioMutualTLSModeLabel,
  1179  				Network:      "network",
  1180  				WorkloadName: "workload",
  1181  				ClusterID:    "cluster",
  1182  				Namespace:    "default",
  1183  			},
  1184  			want: &core.Metadata{
  1185  				FilterMetadata: map[string]*structpb.Struct{
  1186  					EnvoyTransportSocketMetadataKey: {
  1187  						Fields: map[string]*structpb.Value{
  1188  							model.TLSModeLabelShortname: {
  1189  								Kind: &structpb.Value_StringValue{
  1190  									StringValue: model.IstioMutualTLSModeLabel,
  1191  								},
  1192  							},
  1193  						},
  1194  					},
  1195  					IstioMetadataKey: {
  1196  						Fields: map[string]*structpb.Value{
  1197  							"workload": {
  1198  								Kind: &structpb.Value_StringValue{
  1199  									StringValue: "workload;default;workload;;cluster",
  1200  								},
  1201  							},
  1202  						},
  1203  					},
  1204  				},
  1205  			},
  1206  		},
  1207  		{
  1208  			name: "miss workload name",
  1209  			metadata: &model.EndpointMetadata{
  1210  				TLSMode:      model.IstioMutualTLSModeLabel,
  1211  				Network:      "network",
  1212  				WorkloadName: "",
  1213  				ClusterID:    "cluster",
  1214  				Namespace:    "",
  1215  			},
  1216  			want: &core.Metadata{
  1217  				FilterMetadata: map[string]*structpb.Struct{
  1218  					EnvoyTransportSocketMetadataKey: {
  1219  						Fields: map[string]*structpb.Value{
  1220  							model.TLSModeLabelShortname: {
  1221  								Kind: &structpb.Value_StringValue{
  1222  									StringValue: model.IstioMutualTLSModeLabel,
  1223  								},
  1224  							},
  1225  						},
  1226  					},
  1227  					IstioMetadataKey: {
  1228  						Fields: map[string]*structpb.Value{
  1229  							"workload": {
  1230  								Kind: &structpb.Value_StringValue{
  1231  									StringValue: ";;;;cluster",
  1232  								},
  1233  							},
  1234  						},
  1235  					},
  1236  				},
  1237  			},
  1238  		},
  1239  	}
  1240  	for _, tt := range cases {
  1241  		t.Run(tt.name, func(t *testing.T) {
  1242  			input := &core.Metadata{}
  1243  			AppendLbEndpointMetadata(tt.metadata, input)
  1244  			if !reflect.DeepEqual(input, tt.want) {
  1245  				t.Errorf("Unexpected Endpoint metadata got %v, want %v", input, tt.want)
  1246  			}
  1247  		})
  1248  	}
  1249  }
  1250  
  1251  func TestByteCount(t *testing.T) {
  1252  	cases := []struct {
  1253  		in  int
  1254  		out string
  1255  	}{
  1256  		{1, "1B"},
  1257  		{1000, "1.0kB"},
  1258  		{1_000_000, "1.0MB"},
  1259  		{1_500_000, "1.5MB"},
  1260  	}
  1261  	for _, tt := range cases {
  1262  		t.Run(fmt.Sprint(tt.in), func(t *testing.T) {
  1263  			if got := ByteCount(tt.in); got != tt.out {
  1264  				t.Fatalf("got %v wanted %v", got, tt.out)
  1265  			}
  1266  		})
  1267  	}
  1268  }
  1269  
  1270  func TestIPv6Compliant(t *testing.T) {
  1271  	tests := []struct {
  1272  		host  string
  1273  		match string
  1274  	}{
  1275  		{"localhost", "localhost"},
  1276  		{"127.0.0.1", "127.0.0.1"},
  1277  		{"::1", "[::1]"},
  1278  		{"2001:4860:0:2001::68", "[2001:4860:0:2001::68]"},
  1279  	}
  1280  	for _, tt := range tests {
  1281  		t.Run(fmt.Sprint(tt.host), func(t *testing.T) {
  1282  			if got := IPv6Compliant(tt.host); got != tt.match {
  1283  				t.Fatalf("got %v wanted %v", got, tt.match)
  1284  			}
  1285  		})
  1286  	}
  1287  }
  1288  
  1289  func TestDomainName(t *testing.T) {
  1290  	tests := []struct {
  1291  		host  string
  1292  		port  int
  1293  		match string
  1294  	}{
  1295  		{"localhost", 3000, "localhost:3000"},
  1296  		{"127.0.0.1", 3000, "127.0.0.1:3000"},
  1297  		{"::1", 3000, "[::1]:3000"},
  1298  		{"2001:4860:0:2001::68", 3000, "[2001:4860:0:2001::68]:3000"},
  1299  	}
  1300  	for _, tt := range tests {
  1301  		t.Run(fmt.Sprint(tt.host), func(t *testing.T) {
  1302  			if got := DomainName(tt.host, tt.port); got != tt.match {
  1303  				t.Fatalf("got %v wanted %v", got, tt.match)
  1304  			}
  1305  		})
  1306  	}
  1307  }
  1308  
  1309  func TestStatefulSessionFilterConfig(t *testing.T) {
  1310  	cases := []struct {
  1311  		name           string
  1312  		service        *model.Service
  1313  		expectedconfig *statefulsession.StatefulSession
  1314  	}{
  1315  		{
  1316  			name:           "nil service",
  1317  			expectedconfig: nil,
  1318  		},
  1319  		{
  1320  			name: "service without cookie path",
  1321  			service: &model.Service{
  1322  				Attributes: model.ServiceAttributes{
  1323  					Labels: map[string]string{features.PersistentSessionLabel: "test-cookie"},
  1324  				},
  1325  			},
  1326  			expectedconfig: &statefulsession.StatefulSession{
  1327  				SessionState: &core.TypedExtensionConfig{
  1328  					Name: "envoy.http.stateful_session.cookie",
  1329  					TypedConfig: protoconv.MessageToAny(&cookiev3.CookieBasedSessionState{
  1330  						Cookie: &httpv3.Cookie{
  1331  							Path: "/",
  1332  							Name: "test-cookie",
  1333  						},
  1334  					}),
  1335  				},
  1336  			},
  1337  		},
  1338  		{
  1339  			name: "service with cookie path",
  1340  			service: &model.Service{
  1341  				Attributes: model.ServiceAttributes{
  1342  					Labels: map[string]string{features.PersistentSessionLabel: "test-cookie:/path"},
  1343  				},
  1344  			},
  1345  			expectedconfig: &statefulsession.StatefulSession{
  1346  				SessionState: &core.TypedExtensionConfig{
  1347  					Name: "envoy.http.stateful_session.cookie",
  1348  					TypedConfig: protoconv.MessageToAny(&cookiev3.CookieBasedSessionState{
  1349  						Cookie: &httpv3.Cookie{
  1350  							Path: "/path",
  1351  							Name: "test-cookie",
  1352  						},
  1353  					}),
  1354  				},
  1355  			},
  1356  		},
  1357  	}
  1358  	for _, tt := range cases {
  1359  		t.Run(tt.name, func(t *testing.T) {
  1360  			sessionConfig := MaybeBuildStatefulSessionFilterConfig(tt.service)
  1361  			if !reflect.DeepEqual(tt.expectedconfig, sessionConfig) {
  1362  				t.Errorf("unexpected stateful session filter config, expected: %v, got :%v", tt.expectedconfig, sessionConfig)
  1363  			}
  1364  		})
  1365  	}
  1366  }
  1367  
  1368  func TestMergeSubsetTrafficPolicy(t *testing.T) {
  1369  	cases := []struct {
  1370  		name     string
  1371  		original *networking.TrafficPolicy
  1372  		subset   *networking.TrafficPolicy
  1373  		port     *model.Port
  1374  		expected *networking.TrafficPolicy
  1375  	}{
  1376  		{
  1377  			name:     "all nil policies",
  1378  			original: nil,
  1379  			subset:   nil,
  1380  			port:     nil,
  1381  			expected: nil,
  1382  		},
  1383  		{
  1384  			name: "no subset policy",
  1385  			original: &networking.TrafficPolicy{
  1386  				ConnectionPool: &networking.ConnectionPoolSettings{
  1387  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1388  						MaxRetries: 10,
  1389  					},
  1390  				},
  1391  			},
  1392  			subset: nil,
  1393  			port:   nil,
  1394  			expected: &networking.TrafficPolicy{
  1395  				ConnectionPool: &networking.ConnectionPoolSettings{
  1396  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1397  						MaxRetries: 10,
  1398  					},
  1399  				},
  1400  			},
  1401  		},
  1402  		{
  1403  			name:     "no parent policy",
  1404  			original: nil,
  1405  			subset: &networking.TrafficPolicy{
  1406  				ConnectionPool: &networking.ConnectionPoolSettings{
  1407  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1408  						MaxRetries: 10,
  1409  					},
  1410  				},
  1411  			},
  1412  			port: nil,
  1413  			expected: &networking.TrafficPolicy{
  1414  				ConnectionPool: &networking.ConnectionPoolSettings{
  1415  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1416  						MaxRetries: 10,
  1417  					},
  1418  				},
  1419  			},
  1420  		},
  1421  		{
  1422  			name: "merge non-conflicting fields",
  1423  			original: &networking.TrafficPolicy{
  1424  				Tls: &networking.ClientTLSSettings{
  1425  					Mode: networking.ClientTLSSettings_ISTIO_MUTUAL,
  1426  				},
  1427  			},
  1428  			subset: &networking.TrafficPolicy{
  1429  				ConnectionPool: &networking.ConnectionPoolSettings{
  1430  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1431  						MaxRetries: 10,
  1432  					},
  1433  				},
  1434  				Tunnel: &networking.TrafficPolicy_TunnelSettings{
  1435  					TargetHost: "example.com",
  1436  					TargetPort: 8443,
  1437  				},
  1438  			},
  1439  			port: nil,
  1440  			expected: &networking.TrafficPolicy{
  1441  				ConnectionPool: &networking.ConnectionPoolSettings{
  1442  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1443  						MaxRetries: 10,
  1444  					},
  1445  				},
  1446  				Tls: &networking.ClientTLSSettings{
  1447  					Mode: networking.ClientTLSSettings_ISTIO_MUTUAL,
  1448  				},
  1449  				Tunnel: &networking.TrafficPolicy_TunnelSettings{
  1450  					TargetHost: "example.com",
  1451  					TargetPort: 8443,
  1452  				},
  1453  			},
  1454  		},
  1455  		{
  1456  			name: "subset overwrite top-level fields",
  1457  			original: &networking.TrafficPolicy{
  1458  				Tls: &networking.ClientTLSSettings{
  1459  					Mode: networking.ClientTLSSettings_ISTIO_MUTUAL,
  1460  				},
  1461  				ConnectionPool: &networking.ConnectionPoolSettings{
  1462  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1463  						MaxRetries: 10,
  1464  					},
  1465  				},
  1466  			},
  1467  			subset: &networking.TrafficPolicy{
  1468  				Tls: &networking.ClientTLSSettings{
  1469  					Mode: networking.ClientTLSSettings_SIMPLE,
  1470  				},
  1471  			},
  1472  			port: nil,
  1473  			expected: &networking.TrafficPolicy{
  1474  				Tls: &networking.ClientTLSSettings{
  1475  					Mode: networking.ClientTLSSettings_SIMPLE,
  1476  				},
  1477  				ConnectionPool: &networking.ConnectionPoolSettings{
  1478  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1479  						MaxRetries: 10,
  1480  					},
  1481  				},
  1482  			},
  1483  		},
  1484  		{
  1485  			name:     "merge port level policy, and do not inherit top-level fields",
  1486  			original: nil,
  1487  			subset: &networking.TrafficPolicy{
  1488  				LoadBalancer: &networking.LoadBalancerSettings{
  1489  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1490  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1491  					},
  1492  				},
  1493  				ConnectionPool: &networking.ConnectionPoolSettings{
  1494  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1495  						MaxRetries: 10,
  1496  					},
  1497  				},
  1498  				PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
  1499  					{
  1500  						Port: &networking.PortSelector{
  1501  							Number: 8080,
  1502  						},
  1503  						LoadBalancer: &networking.LoadBalancerSettings{
  1504  							LbPolicy: &networking.LoadBalancerSettings_Simple{
  1505  								Simple: networking.LoadBalancerSettings_LEAST_REQUEST,
  1506  							},
  1507  						},
  1508  					},
  1509  				},
  1510  			},
  1511  			port: &model.Port{Port: 8080},
  1512  			expected: &networking.TrafficPolicy{
  1513  				LoadBalancer: &networking.LoadBalancerSettings{
  1514  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1515  						Simple: networking.LoadBalancerSettings_LEAST_REQUEST,
  1516  					},
  1517  				},
  1518  			},
  1519  		},
  1520  		{
  1521  			name: "merge port level policy, and do not inherit top-level fields",
  1522  			original: &networking.TrafficPolicy{
  1523  				LoadBalancer: &networking.LoadBalancerSettings{
  1524  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1525  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1526  					},
  1527  				},
  1528  				OutlierDetection: &networking.OutlierDetection{
  1529  					ConsecutiveErrors: 20,
  1530  				},
  1531  				PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
  1532  					{
  1533  						Port: &networking.PortSelector{
  1534  							Number: 8080,
  1535  						},
  1536  						OutlierDetection: &networking.OutlierDetection{
  1537  							ConsecutiveErrors: 15,
  1538  						},
  1539  					},
  1540  				},
  1541  			},
  1542  			subset: &networking.TrafficPolicy{
  1543  				LoadBalancer: &networking.LoadBalancerSettings{
  1544  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1545  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1546  					},
  1547  				},
  1548  				ConnectionPool: &networking.ConnectionPoolSettings{
  1549  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1550  						MaxRetries: 10,
  1551  					},
  1552  				},
  1553  				PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
  1554  					{
  1555  						Port: &networking.PortSelector{
  1556  							Number: 8080,
  1557  						},
  1558  						OutlierDetection: &networking.OutlierDetection{
  1559  							ConsecutiveErrors: 13,
  1560  						},
  1561  					},
  1562  				},
  1563  			},
  1564  			port: &model.Port{Port: 8080},
  1565  			expected: &networking.TrafficPolicy{
  1566  				OutlierDetection: &networking.OutlierDetection{
  1567  					ConsecutiveErrors: 13,
  1568  				},
  1569  			},
  1570  		},
  1571  		{
  1572  			name: "default cluster, non-matching port selector",
  1573  			original: &networking.TrafficPolicy{
  1574  				LoadBalancer: &networking.LoadBalancerSettings{
  1575  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1576  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1577  					},
  1578  				},
  1579  				OutlierDetection: &networking.OutlierDetection{
  1580  					ConsecutiveErrors: 20,
  1581  				},
  1582  				PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
  1583  					{
  1584  						Port: &networking.PortSelector{
  1585  							Number: 8080,
  1586  						},
  1587  						OutlierDetection: &networking.OutlierDetection{
  1588  							ConsecutiveErrors: 15,
  1589  						},
  1590  					},
  1591  				},
  1592  			},
  1593  			subset: &networking.TrafficPolicy{
  1594  				LoadBalancer: &networking.LoadBalancerSettings{
  1595  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1596  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1597  					},
  1598  				},
  1599  				ConnectionPool: &networking.ConnectionPoolSettings{
  1600  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1601  						MaxRetries: 10,
  1602  					},
  1603  				},
  1604  				PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
  1605  					{
  1606  						Port: &networking.PortSelector{
  1607  							Number: 8080,
  1608  						},
  1609  						OutlierDetection: &networking.OutlierDetection{
  1610  							ConsecutiveErrors: 13,
  1611  						},
  1612  					},
  1613  				},
  1614  			},
  1615  			port: &model.Port{Port: 9090},
  1616  			expected: &networking.TrafficPolicy{
  1617  				LoadBalancer: &networking.LoadBalancerSettings{
  1618  					LbPolicy: &networking.LoadBalancerSettings_Simple{
  1619  						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
  1620  					},
  1621  				},
  1622  				ConnectionPool: &networking.ConnectionPoolSettings{
  1623  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  1624  						MaxRetries: 10,
  1625  					},
  1626  				},
  1627  				OutlierDetection: &networking.OutlierDetection{
  1628  					ConsecutiveErrors: 20,
  1629  				},
  1630  			},
  1631  		},
  1632  	}
  1633  
  1634  	for _, tt := range cases {
  1635  		t.Run(tt.name, func(t *testing.T) {
  1636  			policy := MergeSubsetTrafficPolicy(tt.original, tt.subset, tt.port)
  1637  			assert.Equal(t, policy, tt.expected)
  1638  		})
  1639  	}
  1640  }