dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/bootstrap/bootstrap_test.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  /*
    19   *
    20   * Copyright 2019 gRPC authors.
    21   *
    22   */
    23  
    24  package bootstrap
    25  
    26  import (
    27  	"encoding/json"
    28  	"errors"
    29  	"fmt"
    30  	"os"
    31  	"testing"
    32  )
    33  
    34  import (
    35  	v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
    36  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    37  
    38  	"github.com/golang/protobuf/proto"
    39  	structpb "github.com/golang/protobuf/ptypes/struct"
    40  
    41  	"github.com/google/go-cmp/cmp"
    42  	"github.com/google/go-cmp/cmp/cmpopts"
    43  
    44  	"google.golang.org/grpc"
    45  	"google.golang.org/grpc/credentials/google"
    46  	"google.golang.org/grpc/credentials/insecure"
    47  	"google.golang.org/grpc/credentials/tls/certprovider"
    48  )
    49  
    50  import (
    51  	"dubbo.apache.org/dubbo-go/v3/xds/client/resource/version"
    52  	"dubbo.apache.org/dubbo-go/v3/xds/utils/envconfig"
    53  )
    54  
    55  var (
    56  	v2BootstrapFileMap = map[string]string{
    57  		"emptyNodeProto": `
    58  		{
    59  			"xds_servers" : [{
    60  				"server_uri": "trafficdirector.googleapis.com:443",
    61  				"channel_creds": [
    62  					{ "type": "insecure" }
    63  				]
    64  			}]
    65  		}`,
    66  		"unknownTopLevelFieldInFile": `
    67  		{
    68  			"node": {
    69  				"id": "ENVOY_NODE_ID",
    70  				"metadata": {
    71  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    72  			    }
    73  			},
    74  			"xds_servers" : [{
    75  				"server_uri": "trafficdirector.googleapis.com:443",
    76  				"channel_creds": [
    77  					{ "type": "insecure" }
    78  				]
    79  			}],
    80  			"unknownField": "foobar"
    81  		}`,
    82  		"unknownFieldInNodeProto": `
    83  		{
    84  			"node": {
    85  				"id": "ENVOY_NODE_ID",
    86  				"unknownField": "foobar",
    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  		}`,
    98  		"unknownFieldInXdsServer": `
    99  		{
   100  			"node": {
   101  				"id": "ENVOY_NODE_ID",
   102  				"metadata": {
   103  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   104  			    }
   105  			},
   106  			"xds_servers" : [{
   107  				"server_uri": "trafficdirector.googleapis.com:443",
   108  				"channel_creds": [
   109  					{ "type": "insecure" }
   110  				],
   111  				"unknownField": "foobar"
   112  			}]
   113  		}`,
   114  		"multipleChannelCreds": `
   115  		{
   116  			"node": {
   117  				"id": "ENVOY_NODE_ID",
   118  				"metadata": {
   119  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   120  			    }
   121  			},
   122  			"xds_servers" : [{
   123  				"server_uri": "trafficdirector.googleapis.com:443",
   124  				"channel_creds": [
   125  					{ "type": "not-google-default" },
   126  					{ "type": "google_default" }
   127  				]
   128  			}]
   129  		}`,
   130  		"goodBootstrap": `
   131  		{
   132  			"node": {
   133  				"id": "ENVOY_NODE_ID",
   134  				"metadata": {
   135  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   136  			    }
   137  			},
   138  			"xds_servers" : [{
   139  				"server_uri": "trafficdirector.googleapis.com:443",
   140  				"channel_creds": [
   141  					{ "type": "google_default" }
   142  				]
   143  			}]
   144  		}`,
   145  		"multipleXDSServers": `
   146  		{
   147  			"node": {
   148  				"id": "ENVOY_NODE_ID",
   149  				"metadata": {
   150  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   151  			    }
   152  			},
   153  			"xds_servers" : [
   154  				{
   155  					"server_uri": "trafficdirector.googleapis.com:443",
   156  					"channel_creds": [{ "type": "google_default" }]
   157  				},
   158  				{
   159  					"server_uri": "backup.never.use.com:1234",
   160  					"channel_creds": [{ "type": "not-google-default" }]
   161  				}
   162  			]
   163  		}`,
   164  	}
   165  	v3BootstrapFileMap = map[string]string{
   166  		"serverDoesNotSupportsV3": `
   167  		{
   168  			"node": {
   169  				"id": "ENVOY_NODE_ID",
   170  				"metadata": {
   171  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   172  			    }
   173  			},
   174  			"xds_servers" : [{
   175  				"server_uri": "trafficdirector.googleapis.com:443",
   176  				"channel_creds": [
   177  					{ "type": "google_default" }
   178  				],
   179  				"server_features" : ["foo", "bar"]
   180  			}]
   181  		}`,
   182  		"serverSupportsV3": `
   183  		{
   184  			"node": {
   185  				"id": "ENVOY_NODE_ID",
   186  				"metadata": {
   187  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   188  			    }
   189  			},
   190  			"xds_servers" : [{
   191  				"server_uri": "trafficdirector.googleapis.com:443",
   192  				"channel_creds": [
   193  					{ "type": "google_default" }
   194  				],
   195  				"server_features" : ["foo", "bar", "xds_v3"]
   196  			}]
   197  		}`,
   198  	}
   199  	metadata = &structpb.Struct{
   200  		Fields: map[string]*structpb.Value{
   201  			"TRAFFICDIRECTOR_GRPC_HOSTNAME": {
   202  				Kind: &structpb.Value_StringValue{StringValue: "trafficdirector"},
   203  			},
   204  		},
   205  	}
   206  	v2NodeProto = &v2corepb.Node{
   207  		Id:                   "ENVOY_NODE_ID",
   208  		Metadata:             metadata,
   209  		BuildVersion:         gRPCVersion,
   210  		UserAgentName:        gRPCUserAgentName,
   211  		UserAgentVersionType: &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   212  		ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   213  	}
   214  	v3NodeProto = &v3corepb.Node{
   215  		Id:                   "ENVOY_NODE_ID",
   216  		Metadata:             metadata,
   217  		UserAgentName:        gRPCUserAgentName,
   218  		UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   219  		ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   220  	}
   221  	nilCredsConfigV2 = &Config{
   222  		XDSServer: &ServerConfig{
   223  			ServerURI: "trafficdirector.googleapis.com:443",
   224  			Creds:     grpc.WithTransportCredentials(insecure.NewCredentials()),
   225  			CredsType: "insecure",
   226  			NodeProto: v2NodeProto,
   227  		},
   228  		ClientDefaultListenerResourceNameTemplate: "%s",
   229  	}
   230  	nonNilCredsConfigV2 = &Config{
   231  		XDSServer: &ServerConfig{
   232  			ServerURI: "trafficdirector.googleapis.com:443",
   233  			Creds:     grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   234  			CredsType: "google_default",
   235  			NodeProto: v2NodeProto,
   236  		},
   237  		ClientDefaultListenerResourceNameTemplate: "%s",
   238  	}
   239  	nonNilCredsConfigV3 = &Config{
   240  		XDSServer: &ServerConfig{
   241  			ServerURI:    "trafficdirector.googleapis.com:443",
   242  			Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   243  			CredsType:    "google_default",
   244  			TransportAPI: version.TransportV3,
   245  			NodeProto:    v3NodeProto,
   246  		},
   247  		ClientDefaultListenerResourceNameTemplate: "%s",
   248  	}
   249  )
   250  
   251  func (c *Config) compare(want *Config) error {
   252  	if diff := cmp.Diff(c, want,
   253  		cmpopts.EquateEmpty(),
   254  		cmp.AllowUnexported(ServerConfig{}),
   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("diff: %v", 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  // TODO: enable leak check for this package when
   280  // https://github.com/googleapis/google-cloud-go/issues/2417 is fixed.
   281  
   282  // This function overrides the bootstrap file NAME env variable, to test the
   283  // code that reads file with the given fileName.
   284  func testNewConfigWithFileNameEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   285  	t.Helper()
   286  	origBootstrapFileName := envconfig.XDSBootstrapFileName
   287  	envconfig.XDSBootstrapFileName = fileName
   288  	defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
   289  
   290  	c, err := NewConfig()
   291  	if (err != nil) != wantError {
   292  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   293  	}
   294  	if wantError {
   295  		return
   296  	}
   297  	if err := c.compare(wantConfig); err != nil {
   298  		t.Fatal(err)
   299  	}
   300  }
   301  
   302  // This function overrides the bootstrap file CONTENT env variable, to test the
   303  // code that uses the content from env directly.
   304  func testNewConfigWithFileContentEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   305  	t.Helper()
   306  	b, err := bootstrapFileReadFunc(fileName)
   307  	if err != nil {
   308  		t.Skip(err)
   309  	}
   310  	origBootstrapContent := envconfig.XDSBootstrapFileContent
   311  	envconfig.XDSBootstrapFileContent = string(b)
   312  	defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
   313  
   314  	c, err := NewConfig()
   315  	if (err != nil) != wantError {
   316  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   317  	}
   318  	if wantError {
   319  		return
   320  	}
   321  	if err := c.compare(wantConfig); err != nil {
   322  		t.Fatal(err)
   323  	}
   324  }
   325  
   326  // TestNewConfigV2ProtoFailure exercises the functionality in NewConfig with
   327  // different bootstrap file contents which are expected to fail.
   328  func TestNewConfigV2ProtoFailure(t *testing.T) {
   329  	bootstrapFileMap := map[string]string{
   330  		"empty":          "",
   331  		"badJSON":        `["test": 123]`,
   332  		"noBalancerName": `{"node": {"id": "ENVOY_NODE_ID"}}`,
   333  		"emptyXdsServer": `
   334  		{
   335  			"node": {
   336  				"id": "ENVOY_NODE_ID",
   337  				"metadata": {
   338  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   339  			    }
   340  			}
   341  		}`,
   342  		"emptyChannelCreds": `
   343  		{
   344  			"node": {
   345  				"id": "ENVOY_NODE_ID",
   346  				"metadata": {
   347  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   348  			    }
   349  			},
   350  			"xds_servers" : [{
   351  				"server_uri": "trafficdirector.googleapis.com:443"
   352  			}]
   353  		}`,
   354  		"nonGoogleDefaultCreds": `
   355  		{
   356  			"node": {
   357  				"id": "ENVOY_NODE_ID",
   358  				"metadata": {
   359  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   360  			    }
   361  			},
   362  			"xds_servers" : [{
   363  				"server_uri": "trafficdirector.googleapis.com:443",
   364  				"channel_creds": [
   365  					{ "type": "not-google-default" }
   366  				]
   367  			}]
   368  		}`,
   369  	}
   370  	cancel := setupBootstrapOverride(bootstrapFileMap)
   371  	defer cancel()
   372  
   373  	tests := []struct {
   374  		name      string
   375  		wantError bool
   376  	}{
   377  		{"nonExistentBootstrapFile", true},
   378  		{"empty", true},
   379  		{"badJSON", true},
   380  		{"noBalancerName", true},
   381  		{"emptyXdsServer", true},
   382  	}
   383  
   384  	for _, test := range tests {
   385  		t.Run(test.name, func(t *testing.T) {
   386  			testNewConfigWithFileNameEnv(t, test.name, true, nil)
   387  			testNewConfigWithFileContentEnv(t, test.name, true, nil)
   388  		})
   389  	}
   390  }
   391  
   392  // TestNewConfigV2ProtoSuccess exercises the functionality in NewConfig with
   393  // different bootstrap file contents. It overrides the fileReadFunc by returning
   394  // bootstrap file contents defined in this test, instead of reading from a file.
   395  func TestNewConfigV2ProtoSuccess(t *testing.T) {
   396  	cancel := setupBootstrapOverride(v2BootstrapFileMap)
   397  	defer cancel()
   398  
   399  	tests := []struct {
   400  		name       string
   401  		wantConfig *Config
   402  	}{
   403  		{
   404  			"emptyNodeProto", &Config{
   405  				XDSServer: &ServerConfig{
   406  					ServerURI: "trafficdirector.googleapis.com:443",
   407  					Creds:     grpc.WithTransportCredentials(insecure.NewCredentials()),
   408  					CredsType: "insecure",
   409  					NodeProto: &v2corepb.Node{
   410  						BuildVersion:         gRPCVersion,
   411  						UserAgentName:        gRPCUserAgentName,
   412  						UserAgentVersionType: &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   413  						ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   414  					},
   415  				},
   416  				ClientDefaultListenerResourceNameTemplate: "%s",
   417  			},
   418  		},
   419  		{"unknownTopLevelFieldInFile", nilCredsConfigV2},
   420  		{"unknownFieldInNodeProto", nilCredsConfigV2},
   421  		{"unknownFieldInXdsServer", nilCredsConfigV2},
   422  		{"multipleChannelCreds", nonNilCredsConfigV2},
   423  		{"goodBootstrap", nonNilCredsConfigV2},
   424  		{"multipleXDSServers", nonNilCredsConfigV2},
   425  	}
   426  
   427  	for _, test := range tests {
   428  		t.Run(test.name, func(t *testing.T) {
   429  			testNewConfigWithFileNameEnv(t, test.name, false, test.wantConfig)
   430  			testNewConfigWithFileContentEnv(t, test.name, false, test.wantConfig)
   431  		})
   432  	}
   433  }
   434  
   435  // TestNewConfigV3Support verifies bootstrap functionality involving support for
   436  // the xDS v3 transport protocol. Here the client ends up using v2 or v3 based
   437  // on what the server supports.
   438  func TestNewConfigV3Support(t *testing.T) {
   439  	cancel := setupBootstrapOverride(v3BootstrapFileMap)
   440  	defer cancel()
   441  
   442  	tests := []struct {
   443  		name       string
   444  		wantConfig *Config
   445  	}{
   446  		{"serverDoesNotSupportsV3", nonNilCredsConfigV2},
   447  		{"serverSupportsV3", nonNilCredsConfigV3},
   448  	}
   449  
   450  	for _, test := range tests {
   451  		t.Run(test.name, func(t *testing.T) {
   452  			testNewConfigWithFileNameEnv(t, test.name, false, test.wantConfig)
   453  			testNewConfigWithFileContentEnv(t, test.name, false, test.wantConfig)
   454  		})
   455  	}
   456  }
   457  
   458  // TestNewConfigBootstrapEnvPriority tests that the two env variables are read
   459  // in correct priority.
   460  //
   461  // the case where the bootstrap file
   462  // environment variable is not set.
   463  func TestNewConfigBootstrapEnvPriority(t *testing.T) {
   464  	oldFileReadFunc := bootstrapFileReadFunc
   465  	bootstrapFileReadFunc = func(filename string) ([]byte, error) {
   466  		return fileReadFromFileMap(v2BootstrapFileMap, filename)
   467  	}
   468  	defer func() { bootstrapFileReadFunc = oldFileReadFunc }()
   469  
   470  	goodFileName1 := "goodBootstrap"
   471  	goodConfig1 := nonNilCredsConfigV2
   472  
   473  	goodFileName2 := "serverSupportsV3"
   474  	goodFileContent2 := v3BootstrapFileMap[goodFileName2]
   475  	goodConfig2 := nonNilCredsConfigV3
   476  
   477  	origBootstrapFileName := envconfig.XDSBootstrapFileName
   478  	envconfig.XDSBootstrapFileName = ""
   479  	defer func() { envconfig.XDSBootstrapFileName = origBootstrapFileName }()
   480  
   481  	origBootstrapContent := envconfig.XDSBootstrapFileContent
   482  	envconfig.XDSBootstrapFileContent = ""
   483  	defer func() { envconfig.XDSBootstrapFileContent = origBootstrapContent }()
   484  
   485  	// When both env variables are empty, NewConfig should fail.
   486  	if _, err := NewConfig(); err == nil {
   487  		t.Errorf("NewConfig() returned nil error, expected to fail")
   488  	}
   489  
   490  	// When one of them is set, it should be used.
   491  	envconfig.XDSBootstrapFileName = goodFileName1
   492  	envconfig.XDSBootstrapFileContent = ""
   493  	if c, err := NewConfig(); err != nil || c.compare(goodConfig1) != nil {
   494  		t.Errorf("NewConfig() = %v, %v, want: %v, %v", c, err, goodConfig1, nil)
   495  	}
   496  
   497  	envconfig.XDSBootstrapFileName = ""
   498  	envconfig.XDSBootstrapFileContent = goodFileContent2
   499  	if c, err := NewConfig(); err != nil || c.compare(goodConfig2) != nil {
   500  		t.Errorf("NewConfig() = %v, %v, want: %v, %v", c, err, goodConfig1, nil)
   501  	}
   502  
   503  	// Set both, file name should be read.
   504  	envconfig.XDSBootstrapFileName = goodFileName1
   505  	envconfig.XDSBootstrapFileContent = goodFileContent2
   506  	if c, err := NewConfig(); err != nil || c.compare(goodConfig1) != nil {
   507  		t.Errorf("NewConfig() = %v, %v, want: %v, %v", c, err, goodConfig1, nil)
   508  	}
   509  }
   510  
   511  func init() {
   512  	certprovider.Register(&fakeCertProviderBuilder{})
   513  }
   514  
   515  const fakeCertProviderName = "fake-certificate-provider"
   516  
   517  // fakeCertProviderBuilder builds new instances of fakeCertProvider and
   518  // interprets the config provided to it as JSON with a single key and value.
   519  type fakeCertProviderBuilder struct{}
   520  
   521  // ParseConfig expects input in JSON format containing a map from string to
   522  // string, with a single entry and mapKey being "configKey".
   523  func (b *fakeCertProviderBuilder) ParseConfig(cfg interface{}) (*certprovider.BuildableConfig, error) {
   524  	config, ok := cfg.(json.RawMessage)
   525  	if !ok {
   526  		return nil, fmt.Errorf("fakeCertProviderBuilder received config of type %T, want []byte", config)
   527  	}
   528  	var cfgData map[string]string
   529  	if err := json.Unmarshal(config, &cfgData); err != nil {
   530  		return nil, fmt.Errorf("fakeCertProviderBuilder config parsing failed: %v", err)
   531  	}
   532  	if len(cfgData) != 1 || cfgData["configKey"] == "" {
   533  		return nil, errors.New("fakeCertProviderBuilder received invalid config")
   534  	}
   535  	fc := &fakeStableConfig{config: cfgData}
   536  	return certprovider.NewBuildableConfig(fakeCertProviderName, fc.canonical(), func(certprovider.BuildOptions) certprovider.Provider {
   537  		return &fakeCertProvider{}
   538  	}), nil
   539  }
   540  
   541  func (b *fakeCertProviderBuilder) Name() string {
   542  	return fakeCertProviderName
   543  }
   544  
   545  type fakeStableConfig struct {
   546  	config map[string]string
   547  }
   548  
   549  func (c *fakeStableConfig) canonical() []byte {
   550  	var cfg string
   551  	for k, v := range c.config {
   552  		cfg = fmt.Sprintf("%s:%s", k, v)
   553  	}
   554  	return []byte(cfg)
   555  }
   556  
   557  // fakeCertProvider is an empty implementation of the Provider interface.
   558  type fakeCertProvider struct {
   559  	certprovider.Provider
   560  }
   561  
   562  func TestNewConfigWithCertificateProviders(t *testing.T) {
   563  	return
   564  	/*bootstrapFileMap := map[string]string{
   565  		"badJSONCertProviderConfig": `
   566  		{
   567  			"node": {
   568  				"id": "ENVOY_NODE_ID",
   569  				"metadata": {
   570  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   571  			    }
   572  			},
   573  			"xds_servers" : [{
   574  				"server_uri": "trafficdirector.googleapis.com:443",
   575  				"channel_creds": [
   576  					{ "type": "google_default" }
   577  				],
   578  				"server_features" : ["foo", "bar", "xds_v3"],
   579  			}],
   580  			"certificate_providers": "bad JSON"
   581  		}`,
   582  		"allUnknownCertProviders": `
   583  		{
   584  			"node": {
   585  				"id": "ENVOY_NODE_ID",
   586  				"metadata": {
   587  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   588  			    }
   589  			},
   590  			"xds_servers" : [{
   591  				"server_uri": "trafficdirector.googleapis.com:443",
   592  				"channel_creds": [
   593  					{ "type": "google_default" }
   594  				],
   595  				"server_features" : ["foo", "bar", "xds_v3"]
   596  			}],
   597  			"certificate_providers": {
   598  				"unknownProviderInstance1": {
   599  					"plugin_name": "foo",
   600  					"config": {"foo": "bar"}
   601  				},
   602  				"unknownProviderInstance2": {
   603  					"plugin_name": "bar",
   604  					"config": {"foo": "bar"}
   605  				}
   606  			}
   607  		}`,
   608  		"badCertProviderConfig": `
   609  		{
   610  			"node": {
   611  				"id": "ENVOY_NODE_ID",
   612  				"metadata": {
   613  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   614  			    }
   615  			},
   616  			"xds_servers" : [{
   617  				"server_uri": "trafficdirector.googleapis.com:443",
   618  				"channel_creds": [
   619  					{ "type": "google_default" }
   620  				],
   621  				"server_features" : ["foo", "bar", "xds_v3"],
   622  			}],
   623  			"certificate_providers": {
   624  				"unknownProviderInstance": {
   625  					"plugin_name": "foo",
   626  					"config": {"foo": "bar"}
   627  				},
   628  				"fakeProviderInstanceBad": {
   629  					"plugin_name": "fake-certificate-provider",
   630  					"config": {"configKey": 666}
   631  				}
   632  			}
   633  		}`,
   634  		"goodCertProviderConfig": `
   635  		{
   636  			"node": {
   637  				"id": "ENVOY_NODE_ID",
   638  				"metadata": {
   639  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   640  			    }
   641  			},
   642  			"xds_servers" : [{
   643  				"server_uri": "trafficdirector.googleapis.com:443",
   644  				"channel_creds": [
   645  					{ "type": "google_default" }
   646  				],
   647  				"server_features" : ["foo", "bar", "xds_v3"]
   648  			}],
   649  			"certificate_providers": {
   650  				"unknownProviderInstance": {
   651  					"plugin_name": "foo",
   652  					"config": {"foo": "bar"}
   653  				},
   654  				"fakeProviderInstance": {
   655  					"plugin_name": "fake-certificate-provider",
   656  					"config": {"configKey": "configValue"}
   657  				}
   658  			}
   659  		}`,
   660  	}
   661  
   662  	getBuilder := internal2.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
   663  	parser := getBuilder(fakeCertProviderName)
   664  	if parser == nil {
   665  		t.Fatalf("missing certprovider plugin %q", fakeCertProviderName)
   666  	}
   667  	wantCfg, err := parser.ParseConfig(json.RawMessage(`{"configKey": "configValue"}`))
   668  	if err != nil {
   669  		t.Fatalf("config parsing for plugin %q failed: %v", fakeCertProviderName, err)
   670  	}
   671  
   672  	cancel := setupBootstrapOverride(bootstrapFileMap)
   673  	defer cancel()
   674  
   675  	goodConfig := &Config{
   676  		XDSServer: &ServerConfig{
   677  			ServerURI:    "trafficdirector.googleapis.com:443",
   678  			Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   679  			CredsType:    "google_default",
   680  			TransportAPI: version.TransportV3,
   681  			NodeProto:    v3NodeProto,
   682  		},
   683  		CertProviderConfigs: map[string]*certprovider.BuildableConfig{
   684  			"fakeProviderInstance": wantCfg,
   685  		},
   686  		ClientDefaultListenerResourceNameTemplate: "%s",
   687  	}
   688  	tests := []struct {
   689  		name       string
   690  		wantConfig *Config
   691  		wantErr    bool
   692  	}{
   693  		{
   694  			name:    "badJSONCertProviderConfig",
   695  			wantErr: true,
   696  		},
   697  		{
   698  
   699  			name:    "badCertProviderConfig",
   700  			wantErr: true,
   701  		},
   702  		{
   703  
   704  			name:       "allUnknownCertProviders",
   705  			wantConfig: nonNilCredsConfigV3,
   706  		},
   707  		{
   708  			name:       "goodCertProviderConfig",
   709  			wantConfig: goodConfig,
   710  		},
   711  	}
   712  
   713  	for _, test := range tests {
   714  		t.Run(test.name, func(t *testing.T) {
   715  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   716  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   717  		})
   718  	}*/
   719  }
   720  
   721  func TestNewConfigWithServerListenerResourceNameTemplate(t *testing.T) {
   722  	cancel := setupBootstrapOverride(map[string]string{
   723  		"badServerListenerResourceNameTemplate:": `
   724  		{
   725  			"node": {
   726  				"id": "ENVOY_NODE_ID",
   727  				"metadata": {
   728  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   729  			    }
   730  			},
   731  			"xds_servers" : [{
   732  				"server_uri": "trafficdirector.googleapis.com:443",
   733  				"channel_creds": [
   734  					{ "type": "google_default" }
   735  				]
   736  			}],
   737  			"server_listener_resource_name_template": 123456789
   738  		}`,
   739  		"goodServerListenerResourceNameTemplate": `
   740  		{
   741  			"node": {
   742  				"id": "ENVOY_NODE_ID",
   743  				"metadata": {
   744  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   745  			    }
   746  			},
   747  			"xds_servers" : [{
   748  				"server_uri": "trafficdirector.googleapis.com:443",
   749  				"channel_creds": [
   750  					{ "type": "google_default" }
   751  				]
   752  			}],
   753  			"server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s"
   754  		}`,
   755  	})
   756  	defer cancel()
   757  
   758  	tests := []struct {
   759  		name       string
   760  		wantConfig *Config
   761  		wantErr    bool
   762  	}{
   763  		{
   764  			name:    "badServerListenerResourceNameTemplate",
   765  			wantErr: true,
   766  		},
   767  		{
   768  			name: "goodServerListenerResourceNameTemplate",
   769  			wantConfig: &Config{
   770  				XDSServer: &ServerConfig{
   771  					ServerURI:    "trafficdirector.googleapis.com:443",
   772  					Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   773  					CredsType:    "google_default",
   774  					TransportAPI: version.TransportV2,
   775  					NodeProto:    v2NodeProto,
   776  				},
   777  				ServerListenerResourceNameTemplate:        "grpc/server?xds.resource.listening_address=%s",
   778  				ClientDefaultListenerResourceNameTemplate: "%s",
   779  			},
   780  		},
   781  	}
   782  
   783  	for _, test := range tests {
   784  		t.Run(test.name, func(t *testing.T) {
   785  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   786  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   787  		})
   788  	}
   789  }
   790  
   791  func TestNewConfigWithFederation(t *testing.T) {
   792  	cancel := setupBootstrapOverride(map[string]string{
   793  		"badClientListenerResourceNameTemplate": `
   794  		{
   795  			"node": { "id": "ENVOY_NODE_ID" },
   796  			"xds_servers" : [{
   797  				"server_uri": "trafficdirector.googleapis.com:443"
   798  			}],
   799  			"client_default_listener_resource_name_template": 123456789
   800  		}`,
   801  		"badClientListenerResourceNameTemplatePerAuthority": `
   802  		{
   803  			"node": { "id": "ENVOY_NODE_ID" },
   804  			"xds_servers" : [{
   805  				"server_uri": "trafficdirector.googleapis.com:443",
   806  				"channel_creds": [ { "type": "google_default" } ]
   807  			}],
   808  			"authorities": {
   809  				"xds.td.com": {
   810  					"client_listener_resource_name_template": "some/template/%s",
   811  					"xds_servers": [{
   812  						"server_uri": "td.com",
   813  						"channel_creds": [ { "type": "google_default" } ],
   814  						"server_features" : ["foo", "bar", "xds_v3"]
   815  					}]
   816  				}
   817  			}
   818  		}`,
   819  		"good": `
   820  		{
   821  			"node": {
   822  				"id": "ENVOY_NODE_ID",
   823  				"metadata": {
   824  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   825  			    }
   826  			},
   827  			"xds_servers" : [{
   828  				"server_uri": "trafficdirector.googleapis.com:443",
   829  				"channel_creds": [ { "type": "google_default" } ]
   830  			}],
   831  			"server_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
   832  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   833  			"authorities": {
   834  				"xds.td.com": {
   835  					"client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   836  					"xds_servers": [{
   837  						"server_uri": "td.com",
   838  						"channel_creds": [ { "type": "google_default" } ],
   839  						"server_features" : ["foo", "bar", "xds_v3"]
   840  					}]
   841  				}
   842  			}
   843  		}`,
   844  		// If client_default_listener_resource_name_template is not set, it
   845  		// defaults to "%s".
   846  		"goodWithDefaultDefaultClientListenerTemplate": `
   847  		{
   848  			"node": {
   849  				"id": "ENVOY_NODE_ID",
   850  				"metadata": {
   851  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   852  			    }
   853  			},
   854  			"xds_servers" : [{
   855  				"server_uri": "trafficdirector.googleapis.com:443",
   856  				"channel_creds": [ { "type": "google_default" } ]
   857  			}]
   858  		}`,
   859  		// If client_listener_resource_name_template in authority is not set, it
   860  		// defaults to
   861  		// "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s".
   862  		"goodWithDefaultClientListenerTemplatePerAuthority": `
   863  		{
   864  			"node": {
   865  				"id": "ENVOY_NODE_ID",
   866  				"metadata": {
   867  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   868  			    }
   869  			},
   870  			"xds_servers" : [{
   871  				"server_uri": "trafficdirector.googleapis.com:443",
   872  				"channel_creds": [ { "type": "google_default" } ]
   873  			}],
   874  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   875  			"authorities": {
   876  				"xds.td.com": { }
   877  			}
   878  		}`,
   879  		// It's OK for an authority to not have servers. The top-level server
   880  		// will be used.
   881  		"goodWithNoServerPerAuthority": `
   882  		{
   883  			"node": {
   884  				"id": "ENVOY_NODE_ID",
   885  				"metadata": {
   886  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   887  			    }
   888  			},
   889  			"xds_servers" : [{
   890  				"server_uri": "trafficdirector.googleapis.com:443",
   891  				"channel_creds": [ { "type": "google_default" } ]
   892  			}],
   893  			"client_default_listener_resource_name_template": "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   894  			"authorities": {
   895  				"xds.td.com": {
   896  					"client_listener_resource_name_template": "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s"
   897  				}
   898  			}
   899  		}`,
   900  	})
   901  	defer cancel()
   902  
   903  	tests := []struct {
   904  		name       string
   905  		wantConfig *Config
   906  		wantErr    bool
   907  	}{
   908  		{
   909  			name:    "badClientListenerResourceNameTemplate",
   910  			wantErr: true,
   911  		},
   912  		{
   913  			name:    "badClientListenerResourceNameTemplatePerAuthority",
   914  			wantErr: true,
   915  		},
   916  		{
   917  			name: "good",
   918  			wantConfig: &Config{
   919  				XDSServer: &ServerConfig{
   920  					ServerURI:    "trafficdirector.googleapis.com:443",
   921  					Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   922  					CredsType:    "google_default",
   923  					TransportAPI: version.TransportV2,
   924  					NodeProto:    v2NodeProto,
   925  				},
   926  				ServerListenerResourceNameTemplate:        "xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/server?listening_address=%s",
   927  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   928  				Authorities: map[string]*Authority{
   929  					"xds.td.com": {
   930  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   931  						XDSServer: &ServerConfig{
   932  							ServerURI:    "td.com",
   933  							Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   934  							CredsType:    "google_default",
   935  							TransportAPI: version.TransportV3,
   936  							NodeProto:    v3NodeProto,
   937  						},
   938  					},
   939  				},
   940  			},
   941  		},
   942  		{
   943  			name: "goodWithDefaultDefaultClientListenerTemplate",
   944  			wantConfig: &Config{
   945  				XDSServer: &ServerConfig{
   946  					ServerURI:    "trafficdirector.googleapis.com:443",
   947  					Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   948  					CredsType:    "google_default",
   949  					TransportAPI: version.TransportV2,
   950  					NodeProto:    v2NodeProto,
   951  				},
   952  				ClientDefaultListenerResourceNameTemplate: "%s",
   953  			},
   954  		},
   955  		{
   956  			name: "goodWithDefaultClientListenerTemplatePerAuthority",
   957  			wantConfig: &Config{
   958  				XDSServer: &ServerConfig{
   959  					ServerURI:    "trafficdirector.googleapis.com:443",
   960  					Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   961  					CredsType:    "google_default",
   962  					TransportAPI: version.TransportV2,
   963  					NodeProto:    v2NodeProto,
   964  				},
   965  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   966  				Authorities: map[string]*Authority{
   967  					"xds.td.com": {
   968  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   969  					},
   970  				},
   971  			},
   972  		},
   973  		{
   974  			name: "goodWithNoServerPerAuthority",
   975  			wantConfig: &Config{
   976  				XDSServer: &ServerConfig{
   977  					ServerURI:    "trafficdirector.googleapis.com:443",
   978  					Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   979  					CredsType:    "google_default",
   980  					TransportAPI: version.TransportV2,
   981  					NodeProto:    v2NodeProto,
   982  				},
   983  				ClientDefaultListenerResourceNameTemplate: "xdstp://xds.example.com/envoy.config.listener.v3.Listener/%s",
   984  				Authorities: map[string]*Authority{
   985  					"xds.td.com": {
   986  						ClientListenerResourceNameTemplate: "xdstp://xds.td.com/envoy.config.listener.v3.Listener/%s",
   987  					},
   988  				},
   989  			},
   990  		},
   991  	}
   992  
   993  	oldFederationSupport := envconfig.XDSFederation
   994  	envconfig.XDSFederation = true
   995  	defer func() { envconfig.XDSFederation = oldFederationSupport }()
   996  
   997  	for _, test := range tests {
   998  		t.Run(test.name, func(t *testing.T) {
   999  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
  1000  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
  1001  		})
  1002  	}
  1003  }