vitess.io/vitess@v0.16.2/go/vt/vtadmin/testutil/cluster.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testutil
    18  
    19  import (
    20  	"context"
    21  	"database/sql"
    22  	"fmt"
    23  	"sync"
    24  	"testing"
    25  
    26  	"github.com/spf13/pflag"
    27  	"github.com/stretchr/testify/require"
    28  	"google.golang.org/grpc"
    29  
    30  	"vitess.io/vitess/go/vt/grpcclient"
    31  	"vitess.io/vitess/go/vt/topo"
    32  	"vitess.io/vitess/go/vt/topo/memorytopo"
    33  	"vitess.io/vitess/go/vt/vitessdriver"
    34  	"vitess.io/vitess/go/vt/vtadmin/cluster"
    35  	"vitess.io/vitess/go/vt/vtadmin/cluster/discovery"
    36  	"vitess.io/vitess/go/vt/vtadmin/cluster/discovery/fakediscovery"
    37  	vtadminvtctldclient "vitess.io/vitess/go/vt/vtadmin/vtctldclient"
    38  	"vitess.io/vitess/go/vt/vtadmin/vtsql"
    39  	"vitess.io/vitess/go/vt/vtadmin/vtsql/fakevtsql"
    40  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
    41  	grpcvtctldtestutil "vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil"
    42  	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
    43  	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
    44  
    45  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    46  	vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice"
    47  )
    48  
    49  // Dbcfg is a test utility for controlling the behavior of the cluster's DB
    50  // at the package sql level.
    51  type Dbcfg struct {
    52  	ShouldErr bool
    53  }
    54  
    55  // TestClusterConfig controls the way that a cluster.Cluster object is
    56  // constructed for testing vtadmin code.
    57  type TestClusterConfig struct {
    58  	// Cluster provides the protobuf-based version of the cluster info. It is
    59  	// to set the ID and Name of the resulting cluster.Cluster, as well as to
    60  	// name a single, phony, vtgate entry in the cluster's discovery service.
    61  	Cluster *vtadminpb.Cluster
    62  	// VtctldClient provides the vtctldclient.VtctldClient implementation the
    63  	// cluster's vtctld proxy will use. Most unit tests will use an instance of
    64  	// the VtctldClient type provided by this package in order to mock out the
    65  	// vtctld layer.
    66  	VtctldClient vtctldclient.VtctldClient
    67  	// Tablets provides the set of tablets reachable by this cluster's vtsql.DB.
    68  	// Tablets are copied, and then mutated to have their Cluster field set to
    69  	// match the Cluster provided by this TestClusterConfig, so mutations are
    70  	// transparent to the caller.
    71  	Tablets []*vtadminpb.Tablet
    72  	// DBConfig controls the behavior of the cluster's vtsql.DB.
    73  	DBConfig Dbcfg
    74  	// Config controls certain cluster config options, primarily used to
    75  	// properly setup various RPC pools for different testing scenarios.
    76  	// Other fields (such as ID, Name, and DiscoveryImpl) are ignored.
    77  	Config *cluster.Config
    78  }
    79  
    80  const discoveryTestImplName = "vtadmin.testutil"
    81  
    82  var (
    83  	m         sync.Mutex
    84  	testdisco discovery.Discovery
    85  )
    86  
    87  func init() {
    88  	discovery.Register(discoveryTestImplName, func(cluster *vtadminpb.Cluster, flags *pflag.FlagSet, args []string) (discovery.Discovery, error) {
    89  		return testdisco, nil
    90  	})
    91  }
    92  
    93  // BuildCluster is a shared helper for building a cluster based on the given
    94  // test configuration.
    95  func BuildCluster(t testing.TB, cfg TestClusterConfig) *cluster.Cluster {
    96  	t.Helper()
    97  
    98  	disco := fakediscovery.New()
    99  	disco.AddTaggedGates(nil, &vtadminpb.VTGate{Hostname: fmt.Sprintf("%s-%s-gate", cfg.Cluster.Name, cfg.Cluster.Id)})
   100  	disco.AddTaggedVtctlds(nil, &vtadminpb.Vtctld{Hostname: "doesn't matter"})
   101  
   102  	tablets := make([]*vtadminpb.Tablet, len(cfg.Tablets))
   103  	for i, t := range cfg.Tablets {
   104  		tablet := &vtadminpb.Tablet{
   105  			Cluster: cfg.Cluster,
   106  			Tablet:  t.Tablet,
   107  			State:   t.State,
   108  		}
   109  
   110  		tablets[i] = tablet
   111  	}
   112  
   113  	var clusterConf cluster.Config
   114  	if cfg.Config != nil {
   115  		clusterConf = *cfg.Config
   116  	}
   117  
   118  	clusterConf.ID = cfg.Cluster.Id
   119  	clusterConf.Name = cfg.Cluster.Name
   120  	clusterConf.DiscoveryImpl = discoveryTestImplName
   121  
   122  	clusterConf = clusterConf.WithVtctldTestConfigOptions(vtadminvtctldclient.WithDialFunc(func(addr string, ff grpcclient.FailFast, opts ...grpc.DialOption) (vtctldclient.VtctldClient, error) {
   123  		return cfg.VtctldClient, nil
   124  	})).WithVtSQLTestConfigOptions(vtsql.WithDialFunc(func(c vitessdriver.Configuration) (*sql.DB, error) {
   125  		return sql.OpenDB(&fakevtsql.Connector{Tablets: tablets, ShouldErr: cfg.DBConfig.ShouldErr}), nil
   126  	}))
   127  
   128  	m.Lock()
   129  	testdisco = disco
   130  	c, err := cluster.New(
   131  		context.Background(), // consider updating this function to allow callers to provide a context.
   132  		clusterConf,
   133  	)
   134  	m.Unlock()
   135  
   136  	require.NoError(t, err, "failed to create cluster from configs %+v %+v", clusterConf, cfg)
   137  
   138  	return c
   139  }
   140  
   141  // BuildClusters is a helper for building multiple clusters from a slice of
   142  // TestClusterConfigs.
   143  func BuildClusters(t testing.TB, cfgs ...TestClusterConfig) []*cluster.Cluster {
   144  	clusters := make([]*cluster.Cluster, len(cfgs))
   145  
   146  	for i, cfg := range cfgs {
   147  		clusters[i] = BuildCluster(t, cfg)
   148  	}
   149  
   150  	return clusters
   151  }
   152  
   153  // IntegrationTestCluster is a vtadmin cluster suitable for use in integration
   154  // tests. It contains the cluster struct, the topo server backing the cluster,
   155  // and the memorytopo.Factory to force topo errors for certain test cases.
   156  type IntegrationTestCluster struct {
   157  	Cluster     *cluster.Cluster
   158  	Topo        *topo.Server
   159  	TopoFactory *memorytopo.Factory
   160  }
   161  
   162  // BuildIntegrationTestCluster is a helper for building a test cluster with a
   163  // real grpcvtctldserver-backing implementation.
   164  //
   165  // (TODO|@ajm188): Unify this with the BuildCluster API. Also this does not
   166  // support any cluster methods that involve vtgate/vitessdriver queries.
   167  func BuildIntegrationTestCluster(t testing.TB, c *vtadminpb.Cluster, cells ...string) *IntegrationTestCluster {
   168  	t.Helper()
   169  
   170  	ts, factory := memorytopo.NewServerAndFactory(cells...)
   171  	vtctld := grpcvtctldtestutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   172  		return grpcvtctldserver.NewVtctldServer(ts)
   173  	})
   174  
   175  	localclient := localvtctldclient.New(vtctld)
   176  
   177  	testcluster := BuildCluster(t, TestClusterConfig{
   178  		Cluster:      c,
   179  		VtctldClient: localclient,
   180  	})
   181  	return &IntegrationTestCluster{
   182  		Cluster:     testcluster,
   183  		Topo:        ts,
   184  		TopoFactory: factory,
   185  	}
   186  }