1// Copyright 2018 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. 14package bootstrap 15 16import ( 17 "encoding/json" 18 "fmt" 19 "io/ioutil" 20 "net/http" 21 "net/http/httptest" 22 "net/url" 23 "os" 24 "path" 25 "reflect" 26 "regexp" 27 "strings" 28 "testing" 29 30 v1 "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1" 31 core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 32 v2 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2" 33 tracev2 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v2" 34 matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher" 35 "github.com/ghodss/yaml" 36 "github.com/gogo/protobuf/proto" 37 "github.com/golang/protobuf/jsonpb" 38 "github.com/golang/protobuf/ptypes" 39 diff "gopkg.in/d4l3k/messagediff.v1" 40 41 "istio.io/api/annotation" 42 meshconfig "istio.io/api/mesh/v1alpha1" 43 44 "istio.io/istio/pilot/test/util" 45 "istio.io/istio/pkg/bootstrap/platform" 46) 47 48type stats struct { 49 prefixes string 50 suffixes string 51 regexps string 52} 53 54var ( 55 // The following set of inclusions add minimal upstream and downstream metrics. 56 // Upstream metrics record client side measurements. 57 // Downstream metrics record server side measurements. 58 upstreamStatsSuffixes = "upstream_rq_1xx,upstream_rq_2xx,upstream_rq_3xx,upstream_rq_4xx,upstream_rq_5xx," + 59 "upstream_rq_time,upstream_cx_tx_bytes_total,upstream_cx_rx_bytes_total,upstream_cx_total" 60 61 // example downstream metric: http.10.16.48.230_8080.downstream_rq_2xx 62 // http.<pod_ip>_<port>.downstream_rq_2xx 63 // This metric is collected at the inbound listener at a sidecar. 64 // All the other downstream metrics at a sidecar are from the application to the local sidecar. 65 downstreamStatsSuffixes = "downstream_rq_1xx,downstream_rq_2xx,downstream_rq_3xx,downstream_rq_4xx,downstream_rq_5xx," + 66 "downstream_rq_time,downstream_cx_tx_bytes_total,downstream_cx_rx_bytes_total,downstream_cx_total" 67) 68 69// Generate configs for the default configs used by istio. 70// If the template is updated, copy the new golden files from out: 71// cp $TOP/out/linux_amd64/release/bootstrap/all/envoy-rev0.json pkg/bootstrap/testdata/all_golden.json 72// cp $TOP/out/linux_amd64/release/bootstrap/auth/envoy-rev0.json pkg/bootstrap/testdata/auth_golden.json 73// cp $TOP/out/linux_amd64/release/bootstrap/default/envoy-rev0.json pkg/bootstrap/testdata/default_golden.json 74// cp $TOP/out/linux_amd64/release/bootstrap/tracing_datadog/envoy-rev0.json pkg/bootstrap/testdata/tracing_datadog_golden.json 75// cp $TOP/out/linux_amd64/release/bootstrap/tracing_lightstep/envoy-rev0.json pkg/bootstrap/testdata/tracing_lightstep_golden.json 76// cp $TOP/out/linux_amd64/release/bootstrap/tracing_zipkin/envoy-rev0.json pkg/bootstrap/testdata/tracing_zipkin_golden.json 77func TestGolden(t *testing.T) { 78 out := "/tmp" 79 var ts *httptest.Server 80 81 cases := []struct { 82 base string 83 envVars map[string]string 84 annotations map[string]string 85 sdsUDSPath string 86 sdsTokenPath string 87 expectLightstepAccessToken bool 88 stats stats 89 checkLocality bool 90 setup func() 91 teardown func() 92 check func(got *v2.Bootstrap, t *testing.T) 93 }{ 94 { 95 base: "auth", 96 }, 97 { 98 base: "authsds", 99 sdsUDSPath: "udspath", 100 sdsTokenPath: "/var/run/secrets/tokens/istio-token", 101 }, 102 { 103 base: "default", 104 }, 105 { 106 base: "running", 107 envVars: map[string]string{ 108 "ISTIO_META_ISTIO_PROXY_SHA": "istio-proxy:sha", 109 "ISTIO_META_INTERCEPTION_MODE": "REDIRECT", 110 "ISTIO_META_ISTIO_VERSION": "release-3.1", 111 "ISTIO_META_POD_NAME": "svc-0-0-0-6944fb884d-4pgx8", 112 "POD_NAME": "svc-0-0-0-6944fb884d-4pgx8", 113 "POD_NAMESPACE": "test", 114 "INSTANCE_IP": "10.10.10.1", 115 "ISTIO_METAJSON_LABELS": `{"version": "v1alpha1", "app": "test", "istio-locality":"regionA.zoneB.sub_zoneC"}`, 116 }, 117 annotations: map[string]string{ 118 "istio.io/insecurepath": "{\"paths\":[\"/metrics\",\"/live\"]}", 119 }, 120 checkLocality: true, 121 }, 122 { 123 base: "runningsds", 124 envVars: map[string]string{ 125 "ISTIO_META_ISTIO_PROXY_SHA": "istio-proxy:sha", 126 "ISTIO_META_INTERCEPTION_MODE": "REDIRECT", 127 "ISTIO_META_ISTIO_VERSION": "release-3.1", 128 "ISTIO_META_POD_NAME": "svc-0-0-0-6944fb884d-4pgx8", 129 "POD_NAME": "svc-0-0-0-6944fb884d-4pgx8", 130 "POD_NAMESPACE": "test", 131 "INSTANCE_IP": "10.10.10.1", 132 "ISTIO_METAJSON_LABELS": `{"version": "v1alpha1", "app": "test", "istio-locality":"regionA.zoneB.sub_zoneC"}`, 133 }, 134 annotations: map[string]string{ 135 "istio.io/insecurepath": "{\"paths\":[\"/metrics\",\"/live\"]}", 136 }, 137 sdsUDSPath: "udspath", 138 sdsTokenPath: "/var/run/secrets/tokens/istio-token", 139 checkLocality: true, 140 }, 141 { 142 base: "tracing_lightstep", 143 expectLightstepAccessToken: true, 144 }, 145 { 146 base: "tracing_zipkin", 147 }, 148 { 149 base: "tracing_datadog", 150 }, 151 { 152 base: "tracing_stackdriver", 153 setup: func() { 154 ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 155 fmt.Fprintln(w, "my-sd-project") 156 })) 157 158 u, err := url.Parse(ts.URL) 159 if err != nil { 160 t.Fatalf("Unable to parse mock server url: %v", err) 161 } 162 _ = os.Setenv("GCE_METADATA_HOST", u.Host) 163 }, 164 teardown: func() { 165 if ts != nil { 166 ts.Close() 167 } 168 _ = os.Unsetenv("GCE_METADATA_HOST") 169 }, 170 check: func(got *v2.Bootstrap, t *testing.T) { 171 // nolint: staticcheck 172 cfg := got.Tracing.Http.GetTypedConfig() 173 sdMsg := tracev2.OpenCensusConfig{} 174 if err := ptypes.UnmarshalAny(cfg, &sdMsg); err != nil { 175 t.Fatalf("unable to parse: %v %v", cfg, err) 176 } 177 178 want := tracev2.OpenCensusConfig{ 179 TraceConfig: &v1.TraceConfig{ 180 Sampler: &v1.TraceConfig_ConstantSampler{ 181 ConstantSampler: &v1.ConstantSampler{ 182 Decision: v1.ConstantSampler_ALWAYS_PARENT, 183 }, 184 }, 185 MaxNumberOfAttributes: 200, 186 MaxNumberOfAnnotations: 201, 187 MaxNumberOfMessageEvents: 201, 188 MaxNumberOfLinks: 200, 189 }, 190 StackdriverExporterEnabled: true, 191 StdoutExporterEnabled: true, 192 StackdriverProjectId: "my-sd-project", 193 IncomingTraceContext: []tracev2.OpenCensusConfig_TraceContext{ 194 tracev2.OpenCensusConfig_CLOUD_TRACE_CONTEXT, 195 tracev2.OpenCensusConfig_TRACE_CONTEXT, 196 tracev2.OpenCensusConfig_GRPC_TRACE_BIN, 197 tracev2.OpenCensusConfig_B3}, 198 OutgoingTraceContext: []tracev2.OpenCensusConfig_TraceContext{ 199 tracev2.OpenCensusConfig_CLOUD_TRACE_CONTEXT, 200 tracev2.OpenCensusConfig_TRACE_CONTEXT, 201 tracev2.OpenCensusConfig_GRPC_TRACE_BIN, 202 tracev2.OpenCensusConfig_B3}, 203 } 204 205 p, equal := diff.PrettyDiff(sdMsg, want) 206 if !equal { 207 t.Fatalf("t diff: %v\ngot: %v\nwant: %v\n", p, sdMsg, want) 208 } 209 }, 210 }, 211 { 212 // Specify zipkin/statsd address, similar with the default config in v1 tests 213 base: "all", 214 }, 215 { 216 base: "stats_inclusion", 217 annotations: map[string]string{ 218 "sidecar.istio.io/statsInclusionPrefixes": "prefix1,prefix2", 219 "sidecar.istio.io/statsInclusionSuffixes": "suffix1,suffix2", 220 "sidecar.istio.io/extraStatTags": "dlp_status,dlp_error", 221 }, 222 stats: stats{prefixes: "prefix1,prefix2", 223 suffixes: "suffix1,suffix2"}, 224 }, 225 { 226 base: "stats_inclusion", 227 annotations: map[string]string{ 228 "sidecar.istio.io/statsInclusionSuffixes": upstreamStatsSuffixes + "," + downstreamStatsSuffixes, 229 "sidecar.istio.io/extraStatTags": "dlp_status,dlp_error", 230 }, 231 stats: stats{ 232 suffixes: upstreamStatsSuffixes + "," + downstreamStatsSuffixes}, 233 }, 234 { 235 base: "stats_inclusion", 236 annotations: map[string]string{ 237 "sidecar.istio.io/statsInclusionPrefixes": "http.{pod_ip}_", 238 "sidecar.istio.io/extraStatTags": "dlp_status,dlp_error", 239 }, 240 // {pod_ip} is unrolled 241 stats: stats{prefixes: "http.10.3.3.3_,http.10.4.4.4_,http.10.5.5.5_,http.10.6.6.6_"}, 242 }, 243 { 244 base: "stats_inclusion", 245 annotations: map[string]string{ 246 "sidecar.istio.io/statsInclusionRegexps": "http.[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*_8080.downstream_rq_time", 247 "sidecar.istio.io/extraStatTags": "dlp_status,dlp_error", 248 }, 249 stats: stats{regexps: "http.[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*_8080.downstream_rq_time"}, 250 }, 251 { 252 base: "tracing_tls", 253 }, 254 } 255 256 for _, c := range cases { 257 t.Run("Bootstrap-"+c.base, func(t *testing.T) { 258 if c.setup != nil { 259 c.setup() 260 } 261 if c.teardown != nil { 262 defer c.teardown() 263 } 264 265 proxyConfig, err := loadProxyConfig(c.base, out, t) 266 if err != nil { 267 t.Fatalf("unable to load proxy config: %s\n%v", c.base, err) 268 } 269 270 _, localEnv := createEnv(t, map[string]string{}, c.annotations) 271 for k, v := range c.envVars { 272 localEnv = append(localEnv, k+"="+v) 273 } 274 275 fn, err := New(Config{ 276 Node: "sidecar~1.2.3.4~foo~bar", 277 Proxy: proxyConfig, 278 PlatEnv: &fakePlatform{}, 279 PilotSubjectAltName: []string{ 280 "spiffe://cluster.local/ns/istio-system/sa/istio-pilot-service-account"}, 281 LocalEnv: localEnv, 282 NodeIPs: []string{"10.3.3.3", "10.4.4.4", "10.5.5.5", "10.6.6.6", "10.4.4.4"}, 283 OutlierLogPath: "/dev/stdout", 284 PilotCertProvider: "istiod", 285 }).CreateFileForEpoch(0) 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 read, err := ioutil.ReadFile(fn) 291 if err != nil { 292 t.Error("Error reading generated file ", err) 293 return 294 } 295 296 // apply minor modifications for the generated file so that tests are consistent 297 // across different env setups 298 err = ioutil.WriteFile(fn, correctForEnvDifference(read, !c.checkLocality), 0700) 299 if err != nil { 300 t.Error("Error modifying generated file ", err) 301 return 302 } 303 304 // re-read generated file with the changes having been made 305 read, err = ioutil.ReadFile(fn) 306 if err != nil { 307 t.Error("Error reading generated file ", err) 308 return 309 } 310 311 goldenFile := "testdata/" + c.base + "_golden.json" 312 util.RefreshGoldenFile(read, goldenFile, t) 313 314 golden, err := ioutil.ReadFile(goldenFile) 315 if err != nil { 316 golden = []byte{} 317 } 318 319 realM := v2.Bootstrap{} 320 goldenM := v2.Bootstrap{} 321 322 jgolden, err := yaml.YAMLToJSON(golden) 323 324 if err != nil { 325 t.Fatalf("unable to convert: %s %v", c.base, err) 326 } 327 328 if err = jsonpb.UnmarshalString(string(jgolden), &goldenM); err != nil { 329 t.Fatalf("invalid json %s %s\n%v", c.base, err, string(jgolden)) 330 } 331 332 if err = goldenM.Validate(); err != nil { 333 t.Fatalf("invalid golden %s: %v", c.base, err) 334 } 335 336 jreal, err := yaml.YAMLToJSON(read) 337 338 if err != nil { 339 t.Fatalf("unable to convert: %s (%s) %v", c.base, fn, err) 340 } 341 342 if err = jsonpb.UnmarshalString(string(jreal), &realM); err != nil { 343 t.Fatalf("invalid json %v\n%s", err, string(read)) 344 } 345 346 if err = realM.Validate(); err != nil { 347 t.Fatalf("invalid generated file %s: %v", c.base, err) 348 } 349 350 checkStatsMatcher(t, &realM, &goldenM, c.stats) 351 352 if c.check != nil { 353 c.check(&realM, t) 354 } 355 356 checkOpencensusConfig(t, &realM, &goldenM) 357 358 if !reflect.DeepEqual(realM, goldenM) { 359 s, _ := diff.PrettyDiff(goldenM, realM) 360 t.Logf("difference: %s", s) 361 t.Fatalf("\n got: %v\nwant: %v", realM, goldenM) 362 } 363 364 // Check if the LightStep access token file exists 365 _, err = os.Stat(lightstepAccessTokenFile(path.Dir(fn))) 366 if c.expectLightstepAccessToken { 367 if os.IsNotExist(err) { 368 t.Error("expected to find a LightStep access token file but none found") 369 } else if err != nil { 370 t.Error("error running Stat on file: ", err) 371 } 372 } else { 373 if err == nil { 374 t.Error("found a LightStep access token file but none was expected") 375 } else if !os.IsNotExist(err) { 376 t.Error("error running Stat on file: ", err) 377 } 378 } 379 }) 380 } 381} 382 383func checkListStringMatcher(t *testing.T, got *matcher.ListStringMatcher, want string, typ string) { 384 var patterns []string 385 for _, pattern := range got.GetPatterns() { 386 var pat string 387 switch typ { 388 case "prefix": 389 pat = pattern.GetPrefix() 390 case "suffix": 391 pat = pattern.GetSuffix() 392 case "regexp": 393 // Migration tracked in https://github.com/istio/istio/issues/17127 394 //nolint: staticcheck 395 pat = pattern.GetRegex() 396 } 397 398 if pat != "" { 399 patterns = append(patterns, pat) 400 } 401 } 402 gotPattern := strings.Join(patterns, ",") 403 if want != gotPattern { 404 t.Fatalf("%s mismatch:\ngot: %s\nwant: %s", typ, gotPattern, want) 405 } 406} 407 408func checkOpencensusConfig(t *testing.T, got, want *v2.Bootstrap) { 409 if want.Tracing == nil { 410 return 411 } 412 413 if want.Tracing.Http.Name != "envoy.tracers.opencensus" { 414 return 415 } 416 417 if !reflect.DeepEqual(got.Tracing.Http, want.Tracing.Http) { 418 p, _ := diff.PrettyDiff(got.Tracing.Http, want.Tracing.Http) 419 t.Fatalf("t diff: %v\ngot:\n %v\nwant:\n %v\n", p, got.Tracing.Http, want.Tracing.Http) 420 } 421} 422 423func checkStatsMatcher(t *testing.T, got, want *v2.Bootstrap, stats stats) { 424 gsm := got.GetStatsConfig().GetStatsMatcher() 425 426 if stats.prefixes == "" { 427 stats.prefixes = v2Prefixes + requiredEnvoyStatsMatcherInclusionPrefixes + v2Suffix 428 } else { 429 stats.prefixes = v2Prefixes + stats.prefixes + "," + requiredEnvoyStatsMatcherInclusionPrefixes + v2Suffix 430 } 431 432 if err := gsm.Validate(); err != nil { 433 t.Fatalf("Generated invalid matcher: %v", err) 434 } 435 436 checkListStringMatcher(t, gsm.GetInclusionList(), stats.prefixes, "prefix") 437 checkListStringMatcher(t, gsm.GetInclusionList(), stats.suffixes, "suffix") 438 checkListStringMatcher(t, gsm.GetInclusionList(), stats.regexps, "regexp") 439 440 // remove StatsMatcher for general matching 441 got.StatsConfig.StatsMatcher = nil 442 want.StatsConfig.StatsMatcher = nil 443 444 // remove StatsMatcher metadata from matching 445 delete(got.Node.Metadata.Fields, annotation.SidecarStatsInclusionPrefixes.Name) 446 delete(want.Node.Metadata.Fields, annotation.SidecarStatsInclusionPrefixes.Name) 447 delete(got.Node.Metadata.Fields, annotation.SidecarStatsInclusionSuffixes.Name) 448 delete(want.Node.Metadata.Fields, annotation.SidecarStatsInclusionSuffixes.Name) 449 delete(got.Node.Metadata.Fields, annotation.SidecarStatsInclusionRegexps.Name) 450 delete(want.Node.Metadata.Fields, annotation.SidecarStatsInclusionRegexps.Name) 451} 452 453type regexReplacement struct { 454 pattern *regexp.Regexp 455 replacement []byte 456} 457 458// correctForEnvDifference corrects the portions of a generated bootstrap config that vary depending on the environment 459// so that they match the golden file's expected value. 460func correctForEnvDifference(in []byte, excludeLocality bool) []byte { 461 replacements := []regexReplacement{ 462 // Lightstep access tokens are written to a file and that path is dependent upon the environment variables that 463 // are set. Standardize the path so that golden files can be properly checked. 464 { 465 pattern: regexp.MustCompile(`("access_token_file": ").*(lightstep_access_token.txt")`), 466 replacement: []byte("$1/test-path/$2"), 467 }, 468 { 469 // Example: "customConfigFile":"../../tools/packaging/common/envoy_bootstrap_v2.json" 470 // The path may change in CI/other machines 471 pattern: regexp.MustCompile(`("customConfigFile":").*(envoy_bootstrap_v2.json")`), 472 replacement: []byte(`"customConfigFile":"envoy_bootstrap_v2.json"`), 473 }, 474 } 475 if excludeLocality { 476 // zone and region can vary based on the environment, so it shouldn't be considered in the diff. 477 replacements = append(replacements, 478 regexReplacement{ 479 pattern: regexp.MustCompile(`"zone": ".+"`), 480 replacement: []byte("\"zone\": \"\""), 481 }, 482 regexReplacement{ 483 pattern: regexp.MustCompile(`"region": ".+"`), 484 replacement: []byte("\"region\": \"\""), 485 }) 486 } 487 488 out := in 489 for _, r := range replacements { 490 out = r.pattern.ReplaceAll(out, r.replacement) 491 } 492 return out 493} 494 495func loadProxyConfig(base, out string, _ *testing.T) (*meshconfig.ProxyConfig, error) { 496 content, err := ioutil.ReadFile("testdata/" + base + ".proxycfg") 497 if err != nil { 498 return nil, err 499 } 500 cfg := &meshconfig.ProxyConfig{} 501 err = proto.UnmarshalText(string(content), cfg) 502 if err != nil { 503 return nil, err 504 } 505 506 // Exported from makefile or env 507 cfg.ConfigPath = out + "/bootstrap/" + base 508 gobase := os.Getenv("ISTIO_GO") 509 if gobase == "" { 510 gobase = "../.." 511 } 512 cfg.CustomConfigFile = gobase + "/tools/packaging/common/envoy_bootstrap_v2.json" 513 return cfg, nil 514} 515 516func TestIsIPv6Proxy(t *testing.T) { 517 tests := []struct { 518 name string 519 addrs []string 520 expected bool 521 }{ 522 { 523 name: "ipv4 only", 524 addrs: []string{"1.1.1.1", "127.0.0.1", "2.2.2.2"}, 525 expected: false, 526 }, 527 { 528 name: "ipv6 only", 529 addrs: []string{"1111:2222::1", "::1", "2222:3333::1"}, 530 expected: true, 531 }, 532 { 533 name: "mixed ipv4 and ipv6", 534 addrs: []string{"1111:2222::1", "::1", "127.0.0.1", "2.2.2.2", "2222:3333::1"}, 535 expected: false, 536 }, 537 } 538 for _, tt := range tests { 539 result := isIPv6Proxy(tt.addrs) 540 if result != tt.expected { 541 t.Errorf("Test %s failed, expected: %t got: %t", tt.name, tt.expected, result) 542 } 543 } 544} 545 546// createEnv takes labels and annotations are returns environment in go format. 547func createEnv(t *testing.T, labels map[string]string, anno map[string]string) (map[string]string, []string) { 548 merged := map[string]string{} 549 mergeMap(merged, labels) 550 mergeMap(merged, anno) 551 552 envs := make([]string, 0) 553 554 if labels != nil { 555 envs = append(envs, encodeAsJSON(t, labels, "LABELS")) 556 } 557 558 if anno != nil { 559 envs = append(envs, encodeAsJSON(t, anno, "ANNOTATIONS")) 560 } 561 return merged, envs 562} 563 564func encodeAsJSON(t *testing.T, data map[string]string, name string) string { 565 jsonStr, err := json.Marshal(data) 566 if err != nil { 567 t.Fatalf("failed to marshal %s %v: %v", name, data, err) 568 } 569 return IstioMetaJSONPrefix + name + "=" + string(jsonStr) 570} 571 572func TestNodeMetadataEncodeEnvWithIstioMetaPrefix(t *testing.T) { 573 originalKey := "foo" 574 notIstioMetaKey := "NOT_AN_" + IstioMetaPrefix + originalKey 575 anIstioMetaKey := IstioMetaPrefix + originalKey 576 envs := []string{ 577 notIstioMetaKey + "=bar", 578 anIstioMetaKey + "=baz", 579 } 580 nm, _, err := getNodeMetaData(envs, nil, nil, 0, &meshconfig.ProxyConfig{}) 581 if err != nil { 582 t.Fatal(err) 583 } 584 if _, ok := nm.Raw[notIstioMetaKey]; ok { 585 t.Fatalf("%s should not be encoded in node metadata", notIstioMetaKey) 586 } 587 588 if _, ok := nm.Raw[anIstioMetaKey]; ok { 589 t.Fatalf("%s should not be encoded in node metadata. The prefix '%s' should be stripped", anIstioMetaKey, IstioMetaPrefix) 590 } 591 if val, ok := nm.Raw[originalKey]; !ok { 592 t.Fatalf("%s has the prefix %s and it should be encoded in the node metadata", originalKey, IstioMetaPrefix) 593 } else if val != "baz" { 594 t.Fatalf("unexpected value node metadata %s. got %s, want: %s", originalKey, val, "baz") 595 } 596} 597 598func TestNodeMetadata(t *testing.T) { 599 envs := []string{ 600 "ISTIO_META_ISTIO_VERSION=1.0.0", 601 `ISTIO_METAJSON_LABELS={"foo":"bar"}`, 602 } 603 nm, _, err := getNodeMetaData(envs, nil, nil, 0, &meshconfig.ProxyConfig{}) 604 if err != nil { 605 t.Fatal(err) 606 } 607 if nm.IstioVersion != "1.0.0" { 608 t.Fatalf("Expected IstioVersion 1.0.0, got %v", nm.IstioVersion) 609 } 610 if !reflect.DeepEqual(nm.Labels, map[string]string{"foo": "bar"}) { 611 t.Fatalf("Expected Labels foo: bar, got %v", nm.Labels) 612 } 613} 614 615func mergeMap(to map[string]string, from map[string]string) { 616 for k, v := range from { 617 to[k] = v 618 } 619} 620 621type fakePlatform struct { 622 platform.Environment 623 624 meta map[string]string 625} 626 627func (f *fakePlatform) Metadata() map[string]string { 628 return f.meta 629} 630 631func (f *fakePlatform) Locality() *core.Locality { 632 return &core.Locality{} 633} 634