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 }