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)) }) }
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) }) }
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) }) // 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) }) }
func initAPI(ctx context.Context, ts topo.Server, actions *ActionRepository) { 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) // 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) }) //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 != "" { 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) }) // Schema Change http.HandleFunc(apiPrefix+"schema/apply", func(w http.ResponseWriter, r *http.Request) { 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) }) }