istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/proxy_config_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  	"time"
    20  
    21  	"google.golang.org/protobuf/proto"
    22  	"google.golang.org/protobuf/types/known/durationpb"
    23  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    24  
    25  	"istio.io/api/annotation"
    26  	meshconfig "istio.io/api/mesh/v1alpha1"
    27  	"istio.io/api/networking/v1beta1"
    28  	istioTypes "istio.io/api/type/v1beta1"
    29  	"istio.io/istio/pkg/config"
    30  	"istio.io/istio/pkg/config/mesh"
    31  	"istio.io/istio/pkg/config/schema/gvk"
    32  	"istio.io/istio/pkg/test/util/assert"
    33  	"istio.io/istio/pkg/util/protomarshal"
    34  )
    35  
    36  var now = time.Now()
    37  
    38  const istioRootNamespace = "istio-system"
    39  
    40  func TestConvertToMeshConfigProxyConfig(t *testing.T) {
    41  	cases := []struct {
    42  		name     string
    43  		pc       *v1beta1.ProxyConfig
    44  		expected *meshconfig.ProxyConfig
    45  	}{
    46  		{
    47  			name: "concurrency",
    48  			pc: &v1beta1.ProxyConfig{
    49  				Concurrency: &wrappers.Int32Value{Value: 3},
    50  			},
    51  			expected: &meshconfig.ProxyConfig{
    52  				Concurrency: &wrappers.Int32Value{Value: 3},
    53  			},
    54  		},
    55  		{
    56  			name: "environment variables",
    57  			pc: &v1beta1.ProxyConfig{
    58  				EnvironmentVariables: map[string]string{
    59  					"a": "b",
    60  					"c": "d",
    61  				},
    62  			},
    63  			expected: &meshconfig.ProxyConfig{
    64  				ProxyMetadata: map[string]string{
    65  					"a": "b",
    66  					"c": "d",
    67  				},
    68  			},
    69  		},
    70  	}
    71  
    72  	for _, tc := range cases {
    73  		converted := toMeshConfigProxyConfig(tc.pc)
    74  		assert.Equal(t, converted, tc.expected)
    75  	}
    76  }
    77  
    78  func TestMergeWithPrecedence(t *testing.T) {
    79  	cases := []struct {
    80  		name     string
    81  		first    *meshconfig.ProxyConfig
    82  		second   *meshconfig.ProxyConfig
    83  		expected *meshconfig.ProxyConfig
    84  	}{
    85  		{
    86  			name: "concurrency",
    87  			first: &meshconfig.ProxyConfig{
    88  				Concurrency: v(1),
    89  			},
    90  			second: &meshconfig.ProxyConfig{
    91  				Concurrency: v(2),
    92  			},
    93  			expected: &meshconfig.ProxyConfig{
    94  				Concurrency: v(1),
    95  			},
    96  		},
    97  		{
    98  			name: "concurrency value 0",
    99  			first: &meshconfig.ProxyConfig{
   100  				Concurrency: v(0),
   101  			},
   102  			second: &meshconfig.ProxyConfig{
   103  				Concurrency: v(2),
   104  			},
   105  			expected: &meshconfig.ProxyConfig{
   106  				Concurrency: v(0),
   107  			},
   108  		},
   109  		{
   110  			name: "source concurrency nil",
   111  			first: &meshconfig.ProxyConfig{
   112  				Concurrency: nil,
   113  			},
   114  			second: &meshconfig.ProxyConfig{
   115  				Concurrency: v(2),
   116  			},
   117  			expected: &meshconfig.ProxyConfig{
   118  				Concurrency: v(2),
   119  			},
   120  		},
   121  		{
   122  			name: "dest concurrency nil",
   123  			first: &meshconfig.ProxyConfig{
   124  				Concurrency: v(2),
   125  			},
   126  			second: &meshconfig.ProxyConfig{
   127  				Concurrency: nil,
   128  			},
   129  			expected: &meshconfig.ProxyConfig{
   130  				Concurrency: v(2),
   131  			},
   132  		},
   133  		{
   134  			name: "both concurrency nil",
   135  			first: &meshconfig.ProxyConfig{
   136  				Concurrency: nil,
   137  			},
   138  			second: &meshconfig.ProxyConfig{
   139  				Concurrency: nil,
   140  			},
   141  			expected: &meshconfig.ProxyConfig{
   142  				Concurrency: nil,
   143  			},
   144  		},
   145  		{
   146  			name: "envvars",
   147  			first: &meshconfig.ProxyConfig{
   148  				ProxyMetadata: map[string]string{
   149  					"a": "x",
   150  					"b": "y",
   151  				},
   152  			},
   153  			second: &meshconfig.ProxyConfig{
   154  				ProxyMetadata: map[string]string{
   155  					"a": "z",
   156  					"b": "y",
   157  					"c": "d",
   158  				},
   159  			},
   160  			expected: &meshconfig.ProxyConfig{
   161  				ProxyMetadata: map[string]string{
   162  					"a": "x",
   163  					"b": "y",
   164  					"c": "d",
   165  				},
   166  			},
   167  		},
   168  		{
   169  			name: "empty envars merge with populated",
   170  			first: &meshconfig.ProxyConfig{
   171  				ProxyMetadata: map[string]string{},
   172  			},
   173  			second: &meshconfig.ProxyConfig{
   174  				ProxyMetadata: map[string]string{
   175  					"a": "z",
   176  					"b": "y",
   177  					"c": "d",
   178  				},
   179  			},
   180  			expected: &meshconfig.ProxyConfig{
   181  				ProxyMetadata: map[string]string{
   182  					"a": "z",
   183  					"b": "y",
   184  					"c": "d",
   185  				},
   186  			},
   187  		},
   188  		{
   189  			name:  "nil proxyconfig",
   190  			first: nil,
   191  			second: &meshconfig.ProxyConfig{
   192  				ProxyMetadata: map[string]string{
   193  					"a": "z",
   194  					"b": "y",
   195  					"c": "d",
   196  				},
   197  			},
   198  			expected: &meshconfig.ProxyConfig{
   199  				ProxyMetadata: map[string]string{
   200  					"a": "z",
   201  					"b": "y",
   202  					"c": "d",
   203  				},
   204  			},
   205  		},
   206  		{
   207  			name: "terminationDrainDuration",
   208  			first: &meshconfig.ProxyConfig{
   209  				TerminationDrainDuration: durationpb.New(500 * time.Millisecond),
   210  			},
   211  			second: &meshconfig.ProxyConfig{
   212  				TerminationDrainDuration: durationpb.New(5 * time.Second),
   213  			},
   214  			expected: &meshconfig.ProxyConfig{
   215  				TerminationDrainDuration: durationpb.New(500 * time.Millisecond),
   216  			},
   217  		},
   218  		{
   219  			name: "tracing is empty",
   220  			first: &meshconfig.ProxyConfig{
   221  				Tracing: &meshconfig.Tracing{},
   222  			},
   223  			second: &meshconfig.ProxyConfig{
   224  				Tracing: mesh.DefaultProxyConfig().GetTracing(),
   225  			},
   226  			expected: &meshconfig.ProxyConfig{
   227  				Tracing: &meshconfig.Tracing{},
   228  			},
   229  		},
   230  		{
   231  			name: "tracing is not default",
   232  			first: &meshconfig.ProxyConfig{
   233  				Tracing: &meshconfig.Tracing{
   234  					Tracer: &meshconfig.Tracing_Datadog_{},
   235  				},
   236  			},
   237  			second: &meshconfig.ProxyConfig{
   238  				Tracing: mesh.DefaultProxyConfig().GetTracing(),
   239  			},
   240  			expected: &meshconfig.ProxyConfig{
   241  				Tracing: &meshconfig.Tracing{
   242  					Tracer: &meshconfig.Tracing_Datadog_{},
   243  				},
   244  			},
   245  		},
   246  	}
   247  
   248  	for _, tc := range cases {
   249  		merged := mergeWithPrecedence(tc.first, tc.second)
   250  		assert.Equal(t, merged, tc.expected)
   251  	}
   252  }
   253  
   254  func TestEffectiveProxyConfig(t *testing.T) {
   255  	cases := []struct {
   256  		name          string
   257  		configs       []config.Config
   258  		defaultConfig *meshconfig.ProxyConfig
   259  		proxy         *NodeMetadata
   260  		expected      *meshconfig.ProxyConfig
   261  	}{
   262  		{
   263  			name: "CR applies to matching namespace",
   264  			configs: []config.Config{
   265  				newProxyConfig("ns", "test-ns",
   266  					&v1beta1.ProxyConfig{
   267  						Concurrency: v(3),
   268  						Image: &v1beta1.ProxyImage{
   269  							ImageType: "debug",
   270  						},
   271  					}),
   272  			},
   273  			proxy: newMeta("test-ns", nil, nil),
   274  			expected: &meshconfig.ProxyConfig{
   275  				Concurrency: v(3),
   276  				Image: &v1beta1.ProxyImage{
   277  					ImageType: "debug",
   278  				},
   279  			},
   280  		},
   281  		{
   282  			name: "CR takes precedence over meshConfig.defaultConfig",
   283  			configs: []config.Config{
   284  				newProxyConfig("ns", istioRootNamespace,
   285  					&v1beta1.ProxyConfig{
   286  						Concurrency: v(3),
   287  					}),
   288  			},
   289  			defaultConfig: &meshconfig.ProxyConfig{Concurrency: v(2)},
   290  			proxy:         newMeta("bar", nil, nil),
   291  			expected:      &meshconfig.ProxyConfig{Concurrency: v(3)},
   292  		},
   293  		{
   294  			name: "workload matching CR takes precedence over namespace matching CR",
   295  			configs: []config.Config{
   296  				newProxyConfig("workload", "test-ns",
   297  					&v1beta1.ProxyConfig{
   298  						Selector: selector(map[string]string{
   299  							"test": "selector",
   300  						}),
   301  						Concurrency: v(3),
   302  					}),
   303  				newProxyConfig("ns", "test-ns",
   304  					&v1beta1.ProxyConfig{
   305  						Concurrency: v(2),
   306  					}),
   307  			},
   308  			proxy:    newMeta("test-ns", map[string]string{"test": "selector"}, nil),
   309  			expected: &meshconfig.ProxyConfig{Concurrency: v(3)},
   310  		},
   311  		{
   312  			name: "matching workload CR takes precedence over annotation",
   313  			configs: []config.Config{
   314  				newProxyConfig("workload", "test-ns",
   315  					&v1beta1.ProxyConfig{
   316  						Selector: selector(map[string]string{
   317  							"test": "selector",
   318  						}),
   319  						Concurrency: v(3),
   320  						Image: &v1beta1.ProxyImage{
   321  							ImageType: "debug",
   322  						},
   323  					}),
   324  			},
   325  			proxy: newMeta(
   326  				"test-ns",
   327  				map[string]string{
   328  					"test": "selector",
   329  				}, map[string]string{
   330  					annotation.ProxyConfig.Name: "{ \"concurrency\": 5 }",
   331  				}),
   332  			expected: &meshconfig.ProxyConfig{
   333  				Concurrency: v(3),
   334  				Image: &v1beta1.ProxyImage{
   335  					ImageType: "debug",
   336  				},
   337  			},
   338  		},
   339  		{
   340  			name: "CR in other namespaces get ignored",
   341  			configs: []config.Config{
   342  				newProxyConfig("ns", "wrong-ns",
   343  					&v1beta1.ProxyConfig{
   344  						Concurrency: v(1),
   345  					}),
   346  				newProxyConfig("workload", "wrong-ns",
   347  					&v1beta1.ProxyConfig{
   348  						Selector: selector(map[string]string{
   349  							"test": "selector",
   350  						}),
   351  						Concurrency: v(2),
   352  					}),
   353  				newProxyConfig("global", istioRootNamespace,
   354  					&v1beta1.ProxyConfig{
   355  						Concurrency: v(3),
   356  					}),
   357  			},
   358  			proxy:    newMeta("test-ns", map[string]string{"test": "selector"}, nil),
   359  			expected: &meshconfig.ProxyConfig{Concurrency: v(3)},
   360  		},
   361  		{
   362  			name: "multiple matching workload CRs, oldest applies",
   363  			configs: []config.Config{
   364  				setCreationTimestamp(newProxyConfig("workload-a", "test-ns",
   365  					&v1beta1.ProxyConfig{
   366  						Selector: selector(map[string]string{
   367  							"test": "selector",
   368  						}),
   369  						EnvironmentVariables: map[string]string{
   370  							"A": "1",
   371  						},
   372  					}), now),
   373  				setCreationTimestamp(newProxyConfig("workload-b", "test-ns",
   374  					&v1beta1.ProxyConfig{
   375  						Selector: selector(map[string]string{
   376  							"test": "selector",
   377  						}),
   378  						EnvironmentVariables: map[string]string{
   379  							"B": "2",
   380  						},
   381  					}), now.Add(time.Hour)),
   382  				setCreationTimestamp(newProxyConfig("workload-c", "test-ns",
   383  					&v1beta1.ProxyConfig{
   384  						Selector: selector(map[string]string{
   385  							"test": "selector",
   386  						}),
   387  						EnvironmentVariables: map[string]string{
   388  							"C": "3",
   389  						},
   390  					}), now.Add(time.Hour)),
   391  			},
   392  			proxy: newMeta(
   393  				"test-ns",
   394  				map[string]string{
   395  					"test": "selector",
   396  				}, map[string]string{}),
   397  			expected: &meshconfig.ProxyConfig{ProxyMetadata: map[string]string{
   398  				"A": "1",
   399  			}},
   400  		},
   401  		{
   402  			name: "multiple matching namespace CRs, oldest applies",
   403  			configs: []config.Config{
   404  				setCreationTimestamp(newProxyConfig("workload-a", "test-ns",
   405  					&v1beta1.ProxyConfig{
   406  						EnvironmentVariables: map[string]string{
   407  							"A": "1",
   408  						},
   409  					}), now),
   410  				setCreationTimestamp(newProxyConfig("workload-b", "test-ns",
   411  					&v1beta1.ProxyConfig{
   412  						EnvironmentVariables: map[string]string{
   413  							"B": "2",
   414  						},
   415  					}), now.Add(time.Hour)),
   416  				setCreationTimestamp(newProxyConfig("workload-c", "test-ns",
   417  					&v1beta1.ProxyConfig{
   418  						EnvironmentVariables: map[string]string{
   419  							"C": "3",
   420  						},
   421  					}), now.Add(time.Hour)),
   422  			},
   423  			proxy: newMeta(
   424  				"test-ns",
   425  				map[string]string{}, map[string]string{}),
   426  			expected: &meshconfig.ProxyConfig{ProxyMetadata: map[string]string{
   427  				"A": "1",
   428  			}},
   429  		},
   430  		{
   431  			name:  "no configured CR or default config",
   432  			proxy: newMeta("ns", nil, nil),
   433  		},
   434  	}
   435  
   436  	for _, tc := range cases {
   437  		t.Run(tc.name, func(t *testing.T) {
   438  			store := newProxyConfigStore(t, tc.configs)
   439  			m := &meshconfig.MeshConfig{
   440  				RootNamespace: istioRootNamespace,
   441  				DefaultConfig: tc.defaultConfig,
   442  			}
   443  			original, _ := protomarshal.ToJSON(m)
   444  			pcs := GetProxyConfigs(store, m)
   445  			merged := pcs.EffectiveProxyConfig(tc.proxy, m)
   446  			pc := mesh.DefaultProxyConfig()
   447  			proto.Merge(pc, tc.expected)
   448  
   449  			assert.Equal(t, merged, pc)
   450  			after, _ := protomarshal.ToJSON(m)
   451  			assert.Equal(t, original, after, "mesh config should not be mutated")
   452  		})
   453  	}
   454  }
   455  
   456  func newProxyConfig(name, ns string, spec config.Spec) config.Config {
   457  	return config.Config{
   458  		Meta: config.Meta{
   459  			GroupVersionKind: gvk.ProxyConfig,
   460  			Name:             name,
   461  			Namespace:        ns,
   462  		},
   463  		Spec: spec,
   464  	}
   465  }
   466  
   467  func newProxyConfigStore(t *testing.T, configs []config.Config) ConfigStore {
   468  	t.Helper()
   469  
   470  	store := NewFakeStore()
   471  	for _, cfg := range configs {
   472  		store.Create(cfg)
   473  	}
   474  
   475  	return store
   476  }
   477  
   478  func setCreationTimestamp(c config.Config, t time.Time) config.Config {
   479  	c.Meta.CreationTimestamp = t
   480  	return c
   481  }
   482  
   483  func newMeta(ns string, labels, annotations map[string]string) *NodeMetadata {
   484  	return &NodeMetadata{
   485  		Namespace:   ns,
   486  		Labels:      labels,
   487  		Annotations: annotations,
   488  	}
   489  }
   490  
   491  func v(x int32) *wrappers.Int32Value {
   492  	return &wrappers.Int32Value{Value: x}
   493  }
   494  
   495  func selector(l map[string]string) *istioTypes.WorkloadSelector {
   496  	return &istioTypes.WorkloadSelector{MatchLabels: l}
   497  }