istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/bootstrap/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 bootstrap
    16  
    17  import (
    18  	"encoding/json"
    19  	"os"
    20  	"reflect"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"k8s.io/kubectl/pkg/util/fieldpath"
    25  
    26  	"istio.io/api/mesh/v1alpha1"
    27  	"istio.io/istio/pkg/bootstrap/option"
    28  	"istio.io/istio/pkg/config/constants"
    29  	"istio.io/istio/pkg/model"
    30  	"istio.io/istio/pkg/test"
    31  	"istio.io/istio/pkg/util/protomarshal"
    32  	"istio.io/istio/pkg/version"
    33  )
    34  
    35  func TestParseDownwardApi(t *testing.T) {
    36  	cases := []struct {
    37  		name string
    38  		m    map[string]string
    39  	}{
    40  		{
    41  			"empty",
    42  			map[string]string{},
    43  		},
    44  		{
    45  			"single",
    46  			map[string]string{"foo": "bar"},
    47  		},
    48  		{
    49  			"multi",
    50  			map[string]string{
    51  				"app":               "istio-ingressgateway",
    52  				"chart":             "gateways",
    53  				"heritage":          "Tiller",
    54  				"istio":             "ingressgateway",
    55  				"pod-template-hash": "54756dbcf9",
    56  			},
    57  		},
    58  		{
    59  			"multi line",
    60  			map[string]string{
    61  				"config": `foo: bar
    62  other: setting`,
    63  				"istio": "ingressgateway",
    64  			},
    65  		},
    66  		{
    67  			"weird values",
    68  			map[string]string{
    69  				"foo": `a1_-.as1`,
    70  				"bar": `a=b`,
    71  			},
    72  		},
    73  	}
    74  	for _, tt := range cases {
    75  		t.Run(tt.name, func(t *testing.T) {
    76  			// Using the function kubernetes actually uses to write this, we do a round trip of
    77  			// map -> file -> map and ensure the input and output are the same
    78  			got, err := ParseDownwardAPI(fieldpath.FormatMap(tt.m))
    79  			if !reflect.DeepEqual(got, tt.m) {
    80  				t.Fatalf("expected %v, got %v with err: %v", tt.m, got, err)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestGetNodeMetaData(t *testing.T) {
    87  	inputOwner := "test"
    88  	inputWorkloadName := "workload"
    89  
    90  	expectOwner := "test"
    91  	expectWorkloadName := "workload"
    92  	expectExitOnZeroActiveConnections := model.StringBool(true)
    93  
    94  	t.Setenv(IstioMetaPrefix+"OWNER", inputOwner)
    95  	t.Setenv(IstioMetaPrefix+"WORKLOAD_NAME", inputWorkloadName)
    96  
    97  	dir, _ := os.Getwd()
    98  	defer os.Chdir(dir)
    99  	// prepare a pod label file
   100  	tempDir := t.TempDir()
   101  	os.Chdir(tempDir)
   102  	os.MkdirAll("./etc/istio/pod/", os.ModePerm)
   103  	os.WriteFile(constants.PodInfoLabelsPath, []byte(`istio-locality="region.zone.subzone"`), 0o600)
   104  
   105  	node, err := GetNodeMetaData(MetadataOptions{
   106  		ID:                          "test",
   107  		Envs:                        os.Environ(),
   108  		ExitOnZeroActiveConnections: true,
   109  	})
   110  
   111  	g := NewWithT(t)
   112  	g.Expect(err).Should(BeNil())
   113  	g.Expect(node.Metadata.Owner).To(Equal(expectOwner))
   114  	g.Expect(node.Metadata.WorkloadName).To(Equal(expectWorkloadName))
   115  	g.Expect(node.Metadata.ExitOnZeroActiveConnections).To(Equal(expectExitOnZeroActiveConnections))
   116  	g.Expect(node.RawMetadata["OWNER"]).To(Equal(expectOwner))
   117  	g.Expect(node.RawMetadata["WORKLOAD_NAME"]).To(Equal(expectWorkloadName))
   118  	g.Expect(node.Metadata.Labels[model.LocalityLabel]).To(Equal("region/zone/subzone"))
   119  }
   120  
   121  func TestSetIstioVersion(t *testing.T) {
   122  	test.SetForTest(t, &version.Info.Version, "binary")
   123  
   124  	testCases := []struct {
   125  		name            string
   126  		meta            *model.BootstrapNodeMetadata
   127  		binaryVersion   string
   128  		expectedVersion string
   129  	}{
   130  		{
   131  			name:            "if IstioVersion is not specified, set it from binary version",
   132  			meta:            &model.BootstrapNodeMetadata{},
   133  			expectedVersion: "binary",
   134  		},
   135  		{
   136  			name: "if IstioVersion is specified, don't set it from binary version",
   137  			meta: &model.BootstrapNodeMetadata{
   138  				NodeMetadata: model.NodeMetadata{
   139  					IstioVersion: "metadata-version",
   140  				},
   141  			},
   142  			expectedVersion: "metadata-version",
   143  		},
   144  	}
   145  
   146  	for _, tc := range testCases {
   147  		t.Run(tc.name, func(t *testing.T) {
   148  			ret := SetIstioVersion(tc.meta)
   149  			if ret.IstioVersion != tc.expectedVersion {
   150  				t.Fatalf("SetIstioVersion: expected '%s', got '%s'", tc.expectedVersion, ret.IstioVersion)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  func TestConvertNodeMetadata(t *testing.T) {
   157  	node := &model.Node{
   158  		ID: "test",
   159  		Metadata: &model.BootstrapNodeMetadata{
   160  			NodeMetadata: model.NodeMetadata{
   161  				ProxyConfig: &model.NodeMetaProxyConfig{
   162  					ClusterName: &v1alpha1.ProxyConfig_ServiceCluster{
   163  						ServiceCluster: "cluster",
   164  					},
   165  				},
   166  			},
   167  			Owner: "real-owner",
   168  		},
   169  		RawMetadata: map[string]any{},
   170  	}
   171  	node.Metadata.Owner = "real-owner"
   172  	node.RawMetadata["OWNER"] = "fake-owner"
   173  	node.RawMetadata["UNKNOWN"] = "new-field"
   174  	node.RawMetadata["A"] = 1
   175  	node.RawMetadata["B"] = map[string]any{"b": 1}
   176  
   177  	out := ConvertNodeToXDSNode(node)
   178  	{
   179  		b, err := protomarshal.MarshalProtoNames(out)
   180  		if err != nil {
   181  			t.Fatalf("failed to marshal: %v", err)
   182  		}
   183  		// nolint: lll
   184  		want := `{"id":"test","cluster":"cluster","metadata":{"A":1,"B":{"b":1},"OWNER":"real-owner","PROXY_CONFIG":{"serviceCluster":"cluster"},"UNKNOWN":"new-field"}}`
   185  		test.JSONEquals(t, want, string(b))
   186  	}
   187  
   188  	node2 := ConvertXDSNodeToNode(out)
   189  	{
   190  		got, err := json.Marshal(node2)
   191  		if err != nil {
   192  			t.Fatalf("failed to marshal: %v", err)
   193  		}
   194  		// nolint: lll
   195  		want := `{"ID":"test","Metadata":{"PROXY_CONFIG":{"serviceCluster":"cluster"},"OWNER":"real-owner"},"RawMetadata":null,"Locality":null}`
   196  		if want != string(got) {
   197  			t.Fatalf("ConvertXDSNodeToNode: got %q, want %q", string(got), want)
   198  		}
   199  	}
   200  }
   201  
   202  func TestConvertNodeServiceClusterNaming(t *testing.T) {
   203  	cases := []struct {
   204  		name        string
   205  		proxyCfg    *model.NodeMetaProxyConfig
   206  		labels      map[string]string
   207  		wantCluster string
   208  	}{
   209  		{
   210  			name:        "no cluster name (no labels)",
   211  			proxyCfg:    &model.NodeMetaProxyConfig{},
   212  			wantCluster: "istio-proxy.bar",
   213  		},
   214  		{
   215  			name:        "no cluster name (defaults)",
   216  			proxyCfg:    &model.NodeMetaProxyConfig{},
   217  			labels:      map[string]string{"app": "foo"},
   218  			wantCluster: "foo.bar",
   219  		},
   220  		{
   221  			name: "service cluster",
   222  			proxyCfg: &model.NodeMetaProxyConfig{
   223  				ClusterName: &v1alpha1.ProxyConfig_ServiceCluster{
   224  					ServiceCluster: "foo",
   225  				},
   226  			},
   227  			wantCluster: "foo",
   228  		},
   229  		{
   230  			name: "trace service name (app label and namespace)",
   231  			proxyCfg: &model.NodeMetaProxyConfig{
   232  				ClusterName: &v1alpha1.ProxyConfig_TracingServiceName_{
   233  					TracingServiceName: v1alpha1.ProxyConfig_APP_LABEL_AND_NAMESPACE,
   234  				},
   235  			},
   236  			labels:      map[string]string{"app": "foo"},
   237  			wantCluster: "foo.bar",
   238  		},
   239  		{
   240  			name: "trace service name (canonical name)",
   241  			proxyCfg: &model.NodeMetaProxyConfig{
   242  				ClusterName: &v1alpha1.ProxyConfig_TracingServiceName_{
   243  					TracingServiceName: v1alpha1.ProxyConfig_CANONICAL_NAME_ONLY,
   244  				},
   245  			},
   246  			labels:      map[string]string{"service.istio.io/canonical-name": "foo"},
   247  			wantCluster: "foo",
   248  		},
   249  		{
   250  			name: "trace service name (canonical name and namespace)",
   251  			proxyCfg: &model.NodeMetaProxyConfig{
   252  				ClusterName: &v1alpha1.ProxyConfig_TracingServiceName_{
   253  					TracingServiceName: v1alpha1.ProxyConfig_CANONICAL_NAME_AND_NAMESPACE,
   254  				},
   255  			},
   256  			labels:      map[string]string{"service.istio.io/canonical-name": "foo"},
   257  			wantCluster: "foo.bar",
   258  		},
   259  	}
   260  
   261  	for _, v := range cases {
   262  		t.Run(v.name, func(tt *testing.T) {
   263  			node := &model.Node{
   264  				ID: "test",
   265  				Metadata: &model.BootstrapNodeMetadata{
   266  					NodeMetadata: model.NodeMetadata{
   267  						ProxyConfig: v.proxyCfg,
   268  						Labels:      v.labels,
   269  						Namespace:   "bar",
   270  					},
   271  				},
   272  			}
   273  			out := ConvertNodeToXDSNode(node)
   274  			if got, want := out.Cluster, v.wantCluster; got != want {
   275  				tt.Errorf("ConvertNodeToXDSNode(%#v) => cluster = %s; want %s", node, got, want)
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestGetStatOptions(t *testing.T) {
   282  	cases := []struct {
   283  		name            string
   284  		metadataOptions MetadataOptions
   285  		// TODO(ramaraochavali): Add validation for prefix and tags also.
   286  		wantInclusionSuffixes []string
   287  	}{
   288  		{
   289  			name: "with exit on zero connections enabled",
   290  			metadataOptions: MetadataOptions{
   291  				ID:                          "test",
   292  				Envs:                        os.Environ(),
   293  				ProxyConfig:                 &v1alpha1.ProxyConfig{},
   294  				ExitOnZeroActiveConnections: true,
   295  			},
   296  			wantInclusionSuffixes: []string{"rbac.allowed", "rbac.denied", "shadow_allowed", "shadow_denied", "downstream_cx_active"},
   297  		},
   298  		{
   299  			name: "with exit on zero connections disabled",
   300  			metadataOptions: MetadataOptions{
   301  				ID:                          "test",
   302  				Envs:                        os.Environ(),
   303  				ProxyConfig:                 &v1alpha1.ProxyConfig{},
   304  				ExitOnZeroActiveConnections: false,
   305  			},
   306  			wantInclusionSuffixes: []string{"rbac.allowed", "rbac.denied", "shadow_allowed", "shadow_denied"},
   307  		},
   308  	}
   309  
   310  	for _, tc := range cases {
   311  		t.Run(tc.name, func(tt *testing.T) {
   312  			node, _ := GetNodeMetaData(tc.metadataOptions)
   313  			options := getStatsOptions(node.Metadata)
   314  			templateParams, _ := option.NewTemplateParams(options...)
   315  			inclusionSuffixes := templateParams["inclusionSuffix"]
   316  			if !reflect.DeepEqual(inclusionSuffixes, tc.wantInclusionSuffixes) {
   317  				tt.Errorf("unexpected inclusion suffixes. want: %v, got: %v", tc.wantInclusionSuffixes, inclusionSuffixes)
   318  			}
   319  		})
   320  	}
   321  }