gitee.com/zhaochuninhefei/gmgo@v0.0.31-0.20240209061119-069254a02979/grpc/xds/internal/xdsclient/bootstrap/bootstrap_test.go (about)

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