func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) { tabletHealthCache := newTabletHealthCache(ts) // Cells handleCollection("cells", func(r *http.Request) (interface{}, error) { if getItemPath(r.URL.Path) != "" { return nil, errors.New("cells can only be listed, not retrieved") } return ts.GetKnownCells(ctx) }) // Keyspaces handleCollection("keyspaces", func(r *http.Request) (interface{}, error) { keyspace := getItemPath(r.URL.Path) // List all keyspaces. if keyspace == "" { return ts.GetKeyspaces(ctx) } // Perform an action on a keyspace. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyKeyspaceAction(ctx, action, keyspace, r), nil } // Get the keyspace record. return ts.GetKeyspace(ctx, keyspace) }) // Shards handleCollection("shards", func(r *http.Request) (interface{}, error) { shardPath := getItemPath(r.URL.Path) if !strings.Contains(shardPath, "/") { return nil, fmt.Errorf("invalid shard path: %q", shardPath) } parts := strings.SplitN(shardPath, "/", 2) keyspace := parts[0] shard := parts[1] // List the shards in a keyspace. if shard == "" { return ts.GetShardNames(ctx, keyspace) } // Perform an action on a shard. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyShardAction(ctx, action, keyspace, shard, r), nil } // Get the shard record. return ts.GetShard(ctx, keyspace, shard) }) // Tablets handleCollection("tablets", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) // List tablets based on query params. if tabletPath == "" { if err := r.ParseForm(); err != nil { return nil, err } shardRef := r.FormValue("shard") cell := r.FormValue("cell") if shardRef != "" { // Look up by keyspace/shard, and optionally cell. keyspace, shard, err := topoproto.ParseKeyspaceShard(shardRef) if err != nil { return nil, err } if cell != "" { return ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, []string{cell}) } return ts.FindAllTabletAliasesInShard(ctx, keyspace, shard) } // Get all tablets in a cell. if cell == "" { return nil, errors.New("cell param required") } return ts.GetTabletsByCell(ctx, cell) } // Get tablet health. if parts := strings.Split(tabletPath, "/"); len(parts) == 2 && parts[1] == "health" { tabletAlias, err := topoproto.ParseTabletAlias(parts[0]) if err != nil { return nil, err } return tabletHealthCache.Get(ctx, tabletAlias) } tabletAlias, err := topoproto.ParseTabletAlias(tabletPath) if err != nil { return nil, err } // Perform an action on a tablet. if r.Method == "POST" { if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("must specify action") } return actions.ApplyTabletAction(ctx, action, tabletAlias, r), nil } // Get the tablet record. return ts.GetTablet(ctx, tabletAlias) }) // EndPoints handleCollection("endpoints", func(r *http.Request) (interface{}, error) { // We expect cell/keyspace/shard/tabletType. epPath := getItemPath(r.URL.Path) parts := strings.Split(epPath, "/") if len(parts) != 4 { return nil, fmt.Errorf("invalid cell/keyspace/shard/tabletType: %q", epPath) } if parts[3] == "" { // tabletType is empty, so list the tablet types. return ts.GetSrvTabletTypesPerShard(ctx, parts[0], parts[1], parts[2]) } tabletType, err := topoproto.ParseTabletType(parts[3]) if err != nil { return nil, fmt.Errorf("invalid tablet type %v: %v", parts[3], err) } // Get the endpoints object for a specific type. ep, _, err := ts.GetEndPoints(ctx, parts[0], parts[1], parts[2], tabletType) return ep, err }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { req := struct{ Keyspace, SQL string }{} if err := unmarshalRequest(r, &req); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } executor := schemamanager.NewTabletExecutor( tmclient.NewTabletManagerClient(), ts) schemamanager.Run(ctx, schemamanager.NewUIController(req.SQL, req.Keyspace, w), executor) }) // VSchema http.HandleFunc(apiPrefix+"vschema/", func(w http.ResponseWriter, r *http.Request) { // Save VSchema if r.Method == "POST" { vschema, err := ioutil.ReadAll(r.Body) if err != nil { httpErrorf(w, r, "can't read request body: %v", err) return } if err := ts.SaveVSchema(ctx, string(vschema)); err != nil { httpErrorf(w, r, "can't save vschema: %v", err) } return } // Get VSchema vschema, err := ts.GetVSchema(ctx) if err != nil { httpErrorf(w, r, "can't get vschema: %v", err) return } w.Header().Set("Content-Type", jsonContentType) w.Write([]byte(vschema)) }) }
// initTabletMap creates the action agents and associated data structures // for all tablets, based on the vttest proto parameter. func initTabletMap(ts topo.Server, topoProto string, mysqld mysqlctl.MysqlDaemon, dbcfgs dbconfigs.DBConfigs, schemaDir string, mycnf *mysqlctl.Mycnf) error { // parse the input topology tpb := &vttestpb.VTTestTopology{} if err := proto.UnmarshalText(topoProto, tpb); err != nil { return fmt.Errorf("cannot parse topology: %v", err) } tabletMap = make(map[uint32]*tablet) ctx := context.Background() // disable publishing of stats from query service flag.Set("queryserver-config-enable-publish-stats", "false") // iterate through the keyspaces wr := wrangler.New(logutil.NewConsoleLogger(), ts, nil) var uid uint32 = 1 for _, kpb := range tpb.Keyspaces { keyspace := kpb.Name // First parse the ShardingColumnType. // Note if it's empty, we will return 'UNSET'. sct, err := key.ParseKeyspaceIDType(kpb.ShardingColumnType) if err != nil { return fmt.Errorf("parseKeyspaceIDType(%v) failed: %v", kpb.ShardingColumnType, err) } if kpb.ServedFrom != "" { // if we have a redirect, create a completely redirected // keyspace and no tablet if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{ ShardingColumnName: kpb.ShardingColumnName, ShardingColumnType: sct, ServedFroms: []*topodatapb.Keyspace_ServedFrom{ { TabletType: topodatapb.TabletType_MASTER, Keyspace: kpb.ServedFrom, }, { TabletType: topodatapb.TabletType_REPLICA, Keyspace: kpb.ServedFrom, }, { TabletType: topodatapb.TabletType_RDONLY, Keyspace: kpb.ServedFrom, }, }, }); err != nil { return fmt.Errorf("CreateKeyspace(%v) failed: %v", keyspace, err) } } else { // create a regular keyspace if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{ ShardingColumnName: kpb.ShardingColumnName, ShardingColumnType: sct, }); err != nil { return fmt.Errorf("CreateKeyspace(%v) failed: %v", keyspace, err) } // iterate through the shards for _, spb := range kpb.Shards { shard := spb.Name dbname := spb.DbNameOverride if dbname == "" { dbname = fmt.Sprintf("vt_%v_%v", keyspace, shard) } dbcfgs.App.DbName = dbname // create the master if err := createTablet(ctx, ts, cell, uid, keyspace, shard, dbname, topodatapb.TabletType_MASTER, mysqld, dbcfgs); err != nil { return err } uid++ // create a replica slave if err := createTablet(ctx, ts, cell, uid, keyspace, shard, dbname, topodatapb.TabletType_REPLICA, mysqld, dbcfgs); err != nil { return err } uid++ // create a rdonly slave if err := createTablet(ctx, ts, cell, uid, keyspace, shard, dbname, topodatapb.TabletType_RDONLY, mysqld, dbcfgs); err != nil { return err } uid++ } } // vschema for the keyspace if schemaDir != "" { f := path.Join(schemaDir, keyspace, "vschema.json") if _, err := os.Stat(f); err == nil { // load the vschema formal, err := vindexes.LoadFormalKeyspace(f) if err != nil { return fmt.Errorf("cannot load vschema file %v for keyspace %v: %v", f, keyspace, err) } if err := ts.SaveVSchema(ctx, keyspace, formal); err != nil { return fmt.Errorf("SaveVSchema(%v) failed: %v", keyspace, err) } } else { log.Infof("File %v doesn't exist, skipping vschema for keyspace %v", f, keyspace) } } // Rebuild the SrvKeyspace object, so we can support // range-based sharding queries, and export the redirects. if err := wr.RebuildKeyspaceGraph(ctx, keyspace, nil); err != nil { return fmt.Errorf("cannot rebuild %v: %v", keyspace, err) } } // Rebuild the SrvVSchema object if err := topotools.RebuildVSchema(ctx, wr.Logger(), ts, []string{cell}); err != nil { return fmt.Errorf("RebuildVSchemaGraph failed: %v", err) } // Register the tablet dialer for tablet server tabletconn.RegisterDialer("internal", dialer) *tabletconn.TabletProtocol = "internal" // Register the tablet manager client factory for tablet manager tmclient.RegisterTabletManagerClientFactory("internal", func() tmclient.TabletManagerClient { return &internalTabletManagerClient{} }) *tmclient.TabletManagerProtocol = "internal" // run healthcheck on all vttablets tmc := tmclient.NewTabletManagerClient() for _, tablet := range tabletMap { tabletInfo, err := ts.GetTablet(ctx, tablet.agent.TabletAlias) if err != nil { return fmt.Errorf("cannot find tablet: %+v", tablet.agent.TabletAlias) } tmc.RunHealthCheck(ctx, tabletInfo.Tablet) } return nil }
// initTabletMap creates the action agents and associated data structures // for all tablets func initTabletMap(ts topo.Server, topology string, mysqld mysqlctl.MysqlDaemon, dbcfgs dbconfigs.DBConfigs, formal *vindexes.VSchemaFormal, mycnf *mysqlctl.Mycnf) { tabletMap = make(map[uint32]*tablet) ctx := context.Background() // disable publishing of stats from query service flag.Lookup("queryserver-config-enable-publish-stats").Value.Set("false") var uid uint32 = 1 keyspaceMap := make(map[string]bool) for _, entry := range strings.Split(topology, ",") { slash := strings.IndexByte(entry, '/') column := strings.IndexByte(entry, ':') if slash == -1 || column == -1 { log.Fatalf("invalid topology entry: %v", entry) } keyspace := entry[:slash] shard := entry[slash+1 : column] dbname := entry[column+1:] dbcfgs.App.DbName = dbname // create the keyspace if necessary, so we can set the // ShardingColumnName and ShardingColumnType if _, ok := keyspaceMap[keyspace]; !ok { // only set for sharding key info for sharded keyspaces scn := "" sct := topodatapb.KeyspaceIdType_UNSET if shard != "0" { var err error sct, err = key.ParseKeyspaceIDType(*shardingColumnType) if err != nil { log.Fatalf("parseKeyspaceIDType(%v) failed: %v", *shardingColumnType, err) } scn = *shardingColumnName } if err := ts.CreateKeyspace(ctx, keyspace, &topodatapb.Keyspace{ ShardingColumnName: scn, ShardingColumnType: sct, }); err != nil { log.Fatalf("CreateKeyspace(%v) failed: %v", keyspace, err) } keyspaceMap[keyspace] = true kformal := formal.Keyspaces[keyspace] data, err := json.Marshal(kformal) if err != nil { log.Fatalf("Marshal failed: %v", err) } if err := ts.SaveVSchema(ctx, keyspace, string(data)); err != nil { log.Fatalf("SaveVSchema failed: %v", err) } } // create the master alias := &topodatapb.TabletAlias{ Cell: cell, Uid: uid, } log.Infof("Creating master tablet %v for %v/%v", topoproto.TabletAliasString(alias), keyspace, shard) flag.Lookup("debug-url-prefix").Value.Set(fmt.Sprintf("/debug-%d", uid)) masterController := tabletserver.NewServer() masterAgent := tabletmanager.NewComboActionAgent(ctx, ts, alias, int32(8000+uid), int32(9000+uid), masterController, dbcfgs, mysqld, keyspace, shard, dbname, "replica") if err := masterAgent.TabletExternallyReparented(ctx, ""); err != nil { log.Fatalf("TabletExternallyReparented failed on master: %v", err) } tabletMap[uid] = &tablet{ keyspace: keyspace, shard: shard, tabletType: topodatapb.TabletType_MASTER, dbname: dbname, qsc: masterController, agent: masterAgent, } uid++ // create a replica slave alias = &topodatapb.TabletAlias{ Cell: cell, Uid: uid, } log.Infof("Creating replica tablet %v for %v/%v", topoproto.TabletAliasString(alias), keyspace, shard) flag.Lookup("debug-url-prefix").Value.Set(fmt.Sprintf("/debug-%d", uid)) replicaController := tabletserver.NewServer() tabletMap[uid] = &tablet{ keyspace: keyspace, shard: shard, tabletType: topodatapb.TabletType_REPLICA, dbname: dbname, qsc: replicaController, agent: tabletmanager.NewComboActionAgent(ctx, ts, alias, int32(8000+uid), int32(9000+uid), replicaController, dbcfgs, mysqld, keyspace, shard, dbname, "replica"), } uid++ // create a rdonly slave alias = &topodatapb.TabletAlias{ Cell: cell, Uid: uid, } log.Infof("Creating rdonly tablet %v for %v/%v", topoproto.TabletAliasString(alias), keyspace, shard) flag.Lookup("debug-url-prefix").Value.Set(fmt.Sprintf("/debug-%d", uid)) rdonlyController := tabletserver.NewServer() tabletMap[uid] = &tablet{ keyspace: keyspace, shard: shard, tabletType: topodatapb.TabletType_RDONLY, dbname: dbname, qsc: rdonlyController, agent: tabletmanager.NewComboActionAgent(ctx, ts, alias, int32(8000+uid), int32(9000+uid), rdonlyController, dbcfgs, mysqld, keyspace, shard, dbname, "rdonly"), } uid++ } // Rebuild the SrvKeyspace objects, we we can support range-based // sharding queries. wr := wrangler.New(logutil.NewConsoleLogger(), ts, nil) for keyspace := range keyspaceMap { if err := wr.RebuildKeyspaceGraph(ctx, keyspace, nil, true); err != nil { log.Fatalf("cannot rebuild %v: %v", keyspace, err) } } // Register the tablet dialer for tablet server tabletconn.RegisterDialer("internal", dialer) *tabletconn.TabletProtocol = "internal" // Register the tablet manager client factory for tablet manager tmclient.RegisterTabletManagerClientFactory("internal", func() tmclient.TabletManagerClient { return &internalTabletManagerClient{} }) *tmclient.TabletManagerProtocol = "internal" }