// ExecuteVtctlCommand is the server side method that will execute the query, // and stream the results. func (s *VtctlServer) ExecuteVtctlCommand(context context.Context, query *gorpcproto.ExecuteVtctlCommandArgs, sendReply func(interface{}) error) error { // create a logger, send the result back to the caller logstream := logutil.NewChannelLogger(10) logger := logutil.NewTeeLogger(logstream, logutil.NewConsoleLogger()) // send logs to the caller wg := sync.WaitGroup{} wg.Add(1) go func() { for e := range logstream { // Note we don't interrupt the loop here, as // we still need to flush and finish the // command, even if the channel to the client // has been broken. We'll just keep trying. sendReply(&e) } wg.Done() }() // create the wrangler wr := wrangler.New(logger, s.ts, query.ActionTimeout, query.LockTimeout) // execute the command err := vtctl.RunCommand(wr, query.Args) // close the log channel, and wait for them all to be sent close(logstream) wg.Wait() return err }
// ExecuteVtctlCommand is the server side method that will execute the query, // and stream the results. func (s *VtctlServer) ExecuteVtctlCommand(context context.Context, query *gorpcproto.ExecuteVtctlCommandArgs, sendReply func(interface{}) error) error { // create a logger, send the result back to the caller logger := logutil.NewChannelLogger(10) wg := sync.WaitGroup{} wg.Add(1) go func() { for e := range logger { // Note we don't interrupt the loop here, as // we still need to flush and finish the // command, even if the channel to the client // has been broken. We'll just keep logging the lines. if err := sendReply(&e); err != nil { log.Warningf("Cannot send vtctl log line: %v", e) } } wg.Done() }() // create the wrangler wr := wrangler.New(logger, s.ts, query.ActionTimeout, query.LockTimeout) // execute the command, wait for it to finish if required actionPath, err := vtctl.RunCommand(wr, query.Args) if err == nil && actionPath != "" { err = wr.WaitForCompletion(actionPath) } // close the log channel, and wait for them all to be sent close(logger) wg.Wait() return err }
// ExecuteVtctlCommand is part of the pb.VtctlServer interface func (s *VtctlServer) ExecuteVtctlCommand(args *pb.ExecuteVtctlCommandRequest, stream pbs.Vtctl_ExecuteVtctlCommandServer) (err error) { defer servenv.HandlePanic("vtctl", &err) // create a logger, send the result back to the caller logstream := logutil.NewChannelLogger(10) logger := logutil.NewTeeLogger(logstream, logutil.NewConsoleLogger()) // send logs to the caller wg := sync.WaitGroup{} wg.Add(1) go func() { for e := range logstream { // Note we don't interrupt the loop here, as // we still need to flush and finish the // command, even if the channel to the client // has been broken. We'll just keep trying. stream.Send(&pb.ExecuteVtctlCommandResponse{ Event: logutil.LoggerEventToProto(&e), }) } wg.Done() }() // create the wrangler wr := wrangler.New(logger, s.ts, tmclient.NewTabletManagerClient(), time.Duration(args.LockTimeout)) // execute the command err = vtctl.RunCommand(stream.Context(), wr, args.Args) // close the log channel, and wait for them all to be sent close(logstream) wg.Wait() return err }
func main() { defer exit.RecoverAll() defer logutil.Flush() flag.Parse() args := flag.Args() if len(args) == 0 { flag.Usage() exit.Return(1) } action := args[0] installSignalHandlers() startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " ")) if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil { syslogger.Info(startMsg) } else { log.Warningf("cannot connect to syslog: %v", err) } topoServer := topo.GetServer() defer topo.CloseServers() wr := wrangler.New(logutil.NewConsoleLogger(), topoServer, *waitTime, *lockWaitTimeout) actionPath, err := vtctl.RunCommand(wr, args) switch err { case vtctl.ErrUnknownCommand: flag.Usage() exit.Return(1) case nil: // keep going default: log.Errorf("action failed: %v %v", action, err) exit.Return(255) } if actionPath != "" { if *noWaitForAction { fmt.Println(actionPath) } else { err := wr.WaitForCompletion(actionPath) if err != nil { log.Error(err.Error()) exit.Return(255) } else { log.Infof("action completed: %v", actionPath) } } } }
func main() { defer exit.RecoverAll() defer logutil.Flush() flag.Parse() args := flag.Args() if len(args) == 0 { flag.Usage() exit.Return(1) } action := args[0] startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " ")) if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil { syslogger.Info(startMsg) } else { log.Warningf("cannot connect to syslog: %v", err) } topoServer := topo.GetServer() defer topo.CloseServers() ctx, cancel := context.WithTimeout(context.Background(), *waitTime) wr := wrangler.New(logutil.NewConsoleLogger(), topoServer, tmclient.NewTabletManagerClient(), *lockWaitTimeout) installSignalHandlers(cancel) for _, f := range initFuncs { f() } err := vtctl.RunCommand(ctx, wr, args) cancel() switch err { case vtctl.ErrUnknownCommand: flag.Usage() exit.Return(1) case nil: // keep going default: log.Errorf("action failed: %v %v", action, err) exit.Return(255) } }
// ExecuteVtctlCommand is part of the vtctldatapb.VtctlServer interface func (s *VtctlServer) ExecuteVtctlCommand(args *vtctldatapb.ExecuteVtctlCommandRequest, stream vtctlservicepb.Vtctl_ExecuteVtctlCommandServer) (err error) { defer servenv.HandlePanic("vtctl", &err) // create a logger, send the result back to the caller logstream := logutil.NewCallbackLogger(func(e *logutilpb.Event) { // If the client disconnects, we will just fail // to send the log events, but won't interrupt // the command. stream.Send(&vtctldatapb.ExecuteVtctlCommandResponse{ Event: e, }) }) logger := logutil.NewTeeLogger(logstream, logutil.NewConsoleLogger()) // create the wrangler wr := wrangler.New(logger, s.ts, tmclient.NewTabletManagerClient()) // execute the command return vtctl.RunCommand(stream.Context(), wr, args.Args) }
// ExecuteVtctlCommand is the server side method that will execute the query, // and stream the results. func (s *VtctlServer) ExecuteVtctlCommand(ctx context.Context, query *gorpcproto.ExecuteVtctlCommandArgs, sendReply func(interface{}) error) (err error) { defer vtctl.HandlePanic(&err) // create a logger, send the result back to the caller logstream := logutil.NewChannelLogger(10) logger := logutil.NewTeeLogger(logstream, logutil.NewConsoleLogger()) // send logs to the caller wg := sync.WaitGroup{} wg.Add(1) go func() { for e := range logstream { // Note we don't interrupt the loop here, as // we still need to flush and finish the // command, even if the channel to the client // has been broken. We'll just keep trying. sendReply(&e) } wg.Done() }() // create the wrangler wr := wrangler.New(logger, s.ts, tmclient.NewTabletManagerClient(), query.LockTimeout) // FIXME(alainjobart) use a single context, copy the source info from it ctx, cancel := context.WithTimeout(context.TODO(), query.ActionTimeout) // execute the command err = vtctl.RunCommand(ctx, wr, query.Args) cancel() // close the log channel, and wait for them all to be sent close(logstream) wg.Wait() return err }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository, realtimeStats *realtimeStats) { tabletHealthCache := newTabletHealthCache(ts) tmClient := tmclient.NewTabletManagerClient() // 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) switch r.Method { case "GET": // List all keyspaces. if keyspace == "" { return ts.GetKeyspaces(ctx) } // Get the keyspace record. k, err := ts.GetKeyspace(ctx, keyspace) // Pass the embedded proto directly or jsonpb will panic. return k.Keyspace, err // Perform an action on a keyspace. case "POST": if keyspace == "" { return nil, errors.New("A POST request needs a keyspace in the URL") } if err := r.ParseForm(); err != nil { return nil, err } action := r.FormValue("action") if action == "" { return nil, errors.New("A POST request must specify action") } return actions.ApplyKeyspaceAction(ctx, action, keyspace, r), nil default: return nil, fmt.Errorf("unsupported HTTP method: %v", r.Method) } }) // 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. si, err := ts.GetShard(ctx, keyspace, shard) // Pass the embedded proto directly or jsonpb will panic. return si.Shard, err }) // SrvKeyspace handleCollection("srv_keyspace", func(r *http.Request) (interface{}, error) { keyspacePath := getItemPath(r.URL.Path) parts := strings.SplitN(keyspacePath, "/", 2) // Request was incorrectly formatted. if len(parts) != 2 { return nil, fmt.Errorf("invalid srvkeyspace path: %q expected path: /srv_keyspace/<cell>/<keyspace>", keyspacePath) } cell := parts[0] keyspace := parts[1] if cell == "local" { if *localCell == "" { return nil, fmt.Errorf("local cell requested, but not specified. Please set with -cell flag") } cell = *localCell } // If a keyspace is provided then return the specified srvkeyspace. if keyspace != "" { srvKeyspace, err := ts.GetSrvKeyspace(ctx, cell, keyspace) if err != nil { return nil, fmt.Errorf("Can't get server keyspace: %v", err) } return srvKeyspace, nil } // Else return the srvKeyspace from all keyspaces. srvKeyspaces := make(map[string]interface{}) keyspaceNamesList, err := ts.GetSrvKeyspaceNames(ctx, cell) if err != nil { return nil, fmt.Errorf("can't get list of SrvKeyspaceNames for cell %q: GetSrvKeyspaceNames returned: %v", cell, err) } for _, keyspaceName := range keyspaceNamesList { err := addSrvkeyspace(ctx, ts, cell, keyspaceName, srvKeyspaces) if err != nil { return nil, err } } return srvKeyspaces, nil }) // 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 != "" { result, err := ts.FindAllTabletAliasesInShardByCell(ctx, keyspace, shard, []string{cell}) if err != nil && err != topo.ErrPartialResult { return result, err } return result, nil } result, err := ts.FindAllTabletAliasesInShard(ctx, keyspace, shard) if err != nil && err != topo.ErrPartialResult { return result, err } return result, nil } // 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. t, err := ts.GetTablet(ctx, tabletAlias) // Pass the embedded proto directly or jsonpb will panic. return t.Tablet, err }) // Healthcheck real time status per (cell, keyspace, tablet type, metric). handleCollection("tablet_statuses", func(r *http.Request) (interface{}, error) { targetPath := getItemPath(r.URL.Path) // Get the heatmap data based on query parameters. if targetPath == "" { if err := r.ParseForm(); err != nil { return nil, err } keyspace := r.FormValue("keyspace") cell := r.FormValue("cell") tabletType := r.FormValue("type") _, err := topoproto.ParseTabletType(tabletType) // Excluding the case where parse fails because all tabletTypes was chosen. if err != nil && tabletType != "all" { return nil, fmt.Errorf("invalid tablet type: %v ", err) } metric := r.FormValue("metric") // Setting default values if none was specified in the query params. if keyspace == "" { keyspace = "all" } if cell == "" { cell = "all" } if tabletType == "" { tabletType = "all" } if metric == "" { metric = "health" } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } heatmap, err := realtimeStats.heatmapData(keyspace, cell, tabletType, metric) if err != nil { return nil, fmt.Errorf("couldn't get heatmap data: %v", err) } return heatmap, nil } return nil, fmt.Errorf("invalid target path: %q expected path: ?keyspace=<keyspace>&cell=<cell>&type=<type>&metric=<metric>", targetPath) }) handleCollection("tablet_health", func(r *http.Request) (interface{}, error) { tabletPath := getItemPath(r.URL.Path) parts := strings.SplitN(tabletPath, "/", 2) // Request was incorrectly formatted. if len(parts) != 2 { return nil, fmt.Errorf("invalid tablet_health path: %q expected path: /tablet_health/<cell>/<uid>", tabletPath) } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } cell := parts[0] uidStr := parts[1] uid, err := topoproto.ParseUID(uidStr) if err != nil { return nil, fmt.Errorf("incorrect uid: %v", err) } tabletAlias := topodatapb.TabletAlias{ Cell: cell, Uid: uid, } tabletStat, err := realtimeStats.tabletStats(&tabletAlias) if err != nil { return nil, fmt.Errorf("could not get tabletStats: %v", err) } return tabletStat, nil }) handleCollection("topology_info", func(r *http.Request) (interface{}, error) { targetPath := getItemPath(r.URL.Path) // Retrieving topology information (keyspaces, cells, and types) based on query params. if targetPath == "" { if err := r.ParseForm(); err != nil { return nil, err } keyspace := r.FormValue("keyspace") cell := r.FormValue("cell") // Setting default values if none was specified in the query params. if keyspace == "" { keyspace = "all" } if cell == "" { cell = "all" } if realtimeStats == nil { return nil, fmt.Errorf("realtimeStats not initialized") } return realtimeStats.topologyInfo(keyspace, cell), nil } return nil, fmt.Errorf("invalid target path: %q expected path: ?keyspace=<keyspace>&cell=<cell>", targetPath) }) // Vtctl Command http.HandleFunc(apiPrefix+"vtctl/", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { httpErrorf(w, r, "Access denied") return } var args []string resp := struct { Error string Output string }{} if err := unmarshalRequest(r, &args); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } logstream := logutil.NewMemoryLogger() wr := wrangler.New(logstream, ts, tmClient) // TODO(enisoc): Context for run command should be request-scoped. err := vtctl.RunCommand(ctx, wr, args) if err != nil { resp.Error = err.Error() } resp.Output = logstream.String() data, err := json.MarshalIndent(resp, "", " ") if err != nil { httpErrorf(w, r, "json error: %v", err) return } w.Header().Set("Content-Type", jsonContentType) w.Write(data) }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { httpErrorf(w, r, "Access denied") return } req := struct { Keyspace, SQL string SlaveTimeoutSeconds int }{} if err := unmarshalRequest(r, &req); err != nil { httpErrorf(w, r, "can't unmarshal request: %v", err) return } if req.SlaveTimeoutSeconds <= 0 { req.SlaveTimeoutSeconds = 10 } logger := logutil.NewCallbackLogger(func(ev *logutilpb.Event) { w.Write([]byte(logutil.EventString(ev))) }) wr := wrangler.New(logger, ts, tmClient) executor := schemamanager.NewTabletExecutor( wr, time.Duration(req.SlaveTimeoutSeconds)*time.Second) schemamanager.Run(ctx, schemamanager.NewUIController(req.SQL, req.Keyspace, w), executor) }) }