/*
 * Copyright 2020 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cdsbalancer

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"testing"

	"github.com/google/go-cmp/cmp"
	"google.golang.org/grpc/attributes"
	"google.golang.org/grpc/balancer"
	"google.golang.org/grpc/credentials/local"
	"google.golang.org/grpc/credentials/tls/certprovider"
	"google.golang.org/grpc/credentials/xds"
	"google.golang.org/grpc/internal"
	xdscredsinternal "google.golang.org/grpc/internal/credentials/xds"
	"google.golang.org/grpc/internal/testutils"
	"google.golang.org/grpc/internal/xds/matcher"
	"google.golang.org/grpc/resolver"
	"google.golang.org/grpc/xds/internal/testutils/fakeclient"
	"google.golang.org/grpc/xds/internal/xdsclient/bootstrap"
	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
)

const (
	fakeProvider1Name = "fake-certificate-provider-1"
	fakeProvider2Name = "fake-certificate-provider-2"
	fakeConfig        = "my fake config"
	testSAN           = "test-san"
)

var (
	testSANMatchers = []matcher.StringMatcher{
		matcher.StringMatcherForTesting(newStringP(testSAN), nil, nil, nil, nil, true),
		matcher.StringMatcherForTesting(nil, newStringP(testSAN), nil, nil, nil, false),
		matcher.StringMatcherForTesting(nil, nil, newStringP(testSAN), nil, nil, false),
		matcher.StringMatcherForTesting(nil, nil, nil, nil, regexp.MustCompile(testSAN), false),
		matcher.StringMatcherForTesting(nil, nil, nil, newStringP(testSAN), nil, false),
	}
	fpb1, fpb2                   *fakeProviderBuilder
	bootstrapConfig              *bootstrap.Config
	cdsUpdateWithGoodSecurityCfg = xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		SecurityCfg: &xdsresource.SecurityConfig{
			RootInstanceName:       "default1",
			IdentityInstanceName:   "default2",
			SubjectAltNameMatchers: testSANMatchers,
		},
		LBPolicy: wrrLocalityLBConfigJSON,
	}
	cdsUpdateWithMissingSecurityCfg = xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		SecurityCfg: &xdsresource.SecurityConfig{
			RootInstanceName: "not-default",
		},
	}
)

func newStringP(s string) *string {
	return &s
}

func init() {
	fpb1 = &fakeProviderBuilder{name: fakeProvider1Name}
	fpb2 = &fakeProviderBuilder{name: fakeProvider2Name}
	cfg1, _ := fpb1.ParseConfig(fakeConfig + "1111")
	cfg2, _ := fpb2.ParseConfig(fakeConfig + "2222")
	bootstrapConfig = &bootstrap.Config{
		CertProviderConfigs: map[string]*certprovider.BuildableConfig{
			"default1": cfg1,
			"default2": cfg2,
		},
	}
	certprovider.Register(fpb1)
	certprovider.Register(fpb2)
}

// fakeProviderBuilder builds new instances of fakeProvider and interprets the
// config provided to it as a string.
type fakeProviderBuilder struct {
	name string
}

func (b *fakeProviderBuilder) ParseConfig(config interface{}) (*certprovider.BuildableConfig, error) {
	s, ok := config.(string)
	if !ok {
		return nil, fmt.Errorf("providerBuilder %s received config of type %T, want string", b.name, config)
	}
	return certprovider.NewBuildableConfig(b.name, []byte(s), func(certprovider.BuildOptions) certprovider.Provider {
		return &fakeProvider{
			Distributor: certprovider.NewDistributor(),
			config:      s,
		}
	}), nil
}

func (b *fakeProviderBuilder) Name() string {
	return b.name
}

// fakeProvider is an implementation of the Provider interface which provides a
// method for tests to invoke to push new key materials.
type fakeProvider struct {
	*certprovider.Distributor
	config string
}

// Close helps implement the Provider interface.
func (p *fakeProvider) Close() {
	p.Distributor.Stop()
}

// setupWithXDSCreds performs all the setup steps required for tests which use
// xDSCredentials.
func setupWithXDSCreds(t *testing.T) (*fakeclient.Client, *cdsBalancer, *testEDSBalancer, *testutils.TestClientConn, func()) {
	t.Helper()
	xdsC := fakeclient.NewClient()
	builder := balancer.Get(cdsName)
	if builder == nil {
		t.Fatalf("balancer.Get(%q) returned nil", cdsName)
	}
	// Create and pass xdsCredentials while building the CDS balancer.
	creds, err := xds.NewClientCredentials(xds.ClientOptions{
		FallbackCreds: local.NewCredentials(), // Placeholder fallback credentials.
	})
	if err != nil {
		t.Fatalf("Failed to create xDS client creds: %v", err)
	}
	// Create a new CDS balancer and pass it a fake balancer.ClientConn which we
	// can use to inspect the different calls made by the balancer.
	tcc := testutils.NewTestClientConn(t)
	cdsB := builder.Build(tcc, balancer.BuildOptions{DialCreds: creds})

	// Override the creation of the EDS balancer to return a fake EDS balancer
	// implementation.
	edsB := newTestEDSBalancer()
	oldEDSBalancerBuilder := newChildBalancer
	newChildBalancer = func(cc balancer.ClientConn, opts balancer.BuildOptions) (balancer.Balancer, error) {
		edsB.parentCC = cc
		return edsB, nil
	}

	// Push a ClientConnState update to the CDS balancer with a cluster name.
	if err := cdsB.UpdateClientConnState(cdsCCS(clusterName, xdsC)); err != nil {
		t.Fatalf("cdsBalancer.UpdateClientConnState failed with error: %v", err)
	}

	// Make sure the CDS balancer registers a Cluster watch with the xDS client
	// passed via attributes in the above update.
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	gotCluster, err := xdsC.WaitForWatchCluster(ctx)
	if err != nil {
		t.Fatalf("xdsClient.WatchCDS failed with error: %v", err)
	}
	if gotCluster != clusterName {
		t.Fatalf("xdsClient.WatchCDS called for cluster: %v, want: %v", gotCluster, clusterName)
	}

	return xdsC, cdsB.(*cdsBalancer), edsB, tcc, func() {
		newChildBalancer = oldEDSBalancerBuilder
	}
}

// makeNewSubConn invokes the NewSubConn() call on the balancer.ClientConn
// passed to the EDS balancer, and verifies that the CDS balancer forwards the
// call appropriately to its parent balancer.ClientConn with or without
// attributes bases on the value of wantFallback.
func makeNewSubConn(ctx context.Context, edsCC balancer.ClientConn, parentCC *testutils.TestClientConn, wantFallback bool) (balancer.SubConn, error) {
	dummyAddr := "foo-address"
	addrs := []resolver.Address{{Addr: dummyAddr}}
	sc, err := edsCC.NewSubConn(addrs, balancer.NewSubConnOptions{})
	if err != nil {
		return nil, fmt.Errorf("NewSubConn(%+v) on parent ClientConn failed: %v", addrs, err)
	}

	select {
	case <-ctx.Done():
		return nil, errors.New("timeout when waiting for new SubConn")
	case gotAddrs := <-parentCC.NewSubConnAddrsCh:
		if len(gotAddrs) != 1 {
			return nil, fmt.Errorf("NewSubConn expected 1 address, got %d", len(gotAddrs))
		}
		if got, want := gotAddrs[0].Addr, addrs[0].Addr; got != want {
			return nil, fmt.Errorf("resolver.Address passed to parent ClientConn has address %q, want %q", got, want)
		}
		getHI := internal.GetXDSHandshakeInfoForTesting.(func(attr *attributes.Attributes) *xdscredsinternal.HandshakeInfo)
		hi := getHI(gotAddrs[0].Attributes)
		if hi == nil {
			return nil, errors.New("resolver.Address passed to parent ClientConn doesn't contain attributes")
		}
		if gotFallback := hi.UseFallbackCreds(); gotFallback != wantFallback {
			return nil, fmt.Errorf("resolver.Address HandshakeInfo uses fallback creds? %v, want %v", gotFallback, wantFallback)
		}
		if !wantFallback {
			if diff := cmp.Diff(testSANMatchers, hi.GetSANMatchersForTesting(), cmp.AllowUnexported(regexp.Regexp{})); diff != "" {
				return nil, fmt.Errorf("unexpected diff in the list of SAN matchers (-got, +want):\n%s", diff)
			}
		}
	}
	return sc, nil
}

// TestSecurityConfigWithoutXDSCreds tests the case where xdsCredentials are not
// in use, but the CDS balancer receives a Cluster update with security
// configuration. Verifies that no certificate providers are created, and that
// the address attributes added as part of the intercepted NewSubConn() method
// indicate the use of fallback credentials.
func (s) TestSecurityConfigWithoutXDSCreds(t *testing.T) {
	// This creates a CDS balancer, pushes a ClientConnState update with a fake
	// xdsClient, and makes sure that the CDS balancer registers a watch on the
	// provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithWatch(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Override the provider builder function to push on a channel. We do not
	// expect this function to be called as part of this test.
	providerCh := testutils.NewChannel()
	origBuildProvider := buildProvider
	buildProvider = func(c map[string]*certprovider.BuildableConfig, id, cert string, wi, wr bool) (certprovider.Provider, error) {
		p, err := origBuildProvider(c, id, cert, wi, wr)
		providerCh.Send(nil)
		return p, err
	}
	defer func() { buildProvider = origBuildProvider }()

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	cdsUpdate := xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		LBPolicy:    wrrLocalityLBConfigJSON,
	}
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that the HandshakeInfo does not contain any
	// certificate providers, forcing the credentials implementation to use
	// fallback creds.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, true); err != nil {
		t.Fatal(err)
	}

	// Again, since xdsCredentials are not in use, no certificate providers
	// should have been initialized by the CDS balancer.
	sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
	defer sCancel()
	if _, err := providerCh.Receive(sCtx); err != context.DeadlineExceeded {
		t.Fatal("cds balancer created certificate providers when not using xds credentials")
	}
}

// TestNoSecurityConfigWithXDSCreds tests the case where xdsCredentials are in
// use, but the CDS balancer receives a Cluster update without security
// configuration. Verifies that no certificate providers are created, and that
// the address attributes added as part of the intercepted NewSubConn() method
// indicate the use of fallback credentials.
func (s) TestNoSecurityConfigWithXDSCreds(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Override the provider builder function to push on a channel. We do not
	// expect this function to be called as part of this test.
	providerCh := testutils.NewChannel()
	origBuildProvider := buildProvider
	buildProvider = func(c map[string]*certprovider.BuildableConfig, id, cert string, wi, wr bool) (certprovider.Provider, error) {
		p, err := origBuildProvider(c, id, cert, wi, wr)
		providerCh.Send(nil)
		return p, err
	}
	defer func() { buildProvider = origBuildProvider }()

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup. No security config is
	// passed to the CDS balancer as part of this update.
	cdsUpdate := xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		LBPolicy:    wrrLocalityLBConfigJSON,
	}
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that the HandshakeInfo does not contain any
	// certificate providers, forcing the credentials implementation to use
	// fallback creds.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, true); err != nil {
		t.Fatal(err)
	}

	// Again, since no security configuration was received, no certificate
	// providers should have been initialized by the CDS balancer.
	sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
	defer sCancel()
	if _, err := providerCh.Receive(sCtx); err != context.DeadlineExceeded {
		t.Fatal("cds balancer created certificate providers when not using xds credentials")
	}
}

// TestSecurityConfigNotFoundInBootstrap tests the case where the security
// config returned by the xDS server cannot be resolved based on the contents of
// the bootstrap config. Verifies that the balancer puts the channel in a failed
// state, and returns an error picker.
func (s) TestSecurityConfigNotFoundInBootstrap(t *testing.T) {
	// We test two cases here:
	// 0: Bootstrap contains security config. But received plugin instance name
	//    is not found in the bootstrap config.
	// 1: Bootstrap contains no security config.
	for i := 0; i < 2; i++ {
		// This creates a CDS balancer which uses xdsCredentials, pushes a
		// ClientConnState update with a fake xdsClient, and makes sure that the CDS
		// balancer registers a watch on the provided xdsClient.
		xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
		defer func() {
			cancel()
			cdsB.Close()
		}()

		if i == 0 {
			// Set the bootstrap config used by the fake client.
			xdsC.SetBootstrapConfig(bootstrapConfig)
		}

		// Here we invoke the watch callback registered on the fake xdsClient. A bad
		// security config is passed here. So, we expect the CDS balancer to not
		// create an EDS balancer and instead reject this update and put the channel
		// in a bad state.
		xdsC.InvokeWatchClusterCallback(cdsUpdateWithMissingSecurityCfg, nil)

		// The CDS balancer has not yet created an EDS balancer. So, this bad
		// watcher update should not be forwarded forwarded to our fake EDS balancer
		// as an error.
		sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
		defer sCancel()
		if err := edsB.waitForResolverError(sCtx, nil); err != context.DeadlineExceeded {
			t.Fatal("eds balancer shouldn't get error (shouldn't be built yet)")
		}

		// Make sure the CDS balancer reports an error picker.
		ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
		defer ctxCancel()
		if err := tcc.WaitForErrPicker(ctx); err != nil {
			t.Fatal(err)
		}
	}
}

// TestCertproviderStoreError tests the case where the certprovider.Store
// returns an error when the CDS balancer attempts to create a provider.
func (s) TestCertproviderStoreError(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Override the provider builder function to return an error.
	origBuildProvider := buildProvider
	buildProvider = func(c map[string]*certprovider.BuildableConfig, id, cert string, wi, wr bool) (certprovider.Provider, error) {
		return nil, errors.New("certprovider store error")
	}
	defer func() { buildProvider = origBuildProvider }()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. Even
	// though the received update is good, the certprovider.Store is configured
	// to return an error. So, CDS balancer should reject this config and report
	// an error.
	xdsC.InvokeWatchClusterCallback(cdsUpdateWithGoodSecurityCfg, nil)

	// The CDS balancer has not yet created an EDS balancer. So, this bad
	// watcher update should not be forwarded forwarded to our fake EDS balancer
	// as an error.
	sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
	defer sCancel()
	if err := edsB.waitForResolverError(sCtx, nil); err != context.DeadlineExceeded {
		t.Fatal("eds balancer shouldn't get error (shouldn't be built yet)")
	}

	// Make sure the CDS balancer reports an error picker.
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := tcc.WaitForErrPicker(ctx); err != nil {
		t.Fatal(err)
	}
}

func (s) TestSecurityConfigUpdate_BadToGood(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. A bad
	// security config is passed here. So, we expect the CDS balancer to not
	// create an EDS balancer and instead reject this update and put the channel
	// in a bad state.
	xdsC.InvokeWatchClusterCallback(cdsUpdateWithMissingSecurityCfg, nil)

	// The CDS balancer has not yet created an EDS balancer. So, this bad
	// watcher update should not be forwarded forwarded to our fake EDS balancer
	// as an error.
	sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
	defer sCancel()
	if err := edsB.waitForResolverError(sCtx, nil); err != context.DeadlineExceeded {
		t.Fatal("eds balancer shouldn't get error (shouldn't be built yet)")
	}

	// Make sure the CDS balancer reports an error picker.
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := tcc.WaitForErrPicker(ctx); err != nil {
		t.Fatal(err)
	}

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that attributes are added.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false); err != nil {
		t.Fatal(err)
	}
}

// TestGoodSecurityConfig tests the case where the CDS balancer receives
// security configuration as part of the Cluster resource which can be
// successfully resolved using the bootstrap file contents. Verifies that
// certificate providers are created, and that the NewSubConn() call adds
// appropriate address attributes.
func (s) TestGoodSecurityConfig(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that attributes are added.
	sc, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false)
	if err != nil {
		t.Fatal(err)
	}

	// Invoke UpdateAddresses and verify that attributes are added.
	dummyAddr := "bar-address"
	addrs := []resolver.Address{{Addr: dummyAddr}}
	edsB.parentCC.UpdateAddresses(sc, addrs)
	select {
	case <-ctx.Done():
		t.Fatal("timeout when waiting for addresses to be updated on the subConn")
	case gotAddrs := <-tcc.UpdateAddressesAddrsCh:
		if len(gotAddrs) != 1 {
			t.Fatalf("UpdateAddresses expected 1 address, got %d", len(gotAddrs))
		}
		if got, want := gotAddrs[0].Addr, addrs[0].Addr; got != want {
			t.Fatalf("resolver.Address passed to parent ClientConn through UpdateAddresses() has address %q, want %q", got, want)
		}
		getHI := internal.GetXDSHandshakeInfoForTesting.(func(attr *attributes.Attributes) *xdscredsinternal.HandshakeInfo)
		hi := getHI(gotAddrs[0].Attributes)
		if hi == nil {
			t.Fatal("resolver.Address passed to parent ClientConn through UpdateAddresses() doesn't contain attributes")
		}
	}
}

func (s) TestSecurityConfigUpdate_GoodToFallback(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that attributes are added.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false); err != nil {
		t.Fatal(err)
	}

	// Here we invoke the watch callback registered on the fake xdsClient with
	// an update which contains bad security config. So, we expect the CDS
	// balancer to forward this error to the EDS balancer and eventually the
	// channel needs to be put in a bad state.
	cdsUpdate := xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		LBPolicy:    wrrLocalityLBConfigJSON,
	}
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that fallback creds are used.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, true); err != nil {
		t.Fatal(err)
	}
}

// TestSecurityConfigUpdate_GoodToBad tests the case where the first security
// config returned by the xDS server is successful, but the second update cannot
// be resolved based on the contents of the bootstrap config. Verifies that the
// error is forwarded to the EDS balancer (which was created as part of the
// first successful update).
func (s) TestSecurityConfigUpdate_GoodToBad(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdateWithGoodSecurityCfg, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// Make a NewSubConn and verify that attributes are added.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false); err != nil {
		t.Fatal(err)
	}

	// Here we invoke the watch callback registered on the fake xdsClient with
	// an update which contains bad security config. So, we expect the CDS
	// balancer to forward this error to the EDS balancer and eventually the
	// channel needs to be put in a bad state.
	xdsC.InvokeWatchClusterCallback(cdsUpdateWithMissingSecurityCfg, nil)

	// We manually check that an error is forwarded to the EDS balancer instead
	// of using one of the helper methods on the testEDSBalancer, because all we
	// care here is whether an error is sent to it or not. We don't care about
	// the exact error.
	gotErr, err := edsB.resolverErrCh.Receive(ctx)
	if err != nil {
		t.Fatal("timeout waiting for CDS balancer to forward error to EDS balancer upon receipt of bad security config")
	}
	if gotErr == nil {
		t.Fatal("CDS balancer did not forward error to EDS balancer upon receipt of bad security config")
	}

	// Since the error being pushed here is not a resource-not-found-error, the
	// registered watch should not be cancelled.
	sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
	defer sCancel()
	if _, err := xdsC.WaitForCancelClusterWatch(sCtx); err != context.DeadlineExceeded {
		t.Fatal("cluster watch cancelled for a non-resource-not-found-error")
	}
}

// TestSecurityConfigUpdate_GoodToGood tests the case where the CDS balancer
// receives two different but successful updates with security configuration.
// Verifies that appropriate providers are created, and that address attributes
// are added.
func (s) TestSecurityConfigUpdate_GoodToGood(t *testing.T) {
	// This creates a CDS balancer which uses xdsCredentials, pushes a
	// ClientConnState update with a fake xdsClient, and makes sure that the CDS
	// balancer registers a watch on the provided xdsClient.
	xdsC, cdsB, edsB, tcc, cancel := setupWithXDSCreds(t)
	defer func() {
		cancel()
		cdsB.Close()
	}()

	// Override the provider builder function to push on a channel.
	providerCh := testutils.NewChannel()
	origBuildProvider := buildProvider
	buildProvider = func(c map[string]*certprovider.BuildableConfig, id, cert string, wi, wr bool) (certprovider.Provider, error) {
		p, err := origBuildProvider(c, id, cert, wi, wr)
		providerCh.Send(nil)
		return p, err
	}
	defer func() { buildProvider = origBuildProvider }()

	// Set the bootstrap config used by the fake client.
	xdsC.SetBootstrapConfig(bootstrapConfig)

	// Here we invoke the watch callback registered on the fake xdsClient. This
	// will trigger the watch handler on the CDS balancer, which will attempt to
	// create a new EDS balancer. The fake EDS balancer created above will be
	// returned to the CDS balancer, because we have overridden the
	// newChildBalancer function as part of test setup.
	cdsUpdate := xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		SecurityCfg: &xdsresource.SecurityConfig{
			RootInstanceName:       "default1",
			SubjectAltNameMatchers: testSANMatchers,
		},
		LBPolicy: wrrLocalityLBConfigJSON,
	}
	wantCCS := edsCCS(serviceName, nil, false, wrrLocalityLBConfigJSON, noopODLBCfgJSON)
	ctx, ctxCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer ctxCancel()
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// We specified only the root provider. So, expect only one provider here.
	if _, err := providerCh.Receive(ctx); err != nil {
		t.Fatalf("Failed to create certificate provider upon receipt of security config")
	}

	// Make a NewSubConn and verify that attributes are added.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false); err != nil {
		t.Fatal(err)
	}

	// Push another update with a new security configuration.
	cdsUpdate = xdsresource.ClusterUpdate{
		ClusterName: serviceName,
		SecurityCfg: &xdsresource.SecurityConfig{
			RootInstanceName:       "default2",
			SubjectAltNameMatchers: testSANMatchers,
		},
		LBPolicy: wrrLocalityLBConfigJSON,
	}
	if err := invokeWatchCbAndWait(ctx, xdsC, cdsWatchInfo{cdsUpdate, nil}, wantCCS, edsB); err != nil {
		t.Fatal(err)
	}

	// We specified only the root provider. So, expect only one provider here.
	if _, err := providerCh.Receive(ctx); err != nil {
		t.Fatalf("Failed to create certificate provider upon receipt of security config")
	}

	// Make a NewSubConn and verify that attributes are added.
	if _, err := makeNewSubConn(ctx, edsB.parentCC, tcc, false); err != nil {
		t.Fatal(err)
	}

	// The HandshakeInfo type does not expose its internals. So, we cannot
	// verify that the HandshakeInfo carried by the attributes have actually
	// been changed. This will be covered in e2e/interop tests.
	// TODO(easwars): Remove this TODO once appropriate e2e/intertop tests have
	// been added.
}
