func (cp *CachePool) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.MONITORING); err != nil { acl.SendError(response, err) return } defer func() { if x := recover(); x != nil { response.Write(([]byte)(x.(error).Error())) } }() response.Header().Set("Content-Type", "text/plain") pool := cp.getPool() if pool == nil { response.Write(([]byte)("closed")) return } command := request.URL.Path[len(statsURL):] if command == "stats" { command = "" } conn := cp.Get() // This is not the same as defer rc.cachePool.Put(conn) defer func() { cp.Put(conn) }() r, err := conn.Stats(command) if err != nil { conn.Close() conn = nil response.Write(([]byte)(err.Error())) } else { response.Write(r) } }
func schemazHandler(tables []*schema.Table, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(schemazHeader) sorter := schemazSorter{ rows: tables, less: func(row1, row2 *schema.Table) bool { return row1.Name > row2.Name }, } sort.Sort(&sorter) envelope := struct { Type []string Table *schema.Table }{ Type: schema.TypeNames, } for _, Value := range sorter.rows { envelope.Table = Value if err := schemazTmpl.Execute(w, envelope); err != nil { log.Errorf("schemaz: couldn't execute template: %v", err) } } }
// ApplyTabletAction applies the provided action to the tablet. func (ar *ActionRepository) ApplyTabletAction(ctx context.Context, actionName string, tabletAlias *topodatapb.TabletAlias, r *http.Request) *ActionResult { result := &ActionResult{ Name: actionName, Parameters: topoproto.TabletAliasString(tabletAlias), } action, ok := ar.tabletActions[actionName] if !ok { result.error("Unknown tablet action") return result } // check the role if action.role != "" { if err := acl.CheckAccessHTTP(r, action.role); err != nil { result.error("Access denied") return result } } // run the action ctx, cancel := context.WithTimeout(ctx, *actionTimeout) wr := wrangler.New(logutil.NewConsoleLogger(), ar.ts, tmclient.NewTabletManagerClient()) output, err := action.method(ctx, wr, tabletAlias, r) cancel() if err != nil { result.error(err.Error()) return result } result.Output = output return result }
// ServeLogs registers the URL on which messages will be broadcast. // It is safe to register multiple URLs for the same StreamLogger. func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) { http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } ch := logger.Subscribe("ServeLogs") defer logger.Unsubscribe(ch) // Notify client that we're set up. Helpful to distinguish low-traffic streams from connection issues. w.WriteHeader(http.StatusOK) w.(http.Flusher).Flush() for message := range ch { if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil { return } w.(http.Flusher).Flush() } }) log.Infof("Streaming logs from %s at %v.", logger.Name(), url) }
func schemazHandler(tables []*schema.Table, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(schemazHeader) sorter := schemazSorter{ rows: tables, less: func(row1, row2 *schema.Table) bool { return row1.Name > row2.Name }, } sort.Sort(&sorter) envelope := struct { ColumnCategory []string CacheType []string Table *schema.Table }{ ColumnCategory: []string{"other", "number", "varbinary"}, CacheType: []string{"none", "read-write", "write-only"}, } for _, Value := range sorter.rows { envelope.Table = Value if err := schemazTmpl.Execute(w, envelope); err != nil { log.Errorf("schemaz: couldn't execute template: %v", err) } } }
// ServeHTTP shows the current plans in the query cache. func (plr *Planner) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if request.URL.Path == "/debug/query_plans" { keys := plr.plans.Keys() response.Header().Set("Content-Type", "text/plain") response.Write([]byte(fmt.Sprintf("Length: %d\n", len(keys)))) for _, v := range keys { response.Write([]byte(fmt.Sprintf("%#v\n", v))) if plan, ok := plr.plans.Peek(v); ok { if b, err := json.MarshalIndent(plan, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } response.Write(([]byte)("\n\n")) } } } else if request.URL.Path == "/debug/vschema" { response.Header().Set("Content-Type", "application/json; charset=utf-8") b, err := json.MarshalIndent(plr.VSchema().Keyspaces, "", " ") if err != nil { response.Write([]byte(err.Error())) return } buf := bytes.NewBuffer(nil) json.HTMLEscape(buf, b) response.Write(buf.Bytes()) } else { response.WriteHeader(http.StatusNotFound) } }
func streamqueryzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } rows := SqlQueryRpcService.qe.streamQList.GetQueryzRows() if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("cannot parse form: %s", err), http.StatusInternalServerError) return } format := r.FormValue("format") if format == "json" { js, err := json.Marshal(rows) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(js) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(streamqueryzHeader) for i := range rows { streamqueryzTmpl.Execute(w, rows[i]) } }
func (ar *ActionRepository) ApplyTabletAction(actionName string, tabletAlias topo.TabletAlias, r *http.Request) *ActionResult { result := &ActionResult{Name: actionName, Parameters: tabletAlias.String()} action, ok := ar.tabletActions[actionName] if !ok { result.error("Unknown tablet action") return result } // check the role if action.role != "" { if err := acl.CheckAccessHTTP(r, action.role); err != nil { result.error("Access denied") return result } } // run the action ar.wr.ResetActionTimeout(wrangler.DefaultActionTimeout) output, err := action.method(ar.wr, tabletAlias, r) if err != nil { result.error(err.Error()) return result } result.Output = output return result }
func healthCheck(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil { acl.SendError(w, err) return } w.Header().Set("Content-Type", "text/plain") if err := IsHealthy(); err != nil { w.Write([]byte("notok")) } w.Write([]byte("ok")) }
func (tsv *TabletServer) registerDebugHealthHandler() { http.HandleFunc("/debug/health", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil { acl.SendError(w, err) return } w.Header().Set("Content-Type", "text/plain") if err := tsv.IsHealthy(); err != nil { w.Write([]byte("not ok")) return } w.Write([]byte("ok")) }) }
// querylogzHandler serves a human readable snapshot of the // current query log. func querylogzHandler(ch chan interface{}, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } timeout, limit := parseTimeoutLimitParams(r) logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(querylogzHeader) tmr := time.NewTimer(timeout) defer tmr.Stop() for i := 0; i < limit; i++ { select { case out := <-ch: select { case <-tmr.C: return default: } stats, ok := out.(*LogStats) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &LogStats{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } var level string if stats.TotalTime().Seconds() < 0.01 { level = "low" } else if stats.TotalTime().Seconds() < 0.1 { level = "medium" } else { level = "high" } tmplData := struct { *LogStats ColorLevel string }{stats, level} if err := querylogzTmpl.Execute(w, tmplData); err != nil { log.Errorf("querylogz: couldn't execute template: %v", err) } case <-tmr.C: return } } }
// txlogzHandler serves a human readable snapshot of the // current transaction log. // Endpoint: /txlogz?timeout=%d&limit=%d // timeout: the txlogz will keep dumping transactions until timeout // limit: txlogz will keep dumping transcations until it hits the limit func txlogzHandler(w http.ResponseWriter, req *http.Request) { if err := acl.CheckAccessHTTP(req, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } timeout, limit := parseTimeoutLimitParams(req) ch := TxLogger.Subscribe("txlogz") defer TxLogger.Unsubscribe(ch) logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(txlogzHeader) tmr := time.NewTimer(timeout) defer tmr.Stop() for i := 0; i < limit; i++ { select { case out := <-ch: txc, ok := out.(*TxConnection) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &TxConnection{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } duration := txc.EndTime.Sub(txc.StartTime).Seconds() var level string if duration < 0.1 { level = "low" } else if duration < 1.0 { level = "medium" } else { level = "high" } tmplData := struct { *TxConnection Duration float64 ColorLevel string }{txc, duration, level} if err := txlogzTmpl.Execute(w, tmplData); err != nil { log.Errorf("txlogz: couldn't execute template: %v", err) } case <-tmr.C: return } } }
func queryzHandler(si *SchemaInfo, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(queryzHeader) keys := si.queries.Keys() sorter := queryzSorter{ rows: make([]*queryzRow, 0, len(keys)), less: func(row1, row2 *queryzRow) bool { return row1.timePQ() > row2.timePQ() }, } for _, v := range si.queries.Keys() { plan := si.peekQuery(v) if plan == nil { continue } Value := &queryzRow{ Query: wrappable(v), Table: plan.TableName, Plan: plan.PlanID, Reason: plan.Reason, } Value.Count, Value.tm, Value.Rows, Value.Errors = plan.Stats() var timepq time.Duration if Value.Count != 0 { timepq = time.Duration(int64(Value.tm) / Value.Count) } if timepq < 10*time.Millisecond { Value.Color = "low" } else if timepq < 100*time.Millisecond { Value.Color = "medium" } else { Value.Color = "high" } sorter.rows = append(sorter.rows, Value) } sort.Sort(&sorter) for _, Value := range sorter.rows { if err := queryzTmpl.Execute(w, Value); err != nil { log.Errorf("queryz: couldn't execute template: %v", err) } } }
func (co *Consolidator) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } items := co.consolidations.Items() response.Header().Set("Content-Type", "text/plain") if items == nil { response.Write([]byte("empty\n")) return } response.Write([]byte(fmt.Sprintf("Length: %d\n", len(items)))) for _, v := range items { response.Write([]byte(fmt.Sprintf("%v: %s\n", v.Value.(*ccount).Get(), v.Key))) } }
func (ar ActionRepository) PopulateTabletActions(actions map[string]template.URL, tabletAlias string, r *http.Request) { for name, value := range ar.tabletActions { // check we are authorized for the role we need if value.role != "" { if err := acl.CheckAccessHTTP(r, value.role); err != nil { continue } } // and populate the entry values := url.Values{} values.Set("action", name) values.Set("alias", tabletAlias) actions[name] = template.URL("/tablet_actions?" + values.Encode()) } }
func (si *SchemaInfo) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if ep, ok := si.endpoints[debugQueryPlansKey]; ok && request.URL.Path == ep { si.handleHTTPQueryPlans(response, request) } else if ep, ok := si.endpoints[debugQueryStatsKey]; ok && request.URL.Path == ep { si.handleHTTPQueryStats(response, request) } else if ep, ok := si.endpoints[debugTableStatsKey]; ok && request.URL.Path == ep { si.handleHTTPTableStats(response, request) } else if ep, ok := si.endpoints[debugSchemaKey]; ok && request.URL.Path == ep { si.handleHTTPSchema(response, request) } else { response.WriteHeader(http.StatusNotFound) } }
func handleAPI(pattern string, handlerFunc func(w http.ResponseWriter, r *http.Request) error) { http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { defer func() { if x := recover(); x != nil { httpErrorf(w, r, "uncaught panic: %v", x) } }() if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { httpErrorf(w, r, "WorkflowManager acl.CheckAccessHTTP failed: %v", err) return } if err := handlerFunc(w, r); err != nil { httpErrorf(w, r, "%v", err) } }) }
// txlogzHandler serves a human readable snapshot of the // current transaction log. func txlogzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } ch := TxLogger.Subscribe() defer TxLogger.Unsubscribe(ch) startHTMLTable(w) defer endHTMLTable(w) w.Write(txlogzHeader) deadline := time.After(10 * time.Second) for i := 0; i < 300; i++ { select { case out := <-ch: txc, ok := out.(*TxConnection) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &TxConnection{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } duration := txc.EndTime.Sub(txc.StartTime).Seconds() var level string if duration < 0.1 { level = "low" } else if duration < 1.0 { level = "medium" } else { level = "high" } tmplData := struct { *TxConnection Duration float64 ColorLevel string }{txc, duration, level} txlogzTmpl.Execute(w, tmplData) case <-deadline: return } } }
// queryzHandler displays the query stats. func queryzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(queryzHeader) si := SqlQueryRpcService.qe.schemaInfo keys := si.queries.Keys() sorter := queryzSorter{ rows: make([]*queryzRow, 0, len(keys)), less: func(row1, row2 *queryzRow) bool { return row1.timePQ() > row2.timePQ() }, } for _, v := range si.queries.Keys() { plan := si.getQuery(v) if plan == nil { continue } Value := &queryzRow{ Query: wrappable(v), Table: plan.TableName, Plan: plan.PlanId, } Value.Count, Value.tm, Value.Rows, Value.Errors = plan.Stats() timepq := time.Duration(int64(Value.tm) / Value.Count) if timepq < 10*time.Millisecond { Value.Color = "low" } else if timepq < 100*time.Millisecond { Value.Color = "medium" } else { Value.Color = "high" } sorter.rows = append(sorter.rows, Value) } sort.Sort(&sorter) for _, Value := range sorter.rows { queryzTmpl.Execute(w, Value) } }
// querylogzHandler serves a human readable snapshot of the // current query log. func querylogzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } ch := SqlQueryLogger.Subscribe("querylogz") defer SqlQueryLogger.Unsubscribe(ch) startHTMLTable(w) defer endHTMLTable(w) w.Write(querylogzHeader) tmr := time.NewTimer(10 * time.Second) defer tmr.Stop() for i := 0; i < 300; i++ { select { case out := <-ch: stats, ok := out.(*SQLQueryStats) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &SQLQueryStats{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } var level string if stats.TotalTime().Seconds() < 0.01 { level = "low" } else if stats.TotalTime().Seconds() < 0.1 { level = "medium" } else { level = "high" } tmplData := struct { *SQLQueryStats ColorLevel string }{stats, level} querylogzTmpl.Execute(w, tmplData) case <-tmr.C: return } } }
func streamqueryzTerminateHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("cannot parse form: %s", err), http.StatusInternalServerError) return } connID := r.FormValue("connID") c, err := strconv.Atoi(connID) if err != nil { http.Error(w, "invalid connID", http.StatusInternalServerError) return } if err = SqlQueryRpcService.qe.streamQList.Terminate(int64(c)); err != nil { http.Error(w, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) return } streamqueryzHandler(w, r) }
// ServeLogs registers the URL on which messages will be broadcast. // It is safe to register multiple URLs for the same StreamLogger. func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) { http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } ch := logger.Subscribe() defer logger.Unsubscribe(ch) for message := range ch { if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil { return } w.(http.Flusher).Flush() } }) log.Infof("Streaming logs from %s at %v.", logger.Name(), url) }
func statusHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } statusMu.Lock() defer statusMu.Unlock() data := struct { Sections []section BinaryName string Hostname string StartTime string }{ Sections: statusSections, BinaryName: binaryName, Hostname: hostname, StartTime: serverStart.Format(time.RFC1123), } if err := statusTmpl.ExecuteTemplate(w, "status", data); err != nil { log.Errorf("servenv: couldn't execute template: %v", err) } }
// HandleHTTPWebSocket registers the WebSocket handler. func (m *Manager) HandleHTTPWebSocket(pattern string) { log.Infof("workflow Manager listening to websocket traffic at %v", pattern) http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { defer func() { if x := recover(); x != nil { errMsg := fmt.Sprintf("uncaught panic: %v", x) log.Error(errMsg) http.Error(w, errMsg, http.StatusInternalServerError) } }() // Check ACL. if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { msg := fmt.Sprintf("WorkflowManager acl.CheckAccessHTTP failed: %v", err) log.Error(msg) http.Error(w, msg, http.StatusUnauthorized) return } // Upgrade to WebSocket. c, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Errorf("upgrade error: %v", err) return } defer c.Close() // Register the handler. notifications := make(chan []byte, 10) tree, i, err := m.NodeManager().GetAndWatchFullTree(notifications) if err != nil { log.Warningf("GetAndWatchFullTree failed: %v", err) return } defer m.NodeManager().CloseWatcher(i) // First we send the full dump if err := c.WriteMessage(websocket.TextMessage, tree); err != nil { log.Warningf("WriteMessage(tree) failed: %v", err) return } // Start a go routine to get messages, send them to a channel. recv := make(chan *ActionParameters, 10) go func() { for { mt, message, err := c.ReadMessage() if err != nil { log.Warningf("failed to read message from websocket: %v", err) close(recv) return } if mt != websocket.TextMessage { log.Warningf("weird message type: %v", mt) } ap := &ActionParameters{} if err := json.Unmarshal(message, ap); err != nil { log.Warningf("failed to JSON-decode message from websocket: %v", err) close(recv) return } recv <- ap } }() // Let's listen to the channels until we're done. for { select { case ap, ok := <-recv: if !ok { // The websocket was most likely closed. return } ctx := context.TODO() if err := m.NodeManager().Action(ctx, ap); err != nil { log.Warningf("Action failed: %v", err) } case message, ok := <-notifications: if !ok { // We ran out of space on the update // channel, so we had to close it. return } if err := c.WriteMessage(websocket.TextMessage, message); err != nil { log.Warningf("WriteMessage(tree) failed: %v", err) return } } } }) }
// InitInteractiveMode installs webserver handlers for each known command. func (wi *Instance) InitInteractiveMode() { indexTemplate := mustParseTemplate("index", indexHTML) subIndexTemplate := mustParseTemplate("subIndex", subIndexHTML) // toplevel menu http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } executeTemplate(w, indexTemplate, commands) }) // command group menus for _, cg := range commands { // keep a local copy of the Command pointer for the // closure. pcg := cg http.HandleFunc("/"+cg.Name, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } executeTemplate(w, subIndexTemplate, pcg) }) for _, c := range cg.Commands { // keep a local copy of the Command pointer for the closure. pc := c http.HandleFunc("/"+cg.Name+"/"+c.Name, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wrk, template, data, err := pc.Interactive(wi.backgroundContext, wi, wi.wr, w, r) if err != nil { httpError(w, "%s", err) } else if template != nil && data != nil { executeTemplate(w, template, data) return } if wrk == nil { httpError(w, "Internal server error. Command: %s did not return correct response.", c.Name) return } if _, err := wi.setAndStartWorker(wrk, wi.wr); err != nil { httpError(w, "Could not set %s worker: %s", c.Name, err) return } http.Redirect(w, r, servenv.StatusURLPath(), http.StatusTemporaryRedirect) }) } } log.Infof("Interactive mode ready") }
// InitStatusHandling installs webserver handlers for global actions like /status, /reset and /cancel. func (wi *Instance) InitStatusHandling() { // code to serve /status workerTemplate := mustParseTemplate("worker", workerStatusHTML) http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wi.currentWorkerMutex.Lock() wrk := wi.currentWorker logger := wi.currentMemoryLogger ctx := wi.currentContext err := wi.lastRunError wi.currentWorkerMutex.Unlock() data := make(map[string]interface{}) if wrk != nil { status := template.HTML("Current worker:<br>\n") + wrk.StatusAsHTML() if ctx == nil { data["Done"] = true if err != nil { status += template.HTML(fmt.Sprintf("<br>\nEnded with an error: %v<br>\n", err)) } } data["Status"] = status if logger != nil { data["Logs"] = template.HTML(strings.Replace(logger.String(), "\n", "</br>\n", -1)) } else { data["Logs"] = template.HTML("See console for logs</br>\n") } } executeTemplate(w, workerTemplate, data) }) // add the section in status that does auto-refresh of status div servenv.AddStatusPart("Worker Status", workerStatusPartHTML, func() interface{} { return nil }) // reset handler http.HandleFunc("/reset", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } if err := wi.Reset(); err != nil { httpError(w, err.Error(), nil) } else { // No worker currently running, we go to the menu. http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } }) // cancel handler http.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wi.currentWorkerMutex.Lock() // no worker, or not running, we go to the menu if wi.currentWorker == nil || wi.currentCancelFunc == nil { wi.currentWorkerMutex.Unlock() http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } // otherwise, cancel the running worker and go back to the status page cancel := wi.currentCancelFunc wi.currentWorkerMutex.Unlock() cancel() http.Redirect(w, r, servenv.StatusURLPath(), http.StatusTemporaryRedirect) }) }
func (si *SchemaInfo) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if request.URL.Path == "/debug/query_plans" { keys := si.queries.Keys() response.Header().Set("Content-Type", "text/plain") response.Write([]byte(fmt.Sprintf("Length: %d\n", len(keys)))) for _, v := range keys { response.Write([]byte(fmt.Sprintf("%#v\n", v))) if plan := si.getQuery(v); plan != nil { if b, err := json.MarshalIndent(plan.ExecPlan, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } response.Write(([]byte)("\n\n")) } } } else if request.URL.Path == "/debug/query_stats" { keys := si.queries.Keys() response.Header().Set("Content-Type", "application/json; charset=utf-8") qstats := make([]perQueryStats, 0, len(keys)) for _, v := range keys { if plan := si.getQuery(v); plan != nil { var pqstats perQueryStats pqstats.Query = unicoded(v) pqstats.Table = plan.TableName pqstats.Plan = plan.PlanId pqstats.QueryCount, pqstats.Time, pqstats.RowCount, pqstats.ErrorCount = plan.Stats() qstats = append(qstats, pqstats) } } if b, err := json.MarshalIndent(qstats, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } } else if request.URL.Path == "/debug/table_stats" { response.Header().Set("Content-Type", "application/json; charset=utf-8") tstats := make(map[string]struct{ hits, absent, misses, invalidations int64 }) var temp, totals struct{ hits, absent, misses, invalidations int64 } func() { si.mu.Lock() defer si.mu.Unlock() for k, v := range si.tables { if v.CacheType != schema.CACHE_NONE { temp.hits, temp.absent, temp.misses, temp.invalidations = v.Stats() tstats[k] = temp totals.hits += temp.hits totals.absent += temp.absent totals.misses += temp.misses totals.invalidations += temp.invalidations } } }() response.Write([]byte("{\n")) for k, v := range tstats { fmt.Fprintf(response, "\"%s\": {\"Hits\": %v, \"Absent\": %v, \"Misses\": %v, \"Invalidations\": %v},\n", k, v.hits, v.absent, v.misses, v.invalidations) } fmt.Fprintf(response, "\"Totals\": {\"Hits\": %v, \"Absent\": %v, \"Misses\": %v, \"Invalidations\": %v}\n", totals.hits, totals.absent, totals.misses, totals.invalidations) response.Write([]byte("}\n")) } else if request.URL.Path == "/debug/schema" { response.Header().Set("Content-Type", "application/json; charset=utf-8") tables := si.GetSchema() b, err := json.MarshalIndent(tables, "", " ") if err != nil { response.Write([]byte(err.Error())) return } buf := bytes.NewBuffer(nil) json.HTMLEscape(buf, b) response.Write(buf.Bytes()) } else { response.WriteHeader(http.StatusNotFound) } }
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) }) }