vitess.io/vitess@v0.16.2/go/cmd/vttablet/vttablet.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  // vt tablet server: Serves queries and performs housekeeping jobs.
    18  package main
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/spf13/pflag"
    27  
    28  	"vitess.io/vitess/go/acl"
    29  	"vitess.io/vitess/go/vt/binlog"
    30  	"vitess.io/vitess/go/vt/dbconfigs"
    31  	"vitess.io/vitess/go/vt/log"
    32  	"vitess.io/vitess/go/vt/mysqlctl"
    33  	"vitess.io/vitess/go/vt/servenv"
    34  	"vitess.io/vitess/go/vt/tableacl"
    35  	"vitess.io/vitess/go/vt/tableacl/simpleacl"
    36  	"vitess.io/vitess/go/vt/topo"
    37  	"vitess.io/vitess/go/vt/topo/topoproto"
    38  	"vitess.io/vitess/go/vt/vttablet/onlineddl"
    39  	"vitess.io/vitess/go/vt/vttablet/tabletmanager"
    40  	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vdiff"
    41  	"vitess.io/vitess/go/vt/vttablet/tabletmanager/vreplication"
    42  	"vitess.io/vitess/go/vt/vttablet/tabletserver"
    43  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    44  	"vitess.io/vitess/go/yaml2"
    45  	"vitess.io/vitess/resources"
    46  
    47  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    48  )
    49  
    50  var (
    51  	enforceTableACLConfig        bool
    52  	tableACLConfig               string
    53  	tableACLConfigReloadInterval time.Duration
    54  	tabletPath                   string
    55  	tabletConfig                 string
    56  
    57  	tm *tabletmanager.TabletManager
    58  )
    59  
    60  func registerFlags(fs *pflag.FlagSet) {
    61  	fs.BoolVar(&enforceTableACLConfig, "enforce-tableacl-config", enforceTableACLConfig, "if this flag is true, vttablet will fail to start if a valid tableacl config does not exist")
    62  	fs.StringVar(&tableACLConfig, "table-acl-config", tableACLConfig, "path to table access checker config file; send SIGHUP to reload this file")
    63  	fs.DurationVar(&tableACLConfigReloadInterval, "table-acl-config-reload-interval", tableACLConfigReloadInterval, "Ticker to reload ACLs. Duration flag, format e.g.: 30s. Default: do not reload")
    64  	fs.StringVar(&tabletPath, "tablet-path", tabletPath, "tablet alias")
    65  	fs.StringVar(&tabletConfig, "tablet_config", tabletConfig, "YAML file config for tablet")
    66  
    67  	acl.RegisterFlags(fs)
    68  }
    69  
    70  func init() {
    71  	servenv.RegisterDefaultFlags()
    72  	servenv.RegisterFlags()
    73  	servenv.RegisterGRPCServerFlags()
    74  	servenv.RegisterGRPCServerAuthFlags()
    75  	servenv.RegisterServiceMapFlag()
    76  	servenv.OnParseFor("vttablet", registerFlags)
    77  }
    78  
    79  func main() {
    80  	dbconfigs.RegisterFlags(dbconfigs.All...)
    81  	mysqlctl.RegisterFlags()
    82  
    83  	servenv.ParseFlags("vttablet")
    84  	servenv.Init()
    85  
    86  	if tabletPath == "" {
    87  		log.Exit("--tablet-path required")
    88  	}
    89  	tabletAlias, err := topoproto.ParseTabletAlias(tabletPath)
    90  	if err != nil {
    91  		log.Exitf("failed to parse --tablet-path: %v", err)
    92  	}
    93  
    94  	// config and mycnf initializations are intertwined.
    95  	config, mycnf := initConfig(tabletAlias)
    96  
    97  	ts := topo.Open()
    98  	qsc := createTabletServer(config, ts, tabletAlias)
    99  
   100  	mysqld := mysqlctl.NewMysqld(config.DB)
   101  	servenv.OnClose(mysqld.Close)
   102  
   103  	if err := extractOnlineDDL(); err != nil {
   104  		log.Exitf("failed to extract online DDL binaries: %v", err)
   105  	}
   106  
   107  	// Initialize and start tm.
   108  	gRPCPort := int32(0)
   109  	if servenv.GRPCPort() != 0 {
   110  		gRPCPort = int32(servenv.GRPCPort())
   111  	}
   112  	tablet, err := tabletmanager.BuildTabletFromInput(tabletAlias, int32(servenv.Port()), gRPCPort, mysqld.GetVersionString(), config.DB)
   113  	if err != nil {
   114  		log.Exitf("failed to parse --tablet-path: %v", err)
   115  	}
   116  	tm = &tabletmanager.TabletManager{
   117  		BatchCtx:            context.Background(),
   118  		TopoServer:          ts,
   119  		Cnf:                 mycnf,
   120  		MysqlDaemon:         mysqld,
   121  		DBConfigs:           config.DB.Clone(),
   122  		QueryServiceControl: qsc,
   123  		UpdateStream:        binlog.NewUpdateStream(ts, tablet.Keyspace, tabletAlias.Cell, qsc.SchemaEngine()),
   124  		VREngine:            vreplication.NewEngine(config, ts, tabletAlias.Cell, mysqld, qsc.LagThrottler()),
   125  		VDiffEngine:         vdiff.NewEngine(config, ts, tablet),
   126  	}
   127  	if err := tm.Start(tablet, config.Healthcheck.IntervalSeconds.Get()); err != nil {
   128  		log.Exitf("failed to parse --tablet-path or initialize DB credentials: %v", err)
   129  	}
   130  	servenv.OnClose(func() {
   131  		// Close the tm so that our topo entry gets pruned properly and any
   132  		// background goroutines that use the topo connection are stopped.
   133  		tm.Close()
   134  
   135  		// tm uses ts. So, it should be closed after tm.
   136  		ts.Close()
   137  	})
   138  
   139  	servenv.RunDefault()
   140  }
   141  
   142  func initConfig(tabletAlias *topodatapb.TabletAlias) (*tabletenv.TabletConfig, *mysqlctl.Mycnf) {
   143  	tabletenv.Init()
   144  	// Load current config after tabletenv.Init, because it changes it.
   145  	config := tabletenv.NewCurrentConfig()
   146  	if err := config.Verify(); err != nil {
   147  		log.Exitf("invalid config: %v", err)
   148  	}
   149  
   150  	if tabletConfig != "" {
   151  		bytes, err := os.ReadFile(tabletConfig)
   152  		if err != nil {
   153  			log.Exitf("error reading config file %s: %v", tabletConfig, err)
   154  		}
   155  		if err := yaml2.Unmarshal(bytes, config); err != nil {
   156  			log.Exitf("error parsing config file %s: %v", bytes, err)
   157  		}
   158  	}
   159  	gotBytes, _ := yaml2.Marshal(config)
   160  	log.Infof("Loaded config file %s successfully:\n%s", tabletConfig, gotBytes)
   161  
   162  	var mycnf *mysqlctl.Mycnf
   163  	var socketFile string
   164  	// If no connection parameters were specified, load the mycnf file
   165  	// and use the socket from it. If connection parameters were specified,
   166  	// we assume that the mysql is not local, and we skip loading mycnf.
   167  	// This also means that backup and restore will not be allowed.
   168  	if !config.DB.HasGlobalSettings() {
   169  		var err error
   170  		if mycnf, err = mysqlctl.NewMycnfFromFlags(tabletAlias.Uid); err != nil {
   171  			log.Exitf("mycnf read failed: %v", err)
   172  		}
   173  		socketFile = mycnf.SocketFile
   174  	} else {
   175  		log.Info("connection parameters were specified. Not loading my.cnf.")
   176  	}
   177  
   178  	// If connection parameters were specified, socketFile will be empty.
   179  	// Otherwise, the socketFile (read from mycnf) will be used to initialize
   180  	// dbconfigs.
   181  	config.DB.InitWithSocket(socketFile)
   182  	for _, cfg := range config.ExternalConnections {
   183  		cfg.InitWithSocket("")
   184  	}
   185  	return config, mycnf
   186  }
   187  
   188  // extractOnlineDDL extracts the gh-ost binary from this executable. gh-ost is appended
   189  // to vttablet executable by `make build` with a go:embed
   190  func extractOnlineDDL() error {
   191  	if binaryFileName, isOverride := onlineddl.GhostBinaryFileName(); !isOverride {
   192  		if err := os.WriteFile(binaryFileName, resources.GhostBinary, 0755); err != nil {
   193  			// One possibility of failure is that gh-ost is up and running. In that case,
   194  			// let's pause and check if the running gh-ost is exact same binary as the one we wish to extract.
   195  			foundBytes, _ := os.ReadFile(binaryFileName)
   196  			if bytes.Equal(resources.GhostBinary, foundBytes) {
   197  				// OK, it's the same binary, there is no need to extract the file anyway
   198  				return nil
   199  			}
   200  			return err
   201  		}
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func createTabletServer(config *tabletenv.TabletConfig, ts *topo.Server, tabletAlias *topodatapb.TabletAlias) *tabletserver.TabletServer {
   208  	if tableACLConfig != "" {
   209  		// To override default simpleacl, other ACL plugins must set themselves to be default ACL factory
   210  		tableacl.Register("simpleacl", &simpleacl.Factory{})
   211  	} else if enforceTableACLConfig {
   212  		log.Exit("table acl config has to be specified with table-acl-config flag because enforce-tableacl-config is set.")
   213  	}
   214  	// creates and registers the query service
   215  	qsc := tabletserver.NewTabletServer("", config, ts, tabletAlias)
   216  	servenv.OnRun(func() {
   217  		qsc.Register()
   218  		addStatusParts(qsc)
   219  	})
   220  	servenv.OnClose(qsc.StopService)
   221  	qsc.InitACL(tableACLConfig, enforceTableACLConfig, tableACLConfigReloadInterval)
   222  	return qsc
   223  }