istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/proxyconfig/proxyconfig_test.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package proxyconfig
    19  
    20  import (
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"istio.io/api/annotation"
    27  	"istio.io/istio/pkg/test/framework"
    28  	"istio.io/istio/pkg/test/framework/components/echo"
    29  	"istio.io/istio/pkg/test/framework/components/echo/deployment"
    30  	"istio.io/istio/pkg/test/framework/components/istio"
    31  	"istio.io/istio/pkg/test/framework/components/namespace"
    32  	"istio.io/istio/pkg/test/framework/label"
    33  	"istio.io/istio/pkg/test/framework/resource"
    34  	"istio.io/istio/pkg/test/util/retry"
    35  	"istio.io/istio/pkg/test/util/tmpl"
    36  )
    37  
    38  var i istio.Instance
    39  
    40  func TestMain(m *testing.M) {
    41  	framework.
    42  		NewSuite(m).
    43  		Skip("used for feature development, no need to run in CI").
    44  		Label(label.CustomSetup).
    45  		Setup(istio.Setup(&i, func(ctx resource.Context, cfg *istio.Config) {
    46  			cfg.ControlPlaneValues = `
    47  values:
    48    meshConfig:
    49      defaultConfig:
    50        proxyMetadata:
    51          A: "1"
    52          B: "2"
    53        `
    54  		})).
    55  		Run()
    56  }
    57  
    58  type proxyConfigInstance struct {
    59  	namespace string
    60  	config    string
    61  }
    62  
    63  func TestProxyConfig(t *testing.T) {
    64  	framework.NewTest(t).
    65  		RequireIstioVersion("1.13").
    66  		Run(func(ctx framework.TestContext) {
    67  			ns := namespace.NewOrFail(ctx, ctx, namespace.Config{
    68  				Prefix: "pc-test",
    69  				Inject: true,
    70  			})
    71  			cases := []struct {
    72  				name string
    73  				// namespace, labels, and annotations for the echo instance
    74  				pcAnnotation string
    75  				// service, echo service to use for this subtest
    76  				service string
    77  				// proxyconfig resources to apply
    78  				configs []proxyConfigInstance
    79  				// expected environment variables post-injection
    80  				expected map[string]string
    81  			}{
    82  				{
    83  					"default config maintained",
    84  					"",
    85  					"",
    86  					[]proxyConfigInstance{},
    87  					map[string]string{
    88  						"A": "1",
    89  						"B": "2",
    90  					},
    91  				},
    92  				{
    93  					"global takes precedence over default config",
    94  					"",
    95  					"",
    96  					[]proxyConfigInstance{
    97  						newProxyConfig("global", "istio-system", nil, map[string]string{
    98  							"A": "3",
    99  						}),
   100  					},
   101  					map[string]string{
   102  						"A": "3",
   103  						"B": "2",
   104  					},
   105  				},
   106  				{
   107  					"pod annotation takes precedence over namespace",
   108  					"{ \"proxyMetadata\": {\"A\": \"5\"} }",
   109  					"",
   110  					[]proxyConfigInstance{
   111  						newProxyConfig("namespace-scoped", ns.Name(), nil, map[string]string{
   112  							"A": "4",
   113  						}),
   114  					},
   115  					map[string]string{
   116  						"A": "5",
   117  					},
   118  				},
   119  				{
   120  					"workload selector takes precedence over namespace",
   121  					"",
   122  					"matcher",
   123  					[]proxyConfigInstance{
   124  						newProxyConfig("namespace-scoped", ns.Name(), nil, map[string]string{
   125  							"A": "6",
   126  						}),
   127  						newProxyConfig("workload-selector", ns.Name(), map[string]string{
   128  							"app": "matcher",
   129  						}, map[string]string{
   130  							"A": "5",
   131  						}),
   132  					},
   133  					map[string]string{
   134  						"A": "5",
   135  					},
   136  				},
   137  			}
   138  
   139  			for i, tc := range cases {
   140  				ctx.NewSubTest(tc.name).Run(func(t framework.TestContext) {
   141  					applyProxyConfigs(t, tc.configs)
   142  					defer deleteProxyConfigs(t, tc.configs)
   143  
   144  					svc := fmt.Sprintf("echo-%d", i)
   145  					if tc.service != "" {
   146  						svc = tc.service
   147  					}
   148  					echoConfig := echo.Config{
   149  						Namespace: ns,
   150  						Service:   svc,
   151  					}
   152  					if tc.pcAnnotation != "" {
   153  						echoConfig.Subsets = []echo.SubsetConfig{
   154  							{
   155  								Annotations: map[string]string{
   156  									annotation.ProxyConfig.Name: tc.pcAnnotation,
   157  								},
   158  							},
   159  						}
   160  					}
   161  
   162  					instances := deployment.New(ctx, t.Clusters().Configs()...).WithConfig(echoConfig).BuildOrFail(t)
   163  					checkInjectedValues(t, instances, tc.expected)
   164  				})
   165  			}
   166  		})
   167  }
   168  
   169  func checkInjectedValues(t framework.TestContext, instances echo.Instances, values map[string]string) {
   170  	t.Helper()
   171  	for _, i := range instances {
   172  		i := i
   173  		attempts := 0
   174  		retry.UntilSuccessOrFail(t, func() error {
   175  			// to avoid sleeping for ProxyConfig propagation, we
   176  			// can just re-trigger injection on every retry.
   177  			if attempts > 0 {
   178  				err := i.Restart()
   179  				if err != nil {
   180  					return fmt.Errorf("failed to restart echo instance: %v", err)
   181  				}
   182  			}
   183  			attempts++
   184  			for _, w := range i.WorkloadsOrFail(t) {
   185  				w := w
   186  				for k, v := range values {
   187  					// can we rely on printenv being in the container once distroless is default?
   188  					out, _, err := i.Config().Cluster.PodExec(w.PodName(), i.Config().Namespace.Name(),
   189  						"istio-proxy", fmt.Sprintf("printenv %s", k))
   190  					out = strings.TrimSuffix(out, "\n")
   191  					if err != nil {
   192  						return fmt.Errorf("could not exec into pod: %v", err)
   193  					}
   194  					if out != v {
   195  						return fmt.Errorf("expected envvar %s with value %q, got %q", k, v, out)
   196  					}
   197  				}
   198  			}
   199  			return nil
   200  		}, retry.Timeout(time.Second*45))
   201  	}
   202  }
   203  
   204  func applyProxyConfigs(ctx framework.TestContext, configs []proxyConfigInstance) {
   205  	for _, config := range configs {
   206  		ctx.ConfigIstio().YAML(config.namespace, config.config).ApplyOrFail(ctx)
   207  	}
   208  	// TODO(Monkeyanator) give a few seconds for PC to propagate
   209  	// shouldn't be required but multicluster seems to have some issues with echo instance restart.
   210  	time.Sleep(time.Second * 5)
   211  }
   212  
   213  func deleteProxyConfigs(ctx framework.TestContext, configs []proxyConfigInstance) {
   214  	for _, config := range configs {
   215  		ctx.ConfigIstio().YAML(config.namespace, config.config).DeleteOrFail(ctx)
   216  	}
   217  }
   218  
   219  func newProxyConfig(name, ns string, selector, values map[string]string) proxyConfigInstance {
   220  	tpl := `
   221  apiVersion: networking.istio.io/v1beta1
   222  kind: ProxyConfig
   223  metadata:
   224    name: {{ .Name }}
   225  spec:
   226  {{- if .Selector }}
   227    selector:
   228      matchLabels:
   229  {{- range $k, $v := .Selector }}
   230        {{ $k }}: {{ $v }}
   231  {{- end }}
   232  {{- end }}
   233    environmentVariables:
   234  {{- range $k, $v := .Values }}
   235      {{ $k }}: "{{ $v }}"
   236  {{- end }}
   237  `
   238  	return proxyConfigInstance{
   239  		namespace: ns,
   240  		config: tmpl.MustEvaluate(tpl, struct {
   241  			Name     string
   242  			Selector map[string]string
   243  			Values   map[string]string
   244  		}{
   245  			Name:     name,
   246  			Selector: selector,
   247  			Values:   values,
   248  		}),
   249  	}
   250  }