1
18
19 package bootstrap
20
21 import (
22 "encoding/json"
23 "errors"
24 "fmt"
25 "os"
26 "testing"
27
28 "github.com/google/go-cmp/cmp"
29 "github.com/google/go-cmp/cmp/cmpopts"
30 "google.golang.org/grpc"
31 "google.golang.org/grpc/credentials/tls/certprovider"
32 "google.golang.org/grpc/internal"
33 "google.golang.org/grpc/internal/envconfig"
34 "google.golang.org/grpc/xds/bootstrap"
35 "google.golang.org/protobuf/proto"
36 "google.golang.org/protobuf/types/known/structpb"
37
38 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
39 )
40
41 var (
42 v3BootstrapFileMap = map[string]string{
43 "serverFeaturesIncludesXDSV3": `
44 {
45 "node": {
46 "id": "ENVOY_NODE_ID",
47 "metadata": {
48 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
49 }
50 },
51 "xds_servers" : [{
52 "server_uri": "trafficdirector.googleapis.com:443",
53 "channel_creds": [
54 { "type": "google_default" }
55 ],
56 "server_features" : ["xds_v3"]
57 }]
58 }`,
59 "serverFeaturesExcludesXDSV3": `
60 {
61 "node": {
62 "id": "ENVOY_NODE_ID",
63 "metadata": {
64 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
65 }
66 },
67 "xds_servers" : [{
68 "server_uri": "trafficdirector.googleapis.com:443",
69 "channel_creds": [
70 { "type": "google_default" }
71 ]
72 }]
73 }`,
74 "emptyNodeProto": `
75 {
76 "xds_servers" : [{
77 "server_uri": "trafficdirector.googleapis.com:443",
78 "channel_creds": [
79 { "type": "insecure" }
80 ]
81 }]
82 }`,
83 "unknownTopLevelFieldInFile": `
84 {
85 "node": {
86 "id": "ENVOY_NODE_ID",
87 "metadata": {
88 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
89 }
90 },
91 "xds_servers" : [{
92 "server_uri": "trafficdirector.googleapis.com:443",
93 "channel_creds": [
94 { "type": "insecure" }
95 ]
96 }],
97 "unknownField": "foobar"
98 }`,
99 "unknownFieldInNodeProto": `
100 {
101 "node": {
102 "id": "ENVOY_NODE_ID",
103 "unknownField": "foobar",
104 "metadata": {
105 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
106 }
107 },
108 "xds_servers" : [{
109 "server_uri": "trafficdirector.googleapis.com:443",
110 "channel_creds": [
111 { "type": "insecure" }
112 ]
113 }]
114 }`,
115 "unknownFieldInXdsServer": `
116 {
117 "node": {
118 "id": "ENVOY_NODE_ID",
119 "metadata": {
120 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
121 }
122 },
123 "xds_servers" : [{
124 "server_uri": "trafficdirector.googleapis.com:443",
125 "channel_creds": [
126 { "type": "insecure" }
127 ],
128 "unknownField": "foobar"
129 }]
130 }`,
131 "multipleChannelCreds": `
132 {
133 "node": {
134 "id": "ENVOY_NODE_ID",
135 "metadata": {
136 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
137 }
138 },
139 "xds_servers" : [{
140 "server_uri": "trafficdirector.googleapis.com:443",
141 "channel_creds": [
142 { "type": "not-google-default" },
143 { "type": "google_default" }
144 ],
145 "server_features": ["xds_v3"]
146 }]
147 }`,
148 "goodBootstrap": `
149 {
150 "node": {
151 "id": "ENVOY_NODE_ID",
152 "metadata": {
153 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
154 }
155 },
156 "xds_servers" : [{
157 "server_uri": "trafficdirector.googleapis.com:443",
158 "channel_creds": [
159 { "type": "google_default" }
160 ],
161 "server_features": ["xds_v3"]
162 }]
163 }`,
164 "multipleXDSServers": `
165 {
166 "node": {
167 "id": "ENVOY_NODE_ID",
168 "metadata": {
169 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
170 }
171 },
172 "xds_servers" : [
173 {
174 "server_uri": "trafficdirector.googleapis.com:443",
175 "channel_creds": [{ "type": "google_default" }],
176 "server_features": ["xds_v3"]
177 },
178 {
179 "server_uri": "backup.never.use.com:1234",
180 "channel_creds": [{ "type": "not-google-default" }]
181 }
182 ]
183 }`,
184 "serverSupportsIgnoreResourceDeletion": `
185 {
186 "node": {
187 "id": "ENVOY_NODE_ID",
188 "metadata": {
189 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
190 }
191 },
192 "xds_servers" : [{
193 "server_uri": "trafficdirector.googleapis.com:443",
194 "channel_creds": [
195 { "type": "google_default" }
196 ],
197 "server_features" : ["ignore_resource_deletion", "xds_v3"]
198 }]
199 }`,
200 }
201 metadata = &structpb.Struct{
202 Fields: map[string]*structpb.Value{
203 "TRAFFICDIRECTOR_GRPC_HOSTNAME": {
204 Kind: &structpb.Value_StringValue{StringValue: "trafficdirector"},
205 },
206 },
207 }
208 v3NodeProto = &v3corepb.Node{
209 Id: "ENVOY_NODE_ID",
210 Metadata: metadata,
211 UserAgentName: gRPCUserAgentName,
212 UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
213 ClientFeatures: []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
214 }
215 nilCredsConfigNoServerFeatures = &Config{
216 XDSServer: &ServerConfig{
217 ServerURI: "trafficdirector.googleapis.com:443",
218 Creds: ChannelCreds{Type: "insecure"},
219 },
220 NodeProto: v3NodeProto,
221 ClientDefaultListenerResourceNameTemplate: "%s",
222 }
223 nonNilCredsConfigV3 = &Config{
224 XDSServer: &ServerConfig{
225 ServerURI: "trafficdirector.googleapis.com:443",
226 Creds: ChannelCreds{Type: "google_default"},
227 ServerFeatures: []string{"xds_v3"},
228 },
229 NodeProto: v3NodeProto,
230 ClientDefaultListenerResourceNameTemplate: "%s",
231 }
232 nonNilCredsConfigWithDeletionIgnored = &Config{
233 XDSServer: &ServerConfig{
234 ServerURI: "trafficdirector.googleapis.com:443",
235 Creds: ChannelCreds{Type: "google_default"},
236 IgnoreResourceDeletion: true,
237 ServerFeatures: []string{"ignore_resource_deletion", "xds_v3"},
238 },
239 NodeProto: v3NodeProto,
240 ClientDefaultListenerResourceNameTemplate: "%s",
241 }
242 nonNilCredsConfigNoServerFeatures = &Config{
243 XDSServer: &ServerConfig{
244 ServerURI: "trafficdirector.googleapis.com:443",
245 Creds: ChannelCreds{Type: "google_default"},
246 },
247 NodeProto: v3NodeProto,
248 ClientDefaultListenerResourceNameTemplate: "%s",
249 }
250 )
251
252 func (c *Config) compare(want *Config) error {
253 if diff := cmp.Diff(want, c,
254 cmpopts.EquateEmpty(),
255 cmp.Comparer(proto.Equal),
256 cmp.Comparer(func(a, b grpc.DialOption) bool { return (a != nil) == (b != nil) }),
257 cmp.Transformer("certproviderconfigstring", func(a *certprovider.BuildableConfig) string { return a.String() }),
258 ); diff != "" {
259 return fmt.Errorf("unexpected diff in config (-want, +got):\n%s", diff)
260 }
261 return nil
262 }
263
264 func fileReadFromFileMap(bootstrapFileMap map[string]string, name string) ([]byte, error) {
265 if b, ok := bootstrapFileMap[name]; ok {
266 return []byte(b), nil
267 }
268 return nil, os.ErrNotExist
269 }
270
271 func setupBootstrapOverride(bootstrapFileMap map[string]string) func() {
272 oldFileReadFunc := bootstrapFileReadFunc
273 bootstrapFileReadFunc = func(filename string) ([]byte, error) {
274 return fileReadFromFileMap(bootstrapFileMap, filename)
275 }
276 return func() { bootstrapFileReadFunc = oldFileReadFunc }
277 }
278
279
280
281
282
283
284 func testNewConfigWithFileNameEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
285 origBootstrapFileName := envconfig.XDSBootstrapFileName
286 envconfig.XDSBootstrapFileName = fileName
287 defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
288
289 c, err := NewConfig()
290 if (err != nil) != wantError {
291 t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
292 }
293 if wantError {
294 return
295 }
296 if err := c.compare(wantConfig); err != nil {
297 t.Fatal(err)
298 }
299 }
300
301
302
303 func testNewConfigWithFileContentEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
304 t.Helper()
305 b, err := bootstrapFileReadFunc(fileName)
306 if err != nil {
307 t.Skip(err)
308 }
309 origBootstrapContent := envconfig.XDSBootstrapFileContent
310 envconfig.XDSBootstrapFileContent = string(b)
311 defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
312
313 c, err := NewConfig()
314 if (err != nil) != wantError {
315 t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
316 }
317 if wantError {
318 return
319 }
320 if err := c.compare(wantConfig); err != nil {
321 t.Fatal(err)
322 }
323 }
324
325
326
327 func TestNewConfigV3ProtoFailure(t *testing.T) {
328 bootstrapFileMap := map[string]string{
329 "empty": "",
330 "badJSON": `["test": 123]`,
331 "noBalancerName": `{"node": {"id": "ENVOY_NODE_ID"}}`,
332 "emptyXdsServer": `
333 {
334 "node": {
335 "id": "ENVOY_NODE_ID",
336 "metadata": {
337 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
338 }
339 }
340 }`,
341 "emptyChannelCreds": `
342 {
343 "node": {
344 "id": "ENVOY_NODE_ID",
345 "metadata": {
346 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
347 }
348 },
349 "xds_servers" : [{
350 "server_uri": "trafficdirector.googleapis.com:443"
351 }]
352 }`,
353 "nonGoogleDefaultCreds": `
354 {
355 "node": {
356 "id": "ENVOY_NODE_ID",
357 "metadata": {
358 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
359 }
360 },
361 "xds_servers" : [{
362 "server_uri": "trafficdirector.googleapis.com:443",
363 "channel_creds": [
364 { "type": "not-google-default" }
365 ]
366 }]
367 }`,
368 }
369 cancel := setupBootstrapOverride(bootstrapFileMap)
370 defer cancel()
371
372 tests := []struct {
373 name string
374 wantError bool
375 }{
376 {"nonExistentBootstrapFile", true},
377 {"empty", true},
378 {"badJSON", true},
379 {"noBalancerName", true},
380 {"emptyXdsServer", true},
381 }
382
383 for _, test := range tests {
384 t.Run(test.name, func(t *testing.T) {
385 testNewConfigWithFileNameEnv(t, test.name, true, nil)
386 testNewConfigWithFileContentEnv(t, test.name, true, nil)
387 })
388 }
389 }
390
391
392
393
394 func TestNewConfigV3ProtoSuccess(t *testing.T) {
395 cancel := setupBootstrapOverride(v3BootstrapFileMap)
396 defer cancel()
397
398 tests := []struct {
399 name string
400 wantConfig *Config
401 }{
402 {
403 "emptyNodeProto", &Config{
404 XDSServer: &ServerConfig{
405 ServerURI: "trafficdirector.googleapis.com:443",
406 Creds: ChannelCreds{Type: "insecure"},
407 },
408 NodeProto: &v3corepb.Node{
409 UserAgentName: gRPCUserAgentName,
410 UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
411 ClientFeatures: []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper},
412 },
413 ClientDefaultListenerResourceNameTemplate: "%s",
414 },
415 },
416 {"unknownTopLevelFieldInFile", nilCredsConfigNoServerFeatures},
417 {"unknownFieldInNodeProto", nilCredsConfigNoServerFeatures},
418 {"unknownFieldInXdsServer", nilCredsConfigNoServerFeatures},
419 {"multipleChannelCreds", nonNilCredsConfigV3},
420 {"goodBootstrap", nonNilCredsConfigV3},
421 {"multipleXDSServers", nonNilCredsConfigV3},
422 {"serverSupportsIgnoreResourceDeletion", nonNilCredsConfigWithDeletionIgnored},
423 }
424
425 for _, test := range tests {
426 t.Run(test.name, func(t *testing.T) {
427 testNewConfigWithFileNameEnv(t, test.name, false, test.wantConfig)
428 testNewConfigWithFileContentEnv(t, test.name, false, test.wantConfig)
429 })
430 }
431 }
432
433
434
435
436
437
438
439 func TestNewConfigBootstrapEnvPriority(t *testing.T) {
440 oldFileReadFunc := bootstrapFileReadFunc
441 bootstrapFileReadFunc = func(filename string) ([]byte, error) {
442 return fileReadFromFileMap(v3BootstrapFileMap, filename)
443 }
444 defer func() { bootstrapFileReadFunc = oldFileReadFunc }()
445
446 goodFileName1 := "serverFeaturesIncludesXDSV3"
447 goodConfig1 := nonNilCredsConfigV3
448
449 goodFileName2 := "serverFeaturesExcludesXDSV3"
450 goodFileContent2 := v3BootstrapFileMap[goodFileName2]
451 goodConfig2 := nonNilCredsConfigNoServerFeatures
452
453 origBootstrapFileName := envconfig.XDSBootstrapFileName
454 envconfig.XDSBootstrapFileName = ""
455 defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
456
457 origBootstrapContent := envconfig.XDSBootstrapFileContent
458 envconfig.XDSBootstrapFileContent = ""
459 defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
460
461
462 if _, err := NewConfig(); err == nil {
463 t.Errorf("NewConfig() returned nil error, expected to fail")
464 }
465
466
467 envconfig.XDSBootstrapFileName = goodFileName1
468 envconfig.XDSBootstrapFileContent = ""
469 c, err := NewConfig()
470 if err != nil {
471 t.Errorf("NewConfig() failed: %v", err)
472 }
473 if err := c.compare(goodConfig1); err != nil {
474 t.Error(err)
475 }
476
477 envconfig.XDSBootstrapFileName = ""
478 envconfig.XDSBootstrapFileContent = goodFileContent2
479 c, err = NewConfig()
480 if err != nil {
481 t.Errorf("NewConfig() failed: %v", err)
482 }
483 if err := c.compare(goodConfig2); err != nil {
484 t.Error(err)
485 }
486
487
488 envconfig.XDSBootstrapFileName = goodFileName1
489 envconfig.XDSBootstrapFileContent = goodFileContent2
490 c, err = NewConfig()
491 if err != nil {
492 t.Errorf("NewConfig() failed: %v", err)
493 }
494 if err := c.compare(goodConfig1); err != nil {
495 t.Error(err)
496 }
497 }
498
499 func init() {
500 certprovider.Register(&fakeCertProviderBuilder{})
501 }
502
503 const fakeCertProviderName = "fake-certificate-provider"
504
505
506
507 type fakeCertProviderBuilder struct{}
508
509
510
511 func (b *fakeCertProviderBuilder) ParseConfig(cfg any) (*certprovider.BuildableConfig, error) {
512 config, ok := cfg.(json.RawMessage)
513 if !ok {
514 return nil, fmt.Errorf("fakeCertProviderBuilder received config of type %T, want []byte", config)
515 }
516 var cfgData map[string]string
517 if err := json.Unmarshal(config, &cfgData); err != nil {
518 return nil, fmt.Errorf("fakeCertProviderBuilder config parsing failed: %v", err)
519 }
520 if len(cfgData) != 1 || cfgData["configKey"] == "" {
521 return nil, errors.New("fakeCertProviderBuilder received invalid config")
522 }
523 fc := &fakeStableConfig{config: cfgData}
524 return certprovider.NewBuildableConfig(fakeCertProviderName, fc.canonical(), func(certprovider.BuildOptions) certprovider.Provider {
525 return &fakeCertProvider{}
526 }), nil
527 }
528
529 func (b *fakeCertProviderBuilder) Name() string {
530 return fakeCertProviderName
531 }
532
533 type fakeStableConfig struct {
534 config map[string]string
535 }
536
537 func (c *fakeStableConfig) canonical() []byte {
538 var cfg string
539 for k, v := range c.config {
540 cfg = fmt.Sprintf("%s:%s", k, v)
541 }
542 return []byte(cfg)
543 }
544
545
546 type fakeCertProvider struct {
547 certprovider.Provider
548 }
549
550 func TestNewConfigWithCertificateProviders(t *testing.T) {
551 bootstrapFileMap := map[string]string{
552 "badJSONCertProviderConfig": `
553 {
554 "node": {
555 "id": "ENVOY_NODE_ID",
556 "metadata": {
557 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
558 }
559 },
560 "xds_servers" : [{
561 "server_uri": "trafficdirector.googleapis.com:443",
562 "channel_creds": [
563 { "type": "google_default" }
564 ],
565 "server_features" : ["foo", "bar", "xds_v3"],
566 }],
567 "certificate_providers": "bad JSON"
568 }`,
569 "allUnknownCertProviders": `
570 {
571 "node": {
572 "id": "ENVOY_NODE_ID",
573 "metadata": {
574 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
575 }
576 },
577 "xds_servers" : [{
578 "server_uri": "trafficdirector.googleapis.com:443",
579 "channel_creds": [
580 { "type": "google_default" }
581 ],
582 "server_features" : ["xds_v3"]
583 }],
584 "certificate_providers": {
585 "unknownProviderInstance1": {
586 "plugin_name": "foo",
587 "config": {"foo": "bar"}
588 },
589 "unknownProviderInstance2": {
590 "plugin_name": "bar",
591 "config": {"foo": "bar"}
592 }
593 }
594 }`,
595 "badCertProviderConfig": `
596 {
597 "node": {
598 "id": "ENVOY_NODE_ID",
599 "metadata": {
600 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
601 }
602 },
603 "xds_servers" : [{
604 "server_uri": "trafficdirector.googleapis.com:443",
605 "channel_creds": [
606 { "type": "google_default" }
607 ],
608 "server_features" : ["xds_v3"],
609 }],
610 "certificate_providers": {
611 "unknownProviderInstance": {
612 "plugin_name": "foo",
613 "config": {"foo": "bar"}
614 },
615 "fakeProviderInstanceBad": {
616 "plugin_name": "fake-certificate-provider",
617 "config": {"configKey": 666}
618 }
619 }
620 }`,
621 "goodCertProviderConfig": `
622 {
623 "node": {
624 "id": "ENVOY_NODE_ID",
625 "metadata": {
626 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
627 }
628 },
629 "xds_servers" : [{
630 "server_uri": "trafficdirector.googleapis.com:443",
631 "channel_creds": [
632 { "type": "insecure" }
633 ],
634 "server_features" : ["xds_v3"]
635 }],
636 "certificate_providers": {
637 "unknownProviderInstance": {
638 "plugin_name": "foo",
639 "config": {"foo": "bar"}
640 },
641 "fakeProviderInstance": {
642 "plugin_name": "fake-certificate-provider",
643 "config": {"configKey": "configValue"}
644 }
645 }
646 }`,
647 }
648
649 getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
650 parser := getBuilder(fakeCertProviderName)
651 if parser == nil {
652 t.Fatalf("missing certprovider plugin %q", fakeCertProviderName)
653 }
654 wantCfg, err := parser.ParseConfig(json.RawMessage(`{"configKey": "configValue"}`))
655 if err != nil {
656 t.Fatalf("config parsing for plugin %q failed: %v", fakeCertProviderName, err)
657 }
658
659 cancel := setupBootstrapOverride(bootstrapFileMap)
660 defer cancel()
661
662
663
664 jsonCfg := `{
665 "server_uri": "trafficdirector.googleapis.com:443",
666 "channel_creds": [{"type": "insecure"}],
667 "server_features": ["xds_v3"]
668 }`
669 serverCfg, err := ServerConfigFromJSON([]byte(jsonCfg))
670 if err != nil {
671 t.Fatalf("Failed to create server config from JSON %s: %v", jsonCfg, err)
672 }
673 goodConfig := &Config{
674 XDSServer: serverCfg,
675 NodeProto: v3NodeProto,
676 CertProviderConfigs: map[string]*certprovider.BuildableConfig{
677 "fakeProviderInstance": wantCfg,
678 },
679 ClientDefaultListenerResourceNameTemplate: "%s",
680 }
681 tests := []struct {
682 name string
683 wantConfig *Config
684 wantErr bool
685 }{
686 {
687 name: "badJSONCertProviderConfig",
688 wantErr: true,
689 },
690 {
691
692 name: "badCertProviderConfig",
693 wantErr: true,
694 },
695 {
696
697 name: "allUnknownCertProviders",
698 wantConfig: nonNilCredsConfigV3,
699 },
700 {
701 name: "goodCertProviderConfig",
702 wantConfig: goodConfig,
703 },
704 }
705
706 for _, test := range tests {
707 t.Run(test.name, func(t *testing.T) {
708 testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
709 testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
710 })
711 }
712 }
713
714 func TestNewConfigWithServerListenerResourceNameTemplate(t *testing.T) {
715 cancel := setupBootstrapOverride(map[string]string{
716 "badServerListenerResourceNameTemplate:": `
717 {
718 "node": {
719 "id": "ENVOY_NODE_ID",
720 "metadata": {
721 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
722 }
723 },
724 "xds_servers" : [{
725 "server_uri": "trafficdirector.googleapis.com:443",
726 "channel_creds": [
727 { "type": "google_default" }
728 ]
729 }],
730 "server_listener_resource_name_template": 123456789
731 }`,
732 "goodServerListenerResourceNameTemplate": `
733 {
734 "node": {
735 "id": "ENVOY_NODE_ID",
736 "metadata": {
737 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
738 }
739 },
740 "xds_servers" : [{
741 "server_uri": "trafficdirector.googleapis.com:443",
742 "channel_creds": [
743 { "type": "google_default" }
744 ]
745 }],
746 "server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s"
747 }`,
748 })
749 defer cancel()
750
751 tests := []struct {
752 name string
753 wantConfig *Config
754 wantErr bool
755 }{
756 {
757 name: "badServerListenerResourceNameTemplate",
758 wantErr: true,
759 },
760 {
761 name: "goodServerListenerResourceNameTemplate",
762 wantConfig: &Config{
763 XDSServer: &ServerConfig{
764 ServerURI: "trafficdirector.googleapis.com:443",
765 Creds: ChannelCreds{Type: "google_default"},
766 },
767 NodeProto: v3NodeProto,
768 ServerListenerResourceNameTemplate: "grpc/server?xds.resource.listening_address=%s",
769 ClientDefaultListenerResourceNameTemplate: "%s",
770 },
771 },
772 }
773
774 for _, test := range tests {
775 t.Run(test.name, func(t *testing.T) {
776 testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
777 testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
778 })
779 }
780 }
781
782 func TestNewConfigWithFederation(t *testing.T) {
783 cancel := setupBootstrapOverride(map[string]string{
784 "badClientListenerResourceNameTemplate": `
785 {
786 "node": { "id": "ENVOY_NODE_ID" },
787 "xds_servers" : [{
788 "server_uri": "trafficdirector.googleapis.com:443"
789 }],
790 "client_default_listener_resource_name_template": 123456789
791 }`,
792 "badClientListenerResourceNameTemplatePerAuthority": `
793 {
794 "node": { "id": "ENVOY_NODE_ID" },
795 "xds_servers" : [{
796 "server_uri": "trafficdirector.googleapis.com:443",
797 "channel_creds": [ { "type": "google_default" } ]
798 }],
799 "authorities": {
800 "xds.td.com": {
801 "client_listener_resource_name_template": "some/template/%s",
802 "xds_servers": [{
803 "server_uri": "td.com",
804 "channel_creds": [ { "type": "google_default" } ],
805 "server_features" : ["foo", "bar", "xds_v3"]
806 }]
807 }
808 }
809 }`,
810 "good": `
811 {
812 "node": {
813 "id": "ENVOY_NODE_ID",
814 "metadata": {
815 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
816 }
817 },
818 "xds_servers" : [{
819 "server_uri": "trafficdirector.googleapis.com:443",
820 "channel_creds": [ { "type": "google_default" } ]
821 }],
822 "server_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
823 "client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
824 "authorities": {
825 "xds.td.com": {
826 "client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
827 "xds_servers": [{
828 "server_uri": "td.com",
829 "channel_creds": [ { "type": "google_default" } ],
830 "server_features" : ["xds_v3"]
831 }]
832 }
833 }
834 }`,
835
836
837 "goodWithDefaultDefaultClientListenerTemplate": `
838 {
839 "node": {
840 "id": "ENVOY_NODE_ID",
841 "metadata": {
842 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
843 }
844 },
845 "xds_servers" : [{
846 "server_uri": "trafficdirector.googleapis.com:443",
847 "channel_creds": [ { "type": "google_default" } ]
848 }]
849 }`,
850
851
852
853 "goodWithDefaultClientListenerTemplatePerAuthority": `
854 {
855 "node": {
856 "id": "ENVOY_NODE_ID",
857 "metadata": {
858 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
859 }
860 },
861 "xds_servers" : [{
862 "server_uri": "trafficdirector.googleapis.com:443",
863 "channel_creds": [ { "type": "google_default" } ]
864 }],
865 "client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
866 "authorities": {
867 "xds.td.com": { },
868 "#.com": { }
869 }
870 }`,
871
872
873 "goodWithNoServerPerAuthority": `
874 {
875 "node": {
876 "id": "ENVOY_NODE_ID",
877 "metadata": {
878 "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
879 }
880 },
881 "xds_servers" : [{
882 "server_uri": "trafficdirector.googleapis.com:443",
883 "channel_creds": [ { "type": "google_default" } ]
884 }],
885 "client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
886 "authorities": {
887 "xds.td.com": {
888 "client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s"
889 }
890 }
891 }`,
892 })
893 defer cancel()
894
895 tests := []struct {
896 name string
897 wantConfig *Config
898 wantErr bool
899 }{
900 {
901 name: "badClientListenerResourceNameTemplate",
902 wantErr: true,
903 },
904 {
905 name: "badClientListenerResourceNameTemplatePerAuthority",
906 wantErr: true,
907 },
908 {
909 name: "good",
910 wantConfig: &Config{
911 XDSServer: &ServerConfig{
912 ServerURI: "trafficdirector.googleapis.com:443",
913 Creds: ChannelCreds{Type: "google_default"},
914 },
915 NodeProto: v3NodeProto,
916 ServerListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
917 ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
918 Authorities: map[string]*Authority{
919 "xds.td.com": {
920 ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
921 XDSServer: &ServerConfig{
922 ServerURI: "td.com",
923 Creds: ChannelCreds{Type: "google_default"},
924 ServerFeatures: []string{"xds_v3"},
925 },
926 },
927 },
928 },
929 },
930 {
931 name: "goodWithDefaultDefaultClientListenerTemplate",
932 wantConfig: &Config{
933 XDSServer: &ServerConfig{
934 ServerURI: "trafficdirector.googleapis.com:443",
935 Creds: ChannelCreds{Type: "google_default"},
936 },
937 NodeProto: v3NodeProto,
938 ClientDefaultListenerResourceNameTemplate: "%s",
939 },
940 },
941 {
942 name: "goodWithDefaultClientListenerTemplatePerAuthority",
943 wantConfig: &Config{
944 XDSServer: &ServerConfig{
945 ServerURI: "trafficdirector.googleapis.com:443",
946 Creds: ChannelCreds{Type: "google_default"},
947 },
948 NodeProto: v3NodeProto,
949 ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
950 Authorities: map[string]*Authority{
951 "xds.td.com": {
952 ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
953 },
954 "#.com": {
955 ClientListenerResourceNameTemplate: "xdstp://%23.com/envoy.config.listener.v3.Listener/%s",
956 },
957 },
958 },
959 },
960 {
961 name: "goodWithNoServerPerAuthority",
962 wantConfig: &Config{
963 XDSServer: &ServerConfig{
964 ServerURI: "trafficdirector.googleapis.com:443",
965 Creds: ChannelCreds{Type: "google_default"},
966 },
967 NodeProto: v3NodeProto,
968 ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
969 Authorities: map[string]*Authority{
970 "xds.td.com": {
971 ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
972 },
973 },
974 },
975 },
976 }
977
978 for _, test := range tests {
979 t.Run(test.name, func(t *testing.T) {
980 testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
981 testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
982 })
983 }
984 }
985
986 func TestServerConfigMarshalAndUnmarshal(t *testing.T) {
987 jsonCfg := `{
988 "server_uri": "test-server",
989 "channel_creds": [{"type": "insecure"}],
990 "server_features": ["xds_v3"]
991 }`
992 origConfig, err := ServerConfigFromJSON([]byte(jsonCfg))
993 if err != nil {
994 t.Fatalf("Failed to create server config from JSON %s: %v", jsonCfg, err)
995 }
996 bs, err := json.Marshal(origConfig)
997 if err != nil {
998 t.Fatalf("failed to marshal: %v", err)
999 }
1000
1001 unmarshaledConfig := new(ServerConfig)
1002 if err := json.Unmarshal(bs, unmarshaledConfig); err != nil {
1003 t.Fatalf("failed to unmarshal: %v", err)
1004 }
1005 if diff := cmp.Diff(origConfig, unmarshaledConfig); diff != "" {
1006 t.Fatalf("Unexpected diff in server config (-want, +got):\n%s", diff)
1007 }
1008 }
1009
1010 func TestDefaultBundles(t *testing.T) {
1011 tests := []string{"google_default", "insecure", "tls"}
1012
1013 for _, typename := range tests {
1014 t.Run(typename, func(t *testing.T) {
1015 if c := bootstrap.GetCredentials(typename); c == nil {
1016 t.Errorf(`bootstrap.GetCredentials(%s) credential is nil, want non-nil`, typename)
1017 }
1018 })
1019 }
1020 }
1021
View as plain text