vitess.io/vitess@v0.16.2/go/cmd/vtcombo/main.go (about)

     1  /*
     2  Copyright 2019 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  // vtcombo: a single binary that contains:
    18  // - a ZK topology server based on an in-memory map.
    19  // - one vtgate instance.
    20  // - many vttablet instances.
    21  // - a vtctld instance so it's easy to see the topology.
    22  package main
    23  
    24  import (
    25  	"context"
    26  	"os"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/spf13/pflag"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	"vitess.io/vitess/go/acl"
    34  	"vitess.io/vitess/go/exit"
    35  	"vitess.io/vitess/go/mysql"
    36  	"vitess.io/vitess/go/vt/dbconfigs"
    37  	"vitess.io/vitess/go/vt/log"
    38  	"vitess.io/vitess/go/vt/logutil"
    39  	"vitess.io/vitess/go/vt/mysqlctl"
    40  	"vitess.io/vitess/go/vt/servenv"
    41  	"vitess.io/vitess/go/vt/srvtopo"
    42  	"vitess.io/vitess/go/vt/topo"
    43  	"vitess.io/vitess/go/vt/topo/memorytopo"
    44  	"vitess.io/vitess/go/vt/topotools"
    45  	"vitess.io/vitess/go/vt/vtcombo"
    46  	"vitess.io/vitess/go/vt/vtctld"
    47  	"vitess.io/vitess/go/vt/vtgate"
    48  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    49  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    50  	"vitess.io/vitess/go/vt/vttest"
    51  	"vitess.io/vitess/go/vt/wrangler"
    52  
    53  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    54  	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
    55  )
    56  
    57  var (
    58  	flags              = pflag.NewFlagSet("vtcombo", pflag.ContinueOnError)
    59  	schemaDir          = flags.String("schema_dir", "", "Schema base directory. Should contain one directory per keyspace, with a vschema.json file if necessary.")
    60  	startMysql         = flags.Bool("start_mysql", false, "Should vtcombo also start mysql")
    61  	mysqlPort          = flags.Int("mysql_port", 3306, "mysql port")
    62  	externalTopoServer = flags.Bool("external_topo_server", false, "Should vtcombo use an external topology server instead of starting its own in-memory topology server. "+
    63  		"If true, vtcombo will use the flags defined in topo/server.go to open topo server")
    64  	plannerName = flags.String("planner-version", "", "Sets the default planner to use when the session has not changed it. Valid values are: V3, Gen4, Gen4Greedy and Gen4Fallback. Gen4Fallback tries the gen4 planner and falls back to the V3 planner if the gen4 fails.")
    65  
    66  	tpb             vttestpb.VTTestTopology
    67  	ts              *topo.Server
    68  	resilientServer *srvtopo.ResilientServer
    69  )
    70  
    71  func init() {
    72  	flags.Var(vttest.TextTopoData(&tpb), "proto_topo", "vttest proto definition of the topology, encoded in compact text format. See vttest.proto for more information.")
    73  	flags.Var(vttest.JSONTopoData(&tpb), "json_topo", "vttest proto definition of the topology, encoded in json format. See vttest.proto for more information.")
    74  
    75  	servenv.RegisterDefaultFlags()
    76  	servenv.RegisterFlags()
    77  	servenv.RegisterGRPCServerFlags()
    78  	servenv.RegisterGRPCServerAuthFlags()
    79  	servenv.RegisterServiceMapFlag()
    80  }
    81  
    82  func startMysqld(uid uint32) (*mysqlctl.Mysqld, *mysqlctl.Mycnf) {
    83  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    84  	mycnfFile := mysqlctl.MycnfFile(uid)
    85  
    86  	var mysqld *mysqlctl.Mysqld
    87  	var cnf *mysqlctl.Mycnf
    88  	var err error
    89  
    90  	if _, statErr := os.Stat(mycnfFile); os.IsNotExist(statErr) {
    91  		mysqld, cnf, err = mysqlctl.CreateMysqldAndMycnf(uid, "", int32(*mysqlPort))
    92  		if err != nil {
    93  			log.Errorf("failed to initialize mysql config :%v", err)
    94  			exit.Return(1)
    95  		}
    96  		if err := mysqld.Init(ctx, cnf, ""); err != nil {
    97  			log.Errorf("failed to initialize mysql :%v", err)
    98  			exit.Return(1)
    99  		}
   100  	} else {
   101  		mysqld, cnf, err = mysqlctl.OpenMysqldAndMycnf(uid)
   102  		if err != nil {
   103  			log.Errorf("failed to find mysql config: %v", err)
   104  			exit.Return(1)
   105  		}
   106  		err = mysqld.RefreshConfig(ctx, cnf)
   107  		if err != nil {
   108  			log.Errorf("failed to refresh config: %v", err)
   109  			exit.Return(1)
   110  		}
   111  		if err := mysqld.Start(ctx, cnf); err != nil {
   112  			log.Errorf("Failed to start mysqld: %v", err)
   113  			exit.Return(1)
   114  		}
   115  	}
   116  	cancel()
   117  	return mysqld, cnf
   118  }
   119  
   120  func main() {
   121  	defer exit.Recover()
   122  	// flag parsing
   123  	var globalFlags *pflag.FlagSet
   124  	dbconfigs.RegisterFlags(dbconfigs.All...)
   125  	mysqlctl.RegisterFlags()
   126  	servenv.OnParseFor("vtcombo", func(fs *pflag.FlagSet) {
   127  		// We're going to force the value later, so don't even bother letting
   128  		// the user know about this flag.
   129  		fs.MarkHidden("tablet_protocol")
   130  
   131  		// Add the vtcombo flags declared above in var/init sections to the
   132  		// global flags.
   133  		fs.AddFlagSet(flags)
   134  		// Save for later -- see comment directly after ParseFlags for why.
   135  		globalFlags = fs
   136  
   137  		acl.RegisterFlags(fs)
   138  	})
   139  
   140  	servenv.ParseFlags("vtcombo")
   141  
   142  	// At this point, servenv.ParseFlags has invoked _flag.Parse, which has
   143  	// combined all the flags everywhere into the globalFlags variable we
   144  	// stashed a reference to earlier in our OnParseFor callback function.
   145  	//
   146  	// We now take those flags and make them available to our `flags` instance,
   147  	// which we call `Set` on various flags to force their values further down
   148  	// in main().
   149  	//
   150  	// N.B.: we could just as easily call Set on globalFlags on everything
   151  	// (including our local flags), but we need to save a reference either way,
   152  	// and that in particular (globalFlags.Set on a local flag) feels more
   153  	// potentially confusing than its inverse (flags.Set on a global flag), so
   154  	// we go this way.
   155  	flags.AddFlagSet(globalFlags)
   156  
   157  	// Stash away a copy of the topology that vtcombo was started with.
   158  	//
   159  	// We will use this to determine the shard structure when keyspaces
   160  	// get recreated.
   161  	originalTopology := proto.Clone(&tpb).(*vttestpb.VTTestTopology)
   162  
   163  	// default cell to "test" if unspecified
   164  	if len(tpb.Cells) == 0 {
   165  		tpb.Cells = append(tpb.Cells, "test")
   166  	}
   167  
   168  	flags.Set("cells_to_watch", strings.Join(tpb.Cells, ","))
   169  
   170  	// vtctld UI requires the cell flag
   171  	flags.Set("cell", tpb.Cells[0])
   172  	if flags.Lookup("log_dir") == nil {
   173  		flags.Set("log_dir", "$VTDATAROOT/tmp")
   174  	}
   175  
   176  	if *externalTopoServer {
   177  		// Open topo server based on the command line flags defined at topo/server.go
   178  		// do not create cell info as it should be done by whoever sets up the external topo server
   179  		ts = topo.Open()
   180  	} else {
   181  		// Create topo server. We use a 'memorytopo' implementation.
   182  		ts = memorytopo.NewServer(tpb.Cells...)
   183  	}
   184  
   185  	// attempt to load any routing rules specified by tpb
   186  	if err := vtcombo.InitRoutingRules(context.Background(), ts, tpb.GetRoutingRules()); err != nil {
   187  		log.Errorf("Failed to load routing rules: %v", err)
   188  		exit.Return(1)
   189  	}
   190  
   191  	servenv.Init()
   192  	tabletenv.Init()
   193  
   194  	mysqld := &vtcomboMysqld{}
   195  	var cnf *mysqlctl.Mycnf
   196  	if *startMysql {
   197  		mysqld.Mysqld, cnf = startMysqld(1)
   198  		servenv.OnClose(func() {
   199  			mysqld.Shutdown(context.TODO(), cnf, true)
   200  		})
   201  		// We want to ensure we can write to this database
   202  		mysqld.SetReadOnly(false)
   203  
   204  	} else {
   205  		dbconfigs.GlobalDBConfigs.InitWithSocket("")
   206  		mysqld.Mysqld = mysqlctl.NewMysqld(&dbconfigs.GlobalDBConfigs)
   207  		servenv.OnClose(mysqld.Close)
   208  	}
   209  
   210  	// Tablet configuration and init.
   211  	// Send mycnf as nil because vtcombo won't do backups and restores.
   212  	//
   213  	// Also force the `--tablet_manager_protocol` and `--tablet_protocol` flags
   214  	// to be the "internal" protocol that InitTabletMap registers.
   215  	flags.Set("tablet_manager_protocol", "internal")
   216  	flags.Set("tablet_protocol", "internal")
   217  	uid, err := vtcombo.InitTabletMap(ts, &tpb, mysqld, &dbconfigs.GlobalDBConfigs, *schemaDir, *startMysql)
   218  	if err != nil {
   219  		log.Errorf("initTabletMapProto failed: %v", err)
   220  		// ensure we start mysql in the event we fail here
   221  		if *startMysql {
   222  			mysqld.Shutdown(context.TODO(), cnf, true)
   223  		}
   224  		exit.Return(1)
   225  	}
   226  
   227  	globalCreateDb = func(ctx context.Context, ks *vttestpb.Keyspace) error {
   228  		// Check if we're recreating a keyspace that was previously deleted by looking
   229  		// at the original topology definition.
   230  		//
   231  		// If we find a matching keyspace, we create it with the same sharding
   232  		// configuration. This ensures that dropping and recreating a keyspace
   233  		// will end up with the same number of shards.
   234  		for _, originalKs := range originalTopology.Keyspaces {
   235  			if originalKs.Name == ks.Name {
   236  				ks = proto.Clone(originalKs).(*vttestpb.Keyspace)
   237  			}
   238  		}
   239  
   240  		wr := wrangler.New(logutil.NewConsoleLogger(), ts, nil)
   241  		newUID, err := vtcombo.CreateKs(ctx, ts, &tpb, mysqld, &dbconfigs.GlobalDBConfigs, *schemaDir, ks, true, uid, wr)
   242  		if err != nil {
   243  			return err
   244  		}
   245  		uid = newUID
   246  		tpb.Keyspaces = append(tpb.Keyspaces, ks)
   247  		return nil
   248  	}
   249  
   250  	globalDropDb = func(ctx context.Context, ksName string) error {
   251  		if err := vtcombo.DeleteKs(ctx, ts, ksName, mysqld, &tpb); err != nil {
   252  			return err
   253  		}
   254  
   255  		// Rebuild the SrvVSchema object
   256  		if err := ts.RebuildSrvVSchema(ctx, tpb.Cells); err != nil {
   257  			return err
   258  		}
   259  
   260  		return nil
   261  	}
   262  
   263  	// Now that we have fully initialized the tablets, rebuild the keyspace graph.
   264  	for _, ks := range tpb.Keyspaces {
   265  		err := topotools.RebuildKeyspace(context.Background(), logutil.NewConsoleLogger(), ts, ks.GetName(), tpb.Cells, false)
   266  		if err != nil {
   267  			if *startMysql {
   268  				mysqld.Shutdown(context.TODO(), cnf, true)
   269  			}
   270  			log.Fatalf("Couldn't build srv keyspace for (%v: %v). Got error: %v", ks, tpb.Cells, err)
   271  		}
   272  	}
   273  
   274  	// vtgate configuration and init
   275  	resilientServer = srvtopo.NewResilientServer(ts, "ResilientSrvTopoServer")
   276  	tabletTypesToWait := []topodatapb.TabletType{
   277  		topodatapb.TabletType_PRIMARY,
   278  		topodatapb.TabletType_REPLICA,
   279  		topodatapb.TabletType_RDONLY,
   280  	}
   281  	plannerVersion, _ := plancontext.PlannerNameToVersion(*plannerName)
   282  
   283  	vtgate.QueryLogHandler = "/debug/vtgate/querylog"
   284  	vtgate.QueryLogzHandler = "/debug/vtgate/querylogz"
   285  	vtgate.QueryzHandler = "/debug/vtgate/queryz"
   286  	// pass nil for healthcheck, it will get created
   287  	vtg := vtgate.Init(context.Background(), nil, resilientServer, tpb.Cells[0], tabletTypesToWait, plannerVersion)
   288  
   289  	// vtctld configuration and init
   290  	err = vtctld.InitVtctld(ts)
   291  	if err != nil {
   292  		exit.Return(1)
   293  	}
   294  
   295  	servenv.OnRun(func() {
   296  		addStatusParts(vtg)
   297  	})
   298  
   299  	servenv.OnTerm(func() {
   300  		log.Error("Terminating")
   301  		// FIXME(alainjobart): stop vtgate
   302  	})
   303  	servenv.OnClose(func() {
   304  		// We will still use the topo server during lameduck period
   305  		// to update our state, so closing it in OnClose()
   306  		ts.Close()
   307  	})
   308  	servenv.RunDefault()
   309  }
   310  
   311  // vtcomboMysqld is a wrapper on top of mysqlctl.Mysqld.
   312  // We need this wrapper because vtcombo runs with a single MySQL instance
   313  // which all the tablets connect to. (replica, primary, all). This means that we shouldn't
   314  // be trying to run any replication related commands on it, otherwise they fail.
   315  type vtcomboMysqld struct {
   316  	*mysqlctl.Mysqld
   317  }
   318  
   319  // SetReplicationSource implements the MysqlDaemon interface
   320  func (mysqld *vtcomboMysqld) SetReplicationSource(ctx context.Context, host string, port int, replicationStopBefore bool, replicationStartAfter bool) error {
   321  	return nil
   322  }
   323  
   324  // StartReplication implements the MysqlDaemon interface
   325  func (mysqld *vtcomboMysqld) StartReplication(hookExtraEnv map[string]string) error {
   326  	return nil
   327  }
   328  
   329  // RestartReplication implements the MysqlDaemon interface
   330  func (mysqld *vtcomboMysqld) RestartReplication(hookExtraEnv map[string]string) error {
   331  	return nil
   332  }
   333  
   334  // StartReplicationUntilAfter implements the MysqlDaemon interface
   335  func (mysqld *vtcomboMysqld) StartReplicationUntilAfter(ctx context.Context, pos mysql.Position) error {
   336  	return nil
   337  }
   338  
   339  // StopReplication implements the MysqlDaemon interface
   340  func (mysqld *vtcomboMysqld) StopReplication(hookExtraEnv map[string]string) error {
   341  	return nil
   342  }
   343  
   344  // SetSemiSyncEnabled implements the MysqlDaemon interface
   345  func (mysqld *vtcomboMysqld) SetSemiSyncEnabled(source, replica bool) error {
   346  	return nil
   347  }