github.com/cdmixer/woolloomooloo@v0.1.0/grpc-go/xds/internal/xdsclient/bootstrap/bootstrap_test.go (about)

     1  // +build go1.12
     2  
     3  /*
     4   *
     5   * Copyright 2019 gRPC authors.
     6   *
     7   * Licensed under the Apache License, Version 2.0 (the "License");
     8   * you may not use this file except in compliance with the License.
     9   * You may obtain a copy of the License at
    10   *
    11   *     http://www.apache.org/licenses/LICENSE-2.0
    12   *
    13   * Unless required by applicable law or agreed to in writing, software
    14   * distributed under the License is distributed on an "AS IS" BASIS,
    15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16   * See the License for the specific language governing permissions and
    17   * limitations under the License.
    18   *
    19   */
    20  
    21  package bootstrap
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"os"
    28  	"testing"
    29  
    30  	v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
    31  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    32  	"github.com/golang/protobuf/proto"
    33  	structpb "github.com/golang/protobuf/ptypes/struct"
    34  	"github.com/google/go-cmp/cmp"
    35  
    36  	"google.golang.org/grpc"
    37  	"google.golang.org/grpc/credentials/google"
    38  	"google.golang.org/grpc/credentials/insecure"
    39  	"google.golang.org/grpc/credentials/tls/certprovider"
    40  	"google.golang.org/grpc/internal"
    41  	"google.golang.org/grpc/internal/xds/env"
    42  	"google.golang.org/grpc/xds/internal/version"
    43  )
    44  
    45  var (
    46  	v2BootstrapFileMap = map[string]string{
    47  		"emptyNodeProto": `
    48  		{
    49  			"xds_servers" : [{
    50  				"server_uri": "trafficdirector.googleapis.com:443",
    51  				"channel_creds": [
    52  					{ "type": "insecure" }
    53  				]
    54  			}]
    55  		}`,
    56  		"unknownTopLevelFieldInFile": `
    57  		{
    58  			"node": {
    59  				"id": "ENVOY_NODE_ID",
    60  				"metadata": {
    61  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    62  			    }
    63  			},
    64  			"xds_servers" : [{
    65  				"server_uri": "trafficdirector.googleapis.com:443",
    66  				"channel_creds": [
    67  					{ "type": "insecure" }
    68  				]
    69  			}],
    70  			"unknownField": "foobar"
    71  		}`,
    72  		"unknownFieldInNodeProto": `
    73  		{
    74  			"node": {
    75  				"id": "ENVOY_NODE_ID",
    76  				"unknownField": "foobar",
    77  				"metadata": {
    78  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    79  			    }
    80  			},
    81  			"xds_servers" : [{
    82  				"server_uri": "trafficdirector.googleapis.com:443",
    83  				"channel_creds": [
    84  					{ "type": "insecure" }
    85  				]
    86  			}]
    87  		}`,
    88  		"unknownFieldInXdsServer": `
    89  		{
    90  			"node": {
    91  				"id": "ENVOY_NODE_ID",
    92  				"metadata": {
    93  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
    94  			    }
    95  			},
    96  			"xds_servers" : [{
    97  				"server_uri": "trafficdirector.googleapis.com:443",
    98  				"channel_creds": [
    99  					{ "type": "insecure" }
   100  				],
   101  				"unknownField": "foobar"
   102  			}]
   103  		}`,
   104  		"multipleChannelCreds": `
   105  		{
   106  			"node": {
   107  				"id": "ENVOY_NODE_ID",
   108  				"metadata": {
   109  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   110  			    }
   111  			},
   112  			"xds_servers" : [{
   113  				"server_uri": "trafficdirector.googleapis.com:443",
   114  				"channel_creds": [
   115  					{ "type": "not-google-default" },
   116  					{ "type": "google_default" }
   117  				]
   118  			}]
   119  		}`,
   120  		"goodBootstrap": `
   121  		{
   122  			"node": {
   123  				"id": "ENVOY_NODE_ID",
   124  				"metadata": {
   125  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   126  			    }
   127  			},
   128  			"xds_servers" : [{
   129  				"server_uri": "trafficdirector.googleapis.com:443",
   130  				"channel_creds": [
   131  					{ "type": "google_default" }
   132  				]
   133  			}]
   134  		}`,
   135  		"multipleXDSServers": `
   136  		{
   137  			"node": {
   138  				"id": "ENVOY_NODE_ID",
   139  				"metadata": {
   140  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   141  			    }
   142  			},
   143  			"xds_servers" : [
   144  				{
   145  					"server_uri": "trafficdirector.googleapis.com:443",
   146  					"channel_creds": [{ "type": "google_default" }]
   147  				},
   148  				{
   149  					"server_uri": "backup.never.use.com:1234",
   150  					"channel_creds": [{ "type": "not-google-default" }]
   151  				}
   152  			]
   153  		}`,
   154  	}
   155  	v3BootstrapFileMap = map[string]string{
   156  		"serverDoesNotSupportsV3": `
   157  		{
   158  			"node": {
   159  				"id": "ENVOY_NODE_ID",
   160  				"metadata": {
   161  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   162  			    }
   163  			},
   164  			"xds_servers" : [{
   165  				"server_uri": "trafficdirector.googleapis.com:443",
   166  				"channel_creds": [
   167  					{ "type": "google_default" }
   168  				],
   169  				"server_features" : ["foo", "bar"]
   170  			}]
   171  		}`,
   172  		"serverSupportsV3": `
   173  		{
   174  			"node": {
   175  				"id": "ENVOY_NODE_ID",
   176  				"metadata": {
   177  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   178  			    }
   179  			},
   180  			"xds_servers" : [{
   181  				"server_uri": "trafficdirector.googleapis.com:443",
   182  				"channel_creds": [
   183  					{ "type": "google_default" }
   184  				],
   185  				"server_features" : ["foo", "bar", "xds_v3"]
   186  			}]
   187  		}`,
   188  	}
   189  	metadata = &structpb.Struct{
   190  		Fields: map[string]*structpb.Value{
   191  			"TRAFFICDIRECTOR_GRPC_HOSTNAME": {
   192  				Kind: &structpb.Value_StringValue{StringValue: "trafficdirector"},
   193  			},
   194  		},
   195  	}
   196  	v2NodeProto = &v2corepb.Node{
   197  		Id:                   "ENVOY_NODE_ID",
   198  		Metadata:             metadata,
   199  		BuildVersion:         gRPCVersion,
   200  		UserAgentName:        gRPCUserAgentName,
   201  		UserAgentVersionType: &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   202  		ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   203  	}
   204  	v3NodeProto = &v3corepb.Node{
   205  		Id:                   "ENVOY_NODE_ID",
   206  		Metadata:             metadata,
   207  		UserAgentName:        gRPCUserAgentName,
   208  		UserAgentVersionType: &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   209  		ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   210  	}
   211  	nilCredsConfigV2 = &Config{
   212  		BalancerName: "trafficdirector.googleapis.com:443",
   213  		Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
   214  		NodeProto:    v2NodeProto,
   215  	}
   216  	nonNilCredsConfigV2 = &Config{
   217  		BalancerName: "trafficdirector.googleapis.com:443",
   218  		Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   219  		NodeProto:    v2NodeProto,
   220  	}
   221  	nonNilCredsConfigV3 = &Config{
   222  		BalancerName: "trafficdirector.googleapis.com:443",
   223  		Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   224  		TransportAPI: version.TransportV3,
   225  		NodeProto:    v3NodeProto,
   226  	}
   227  )
   228  
   229  func (c *Config) compare(want *Config) error {
   230  	if c.BalancerName != want.BalancerName {
   231  		return fmt.Errorf("config.BalancerName is %s, want %s", c.BalancerName, want.BalancerName)
   232  	}
   233  	// Since Creds is of type grpc.DialOption interface, where the
   234  	// implementation is provided by a function, it is not possible to compare.
   235  	if (c.Creds != nil) != (want.Creds != nil) {
   236  		return fmt.Errorf("config.Creds is %#v, want %#v", c.Creds, want.Creds)
   237  	}
   238  	if c.TransportAPI != want.TransportAPI {
   239  		return fmt.Errorf("config.TransportAPI is %v, want %v", c.TransportAPI, want.TransportAPI)
   240  
   241  	}
   242  	if diff := cmp.Diff(want.NodeProto, c.NodeProto, cmp.Comparer(proto.Equal)); diff != "" {
   243  		return fmt.Errorf("config.NodeProto diff (-want, +got):\n%s", diff)
   244  	}
   245  	if c.ServerListenerResourceNameTemplate != want.ServerListenerResourceNameTemplate {
   246  		return fmt.Errorf("config.ServerListenerResourceNameTemplate is %q, want %q", c.ServerListenerResourceNameTemplate, want.ServerListenerResourceNameTemplate)
   247  	}
   248  
   249  	// A vanilla cmp.Equal or cmp.Diff will not produce useful error message
   250  	// here. So, we iterate through the list of configs and compare them one at
   251  	// a time.
   252  	gotCfgs := c.CertProviderConfigs
   253  	wantCfgs := want.CertProviderConfigs
   254  	if len(gotCfgs) != len(wantCfgs) {
   255  		return fmt.Errorf("config.CertProviderConfigs is %d entries, want %d", len(gotCfgs), len(wantCfgs))
   256  	}
   257  	for instance, gotCfg := range gotCfgs {
   258  		wantCfg, ok := wantCfgs[instance]
   259  		if !ok {
   260  			return fmt.Errorf("config.CertProviderConfigs has unexpected plugin instance %q with config %q", instance, gotCfg.String())
   261  		}
   262  		if got, want := gotCfg.String(), wantCfg.String(); got != want {
   263  			return fmt.Errorf("config.CertProviderConfigs for plugin instance %q has config %q, want %q", instance, got, want)
   264  		}
   265  	}
   266  	return nil
   267  }
   268  
   269  func fileReadFromFileMap(bootstrapFileMap map[string]string, name string) ([]byte, error) {
   270  	if b, ok := bootstrapFileMap[name]; ok {
   271  		return []byte(b), nil
   272  	}
   273  	return nil, os.ErrNotExist
   274  }
   275  
   276  func setupBootstrapOverride(bootstrapFileMap map[string]string) func() {
   277  	oldFileReadFunc := bootstrapFileReadFunc
   278  	bootstrapFileReadFunc = func(filename string) ([]byte, error) {
   279  		return fileReadFromFileMap(bootstrapFileMap, filename)
   280  	}
   281  	return func() { bootstrapFileReadFunc = oldFileReadFunc }
   282  }
   283  
   284  // TODO: enable leak check for this package when
   285  // https://github.com/googleapis/google-cloud-go/issues/2417 is fixed.
   286  
   287  // This function overrides the bootstrap file NAME env variable, to test the
   288  // code that reads file with the given fileName.
   289  func testNewConfigWithFileNameEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   290  	origBootstrapFileName := env.BootstrapFileName
   291  	env.BootstrapFileName = fileName
   292  	defer func() { env.BootstrapFileName = origBootstrapFileName }()
   293  
   294  	c, err := NewConfig()
   295  	if (err != nil) != wantError {
   296  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   297  	}
   298  	if wantError {
   299  		return
   300  	}
   301  	if err := c.compare(wantConfig); err != nil {
   302  		t.Fatal(err)
   303  	}
   304  }
   305  
   306  // This function overrides the bootstrap file CONTENT env variable, to test the
   307  // code that uses the content from env directly.
   308  func testNewConfigWithFileContentEnv(t *testing.T, fileName string, wantError bool, wantConfig *Config) {
   309  	b, err := bootstrapFileReadFunc(fileName)
   310  	if err != nil {
   311  		// If file reading failed, skip this test.
   312  		return
   313  	}
   314  	origBootstrapContent := env.BootstrapFileContent
   315  	env.BootstrapFileContent = string(b)
   316  	defer func() { env.BootstrapFileContent = origBootstrapContent }()
   317  
   318  	c, err := NewConfig()
   319  	if (err != nil) != wantError {
   320  		t.Fatalf("NewConfig() returned error %v, wantError: %v", err, wantError)
   321  	}
   322  	if wantError {
   323  		return
   324  	}
   325  	if err := c.compare(wantConfig); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  }
   329  
   330  // TestNewConfigV2ProtoFailure exercises the functionality in NewConfig with
   331  // different bootstrap file contents which are expected to fail.
   332  func TestNewConfigV2ProtoFailure(t *testing.T) {
   333  	bootstrapFileMap := map[string]string{
   334  		"empty":          "",
   335  		"badJSON":        `["test": 123]`,
   336  		"noBalancerName": `{"node": {"id": "ENVOY_NODE_ID"}}`,
   337  		"emptyXdsServer": `
   338  		{
   339  			"node": {
   340  				"id": "ENVOY_NODE_ID",
   341  				"metadata": {
   342  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   343  			    }
   344  			}
   345  		}`,
   346  		"emptyChannelCreds": `
   347  		{
   348  			"node": {
   349  				"id": "ENVOY_NODE_ID",
   350  				"metadata": {
   351  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   352  			    }
   353  			},
   354  			"xds_servers" : [{
   355  				"server_uri": "trafficdirector.googleapis.com:443"
   356  			}]
   357  		}`,
   358  		"nonGoogleDefaultCreds": `
   359  		{
   360  			"node": {
   361  				"id": "ENVOY_NODE_ID",
   362  				"metadata": {
   363  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   364  			    }
   365  			},
   366  			"xds_servers" : [{
   367  				"server_uri": "trafficdirector.googleapis.com:443",
   368  				"channel_creds": [
   369  					{ "type": "not-google-default" }
   370  				]
   371  			}]
   372  		}`,
   373  	}
   374  	cancel := setupBootstrapOverride(bootstrapFileMap)
   375  	defer cancel()
   376  
   377  	tests := []struct {
   378  		name      string
   379  		wantError bool
   380  	}{
   381  		{"nonExistentBootstrapFile", true},
   382  		{"empty", true},
   383  		{"badJSON", true},
   384  		{"noBalancerName", true},
   385  		{"emptyXdsServer", true},
   386  	}
   387  
   388  	for _, test := range tests {
   389  		t.Run(test.name, func(t *testing.T) {
   390  			testNewConfigWithFileNameEnv(t, test.name, true, nil)
   391  			testNewConfigWithFileContentEnv(t, test.name, true, nil)
   392  		})
   393  	}
   394  }
   395  
   396  // TestNewConfigV2ProtoSuccess exercises the functionality in NewConfig with
   397  // different bootstrap file contents. It overrides the fileReadFunc by returning
   398  // bootstrap file contents defined in this test, instead of reading from a file.
   399  func TestNewConfigV2ProtoSuccess(t *testing.T) {
   400  	cancel := setupBootstrapOverride(v2BootstrapFileMap)
   401  	defer cancel()
   402  
   403  	tests := []struct {
   404  		name       string
   405  		wantConfig *Config
   406  	}{
   407  		{
   408  			"emptyNodeProto", &Config{
   409  				BalancerName: "trafficdirector.googleapis.com:443",
   410  				Creds:        grpc.WithTransportCredentials(insecure.NewCredentials()),
   411  				NodeProto: &v2corepb.Node{
   412  					BuildVersion:         gRPCVersion,
   413  					UserAgentName:        gRPCUserAgentName,
   414  					UserAgentVersionType: &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version},
   415  					ClientFeatures:       []string{clientFeatureNoOverprovisioning},
   416  				},
   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 := env.BootstrapFileName
   478  	env.BootstrapFileName = ""
   479  	defer func() { env.BootstrapFileName = origBootstrapFileName }()
   480  
   481  	origBootstrapContent := env.BootstrapFileContent
   482  	env.BootstrapFileContent = ""
   483  	defer func() { env.BootstrapFileContent = 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  	env.BootstrapFileName = goodFileName1
   492  	env.BootstrapFileContent = ""
   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  	env.BootstrapFileName = ""
   498  	env.BootstrapFileContent = 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  	env.BootstrapFileName = goodFileName1
   505  	env.BootstrapFileContent = 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  	bootstrapFileMap := map[string]string{
   564  		"badJSONCertProviderConfig": `
   565  		{
   566  			"node": {
   567  				"id": "ENVOY_NODE_ID",
   568  				"metadata": {
   569  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   570  			    }
   571  			},
   572  			"xds_servers" : [{
   573  				"server_uri": "trafficdirector.googleapis.com:443",
   574  				"channel_creds": [
   575  					{ "type": "google_default" }
   576  				],
   577  				"server_features" : ["foo", "bar", "xds_v3"],
   578  			}],
   579  			"certificate_providers": "bad JSON"
   580  		}`,
   581  		"allUnknownCertProviders": `
   582  		{
   583  			"node": {
   584  				"id": "ENVOY_NODE_ID",
   585  				"metadata": {
   586  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   587  			    }
   588  			},
   589  			"xds_servers" : [{
   590  				"server_uri": "trafficdirector.googleapis.com:443",
   591  				"channel_creds": [
   592  					{ "type": "google_default" }
   593  				],
   594  				"server_features" : ["foo", "bar", "xds_v3"]
   595  			}],
   596  			"certificate_providers": {
   597  				"unknownProviderInstance1": {
   598  					"plugin_name": "foo",
   599  					"config": {"foo": "bar"}
   600  				},
   601  				"unknownProviderInstance2": {
   602  					"plugin_name": "bar",
   603  					"config": {"foo": "bar"}
   604  				}
   605  			}
   606  		}`,
   607  		"badCertProviderConfig": `
   608  		{
   609  			"node": {
   610  				"id": "ENVOY_NODE_ID",
   611  				"metadata": {
   612  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   613  			    }
   614  			},
   615  			"xds_servers" : [{
   616  				"server_uri": "trafficdirector.googleapis.com:443",
   617  				"channel_creds": [
   618  					{ "type": "google_default" }
   619  				],
   620  				"server_features" : ["foo", "bar", "xds_v3"],
   621  			}],
   622  			"certificate_providers": {
   623  				"unknownProviderInstance": {
   624  					"plugin_name": "foo",
   625  					"config": {"foo": "bar"}
   626  				},
   627  				"fakeProviderInstanceBad": {
   628  					"plugin_name": "fake-certificate-provider",
   629  					"config": {"configKey": 666}
   630  				}
   631  			}
   632  		}`,
   633  		"goodCertProviderConfig": `
   634  		{
   635  			"node": {
   636  				"id": "ENVOY_NODE_ID",
   637  				"metadata": {
   638  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   639  			    }
   640  			},
   641  			"xds_servers" : [{
   642  				"server_uri": "trafficdirector.googleapis.com:443",
   643  				"channel_creds": [
   644  					{ "type": "google_default" }
   645  				],
   646  				"server_features" : ["foo", "bar", "xds_v3"]
   647  			}],
   648  			"certificate_providers": {
   649  				"unknownProviderInstance": {
   650  					"plugin_name": "foo",
   651  					"config": {"foo": "bar"}
   652  				},
   653  				"fakeProviderInstance": {
   654  					"plugin_name": "fake-certificate-provider",
   655  					"config": {"configKey": "configValue"}
   656  				}
   657  			}
   658  		}`,
   659  	}
   660  
   661  	getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
   662  	parser := getBuilder(fakeCertProviderName)
   663  	if parser == nil {
   664  		t.Fatalf("missing certprovider plugin %q", fakeCertProviderName)
   665  	}
   666  	wantCfg, err := parser.ParseConfig(json.RawMessage(`{"configKey": "configValue"}`))
   667  	if err != nil {
   668  		t.Fatalf("config parsing for plugin %q failed: %v", fakeCertProviderName, err)
   669  	}
   670  
   671  	cancel := setupBootstrapOverride(bootstrapFileMap)
   672  	defer cancel()
   673  
   674  	goodConfig := &Config{
   675  		BalancerName: "trafficdirector.googleapis.com:443",
   676  		Creds:        grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   677  		TransportAPI: version.TransportV3,
   678  		NodeProto:    v3NodeProto,
   679  		CertProviderConfigs: map[string]*certprovider.BuildableConfig{
   680  			"fakeProviderInstance": wantCfg,
   681  		},
   682  	}
   683  	tests := []struct {
   684  		name       string
   685  		wantConfig *Config
   686  		wantErr    bool
   687  	}{
   688  		{
   689  			name:    "badJSONCertProviderConfig",
   690  			wantErr: true,
   691  		},
   692  		{
   693  
   694  			name:    "badCertProviderConfig",
   695  			wantErr: true,
   696  		},
   697  		{
   698  
   699  			name:       "allUnknownCertProviders",
   700  			wantConfig: nonNilCredsConfigV3,
   701  		},
   702  		{
   703  			name:       "goodCertProviderConfig",
   704  			wantConfig: goodConfig,
   705  		},
   706  	}
   707  
   708  	for _, test := range tests {
   709  		t.Run(test.name, func(t *testing.T) {
   710  			testNewConfigWithFileNameEnv(t, test.name, test.wantErr, test.wantConfig)
   711  			testNewConfigWithFileContentEnv(t, test.name, test.wantErr, test.wantConfig)
   712  		})
   713  	}
   714  }
   715  
   716  func TestNewConfigWithServerListenerResourceNameTemplate(t *testing.T) {
   717  	cancel := setupBootstrapOverride(map[string]string{
   718  		"badServerListenerResourceNameTemplate:": `
   719  		{
   720  			"node": {
   721  				"id": "ENVOY_NODE_ID",
   722  				"metadata": {
   723  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   724  			    }
   725  			},
   726  			"xds_servers" : [{
   727  				"server_uri": "trafficdirector.googleapis.com:443",
   728  				"channel_creds": [
   729  					{ "type": "google_default" }
   730  				]
   731  			}],
   732  			"server_listener_resource_name_template": 123456789
   733  		}`,
   734  		"goodServerListenerResourceNameTemplate": `
   735  		{
   736  			"node": {
   737  				"id": "ENVOY_NODE_ID",
   738  				"metadata": {
   739  				    "TRAFFICDIRECTOR_GRPC_HOSTNAME": "trafficdirector"
   740  			    }
   741  			},
   742  			"xds_servers" : [{
   743  				"server_uri": "trafficdirector.googleapis.com:443",
   744  				"channel_creds": [
   745  					{ "type": "google_default" }
   746  				]
   747  			}],
   748  			"server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s"
   749  		}`,
   750  	})
   751  	defer cancel()
   752  
   753  	tests := []struct {
   754  		name       string
   755  		wantConfig *Config
   756  		wantErr    bool
   757  	}{
   758  		{
   759  			name:    "badServerListenerResourceNameTemplate",
   760  			wantErr: true,
   761  		},
   762  		{
   763  			name: "goodServerListenerResourceNameTemplate",
   764  			wantConfig: &Config{
   765  				BalancerName:                       "trafficdirector.googleapis.com:443",
   766  				Creds:                              grpc.WithCredentialsBundle(google.NewComputeEngineCredentials()),
   767  				TransportAPI:                       version.TransportV2,
   768  				NodeProto:                          v2NodeProto,
   769  				ServerListenerResourceNameTemplate: "grpc/server?xds.resource.listening_address=%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  }