// polyIgnoresAddHandler is for adding a new ignore rule. func polyIgnoresAddHandler(w http.ResponseWriter, r *http.Request) { user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to add an ignore rule.") return } req := &IgnoresRequest{} if err := parseJson(r, req); err != nil { util.ReportError(w, r, err, "Failed to parse submitted data.") return } if req.Filter == "" { util.ReportError(w, r, fmt.Errorf("Invalid Filter: %q", req.Filter), "Filters can't be empty.") return } d, err := human.ParseDuration(req.Duration) if err != nil { util.ReportError(w, r, err, "Failed to parse duration") return } ignoreRule := ignore.NewIgnoreRule(user, time.Now().Add(d), req.Filter, req.Note) if err != nil { util.ReportError(w, r, err, "Failed to create ignore rule.") return } if err = storages.IgnoreStore.Create(ignoreRule); err != nil { util.ReportError(w, r, err, "Failed to create ignore rule.") return } polyIgnoresJSONHandler(w, r) }
// triageUndoHandler performs an "undo" for a given change id. // The change id's are returned in the result of polyTriageLogHandler. // It accepts one query parameter 'id' which is the id if the change // that should be reversed. // If successful it retunrs the same result as a call to polyTriageLogHandler // to reflect the changed triagelog. func triageUndoHandler(w http.ResponseWriter, r *http.Request) { // Get the user and make sure they are logged in. user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to change expectations.") return } // Extract the id to undo. changeID, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil { util.ReportError(w, r, err, "Invalid change id.") return } // Do the undo procedure. _, err = storages.ExpectationsStore.UndoChange(changeID, user) if err != nil { util.ReportError(w, r, err, "Unable to undo.") return } // Send the same response as a query for the first page. polyTriageLogHandler(w, r) }
func AddTaskHandler(w http.ResponseWriter, r *http.Request, task AddTaskVars) { if !ctfeutil.UserHasEditRights(r) { skutil.ReportError(w, r, fmt.Errorf("Must have google or chromium account to add tasks"), "") return } if task.IsAdminTask() && !ctfeutil.UserHasAdminRights(r) { skutil.ReportError(w, r, fmt.Errorf("Must be admin to add admin tasks; contact rmistry@"), "") return } w.Header().Set("Content-Type", "application/json") if err := json.NewDecoder(r.Body).Decode(&task); err != nil { skutil.ReportError(w, r, err, fmt.Sprintf("Failed to add %T task", task)) return } defer skutil.Close(r.Body) task.GetAddTaskCommonVars().Username = login.LoggedInAs(r) task.GetAddTaskCommonVars().TsAdded = ctutil.GetCurrentTs() if len(task.GetAddTaskCommonVars().Username) > 255 { skutil.ReportError(w, r, fmt.Errorf("Username is too long, limit 255 bytes"), "") return } if _, err := AddTask(task); err != nil { skutil.ReportError(w, r, err, fmt.Sprintf("Failed to insert %T task", task)) return } }
func RedoTaskHandler(prototype Task, w http.ResponseWriter, r *http.Request) { if !ctfeutil.UserHasEditRights(r) { skutil.ReportError(w, r, fmt.Errorf("Must have google or chromium account to redo tasks"), "") return } w.Header().Set("Content-Type", "application/json") vars := struct{ Id int64 }{} if err := json.NewDecoder(r.Body).Decode(&vars); err != nil { skutil.ReportError(w, r, err, "Failed to parse redo request") return } defer skutil.Close(r.Body) rowQuery := fmt.Sprintf("SELECT * FROM %s WHERE id = ? AND ts_completed IS NOT NULL", prototype.TableName()) binds := []interface{}{vars.Id} data, err := prototype.Select(rowQuery, binds...) if err != nil { skutil.ReportError(w, r, err, "Unable to find requested task.") return } tasks := AsTaskSlice(data) if len(tasks) != 1 { skutil.ReportError(w, r, err, "Unable to find requested task.") return } addTaskVars := tasks[0].GetPopulatedAddTaskVars() // Replace the username with the new requester. addTaskVars.GetAddTaskCommonVars().Username = login.LoggedInAs(r) if _, err := AddTask(addTaskVars); err != nil { skutil.ReportError(w, r, err, "Could not redo the task.") return } }
// makeBugChomperPage builds and serves the BugChomper page. func makeBugChomperPage(w http.ResponseWriter, r *http.Request) { // Redirect for login if needed. user := login.LoggedInAs(r) if user == "" { http.Redirect(w, r, login.LoginURL(w, r), http.StatusFound) return } glog.Infof("Logged in as %s", user) issueTracker := issue_tracker.New(login.GetHttpClient(r)) w.Header().Set("Content-Type", "text/html") glog.Info("Loading bugs for " + user) bugList, err := issueTracker.GetBugs(PROJECT_NAME, user) if err != nil { reportError(w, err.Error(), http.StatusInternalServerError) return } bugsById := make(map[string]*issue_tracker.Issue) bugsByPriority := make(map[string][]*issue_tracker.Issue) for _, bug := range bugList.Items { bugsById[strconv.Itoa(bug.Id)] = bug var bugPriority string for _, label := range bug.Labels { if strings.HasPrefix(label, PRIORITY_PREFIX) { bugPriority = label[len(PRIORITY_PREFIX):] } } if _, ok := bugsByPriority[bugPriority]; !ok { bugsByPriority[bugPriority] = make( []*issue_tracker.Issue, 0) } bugsByPriority[bugPriority] = append( bugsByPriority[bugPriority], bug) } bugsJson, err := json.Marshal(bugsById) if err != nil { reportError(w, err.Error(), http.StatusInternalServerError) return } data := struct { Title string User string BugsJson template.JS BugsByPriority *map[string][]*issue_tracker.Issue Priorities []string PriorityPrefix string }{ Title: "BugChomper", User: user, BugsJson: template.JS(string(bugsJson)), BugsByPriority: &bugsByPriority, Priorities: issue_tracker.BugPriorities, PriorityPrefix: PRIORITY_PREFIX, } if err := templates.ExecuteTemplate(w, "bug_chomper.html", data); err != nil { reportError(w, err.Error(), http.StatusInternalServerError) return } }
func addBuildCommentHandler(w http.ResponseWriter, r *http.Request) { defer timer.New("addBuildCommentHandler").Stop() if !userHasEditRights(r) { util.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "User does not have edit rights.") return } w.Header().Set("Content-Type", "application/json") cache, err := getCommitCache(w, r) if err != nil { return } buildId, err := strconv.ParseInt(mux.Vars(r)["buildId"], 10, 32) if err != nil { util.ReportError(w, r, err, fmt.Sprintf("Invalid build id: %v", err)) return } comment := struct { Comment string `json:"comment"` }{} if err := json.NewDecoder(r.Body).Decode(&comment); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add comment: %v", err)) return } defer util.Close(r.Body) c := buildbot.BuildComment{ BuildId: int(buildId), User: login.LoggedInAs(r), Timestamp: float64(time.Now().UTC().Unix()), Message: comment.Comment, } if err := cache.AddBuildComment(int(buildId), &c); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add comment: %v", err)) return } }
// changeHandler handles actions on individual services. // // The actions are forwarded off to the pulld service // running on the machine hosting that service. func changeHandler(w http.ResponseWriter, r *http.Request) { if login.LoggedInAs(r) == "" { util.ReportError(w, r, fmt.Errorf("You must be logged on to push."), "") return } if err := r.ParseForm(); err != nil { util.ReportError(w, r, err, "Failed to parse form.") return } action := r.Form.Get("action") name := r.Form.Get("name") machine := ip.Resolve(r.Form.Get("machine")) url := fmt.Sprintf("http://%s:10114/_/change?name=%s&action=%s", machine, name, action) resp, err := client.Post(url, "", nil) if err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to reach %s: %v %s", machine, resp, err)) return } defer util.Close(resp.Body) if resp.StatusCode != 200 { util.ReportError(w, r, err, fmt.Sprintf("Failed to reach %s: %v %s", machine, resp, err)) return } w.Header().Set("Content-Type", "application/json") if _, err := io.Copy(w, resp.Body); err != nil { glog.Errorf("Failed to copy JSON error out: %s", err) } }
func addCommitCommentHandler(w http.ResponseWriter, r *http.Request) { defer timer.New("addCommitCommentHandler").Stop() if !userHasEditRights(r) { util.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "User does not have edit rights.") return } w.Header().Set("Content-Type", "application/json") commit := mux.Vars(r)["commit"] comment := struct { Comment string `json:"comment"` }{} if err := json.NewDecoder(r.Body).Decode(&comment); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add comment: %v", err)) return } defer util.Close(r.Body) c := buildbot.CommitComment{ Commit: commit, User: login.LoggedInAs(r), Timestamp: time.Now().UTC(), Message: comment.Comment, } if err := db.PutCommitComment(&c); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add commit comment: %v", err)) return } }
// redirectHandler handles redirecting to the correct tradefed page. func redirectHandler(w http.ResponseWriter, r *http.Request) { if login.LoggedInAs(r) == "" { r.Header.Set("Referer", r.URL.String()) http.Redirect(w, r, login.LoginURL(w, r), 302) return } vars := mux.Vars(r) codename := vars["codename"] buildNumberStr := vars["buildNumber"] target, err := codenameDB.Get([]byte(codename), nil) if err != nil { util.ReportError(w, r, err, "Not a valid target codename.") return } buildNumber, err := strconv.Atoi(buildNumberStr) if err != nil { util.ReportError(w, r, err, "Not a valid build number.") return } build, err := buildbot.GetBuildFromDB(string(codename), FAKE_MASTER, buildNumber) if err != nil { util.ReportError(w, r, err, "Could not find a matching build.") } id, ok := build.GetProperty("androidinternal_buildid").([]interface{})[1].(string) if !ok { util.ReportError(w, r, fmt.Errorf("Got %#v", id), "Could not find a matching build id.") return } w.Header().Set("Content-Type", "text/html") if _, err := w.Write([]byte(fmt.Sprintf(REDIRECT_TEMPLATE, codename, target, id, id, target, id, id, target))); err != nil { glog.Errorf("Failed to write response: %s", err) } }
func handleAlert(alertId int64, comment string, until int, w http.ResponseWriter, r *http.Request) { email := login.LoggedInAs(r) if !userHasEditRights(email) { util.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "You must be logged in to an account with edit rights to do that.") return } action, ok := mux.Vars(r)["action"] if !ok { util.ReportError(w, r, fmt.Errorf("No action provided."), "No action provided.") return } if action == "dismiss" { glog.Infof("%s %d", action, alertId) if err := alertManager.Dismiss(alertId, email, comment); err != nil { util.ReportError(w, r, err, "Failed to dismiss alert.") return } return } else if action == "snooze" { if until == 0 { util.ReportError(w, r, fmt.Errorf("Invalid snooze time."), fmt.Sprintf("Invalid snooze time")) return } until := time.Unix(int64(until), 0) glog.Infof("%s %d until %v", action, alertId, until.String()) if err := alertManager.Snooze(alertId, until, email, comment); err != nil { util.ReportError(w, r, err, "Failed to snooze alert.") return } return } else if action == "unsnooze" { glog.Infof("%s %d", action, alertId) if err := alertManager.Unsnooze(alertId, email, comment); err != nil { util.ReportError(w, r, err, "Failed to unsnooze alert.") return } return } else if action == "addcomment" { if !StringIsInteresting(comment) { util.ReportError(w, r, fmt.Errorf("Invalid comment text."), comment) return } glog.Infof("%s %d: %s", action, alertId, comment) if err := alertManager.AddComment(alertId, email, comment); err != nil { util.ReportError(w, r, err, "Failed to add comment.") return } return } else { util.ReportError(w, r, fmt.Errorf("Invalid action %s", action), "The requested action is invalid.") return } }
func UserHasAdminRights(r *http.Request) bool { // TODO(benjaminwagner): Add this list to GCE project level metadata and retrieve from there. admins := map[string]bool{ "*****@*****.**": true, "*****@*****.**": true, "*****@*****.**": true, "*****@*****.**": true, "*****@*****.**": true, } return UserHasEditRights(r) && admins[login.LoggedInAs(r)] }
// polyTriageHandler handles a request to change the triage status of one or more // digests of one test. // // It accepts a POST'd JSON serialization of PolyTriageRequest and updates // the expectations. func polyTriageHandler(w http.ResponseWriter, r *http.Request) { req := &PolyTriageRequest{} if err := parseJson(r, req); err != nil { util.ReportError(w, r, err, "Failed to parse JSON request.") return } glog.Infof("Triage request: %#v", req) user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to triage.") return } // Build the expecations change request from the list of digests passed in. digests := req.Digest // Or build the expectations change request from filter, query, and include. if req.All { exp, err := storages.ExpectationsStore.Get() if err != nil { util.ReportError(w, r, err, "Failed to load expectations.") return } e := exp.Tests[req.Test] ii, _, err := imgInfo(req.Filter, req.Query, req.Test, e, -1, req.Include, false, "", "", req.Head) digests = []string{} for _, d := range ii { digests = append(digests, d.Digest) } } // Label the digests. labelledDigests := map[string]types.Label{} for _, d := range digests { labelledDigests[d] = types.LabelFromString(req.Status) } tc := map[string]types.TestClassification{ req.Test: labelledDigests, } // Otherwise update the expectations directly. if err := storages.ExpectationsStore.AddChange(tc, user); err != nil { util.ReportError(w, r, err, "Failed to store the updated expectations.") return } w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) if err := enc.Encode(map[string]string{}); err != nil { glog.Errorf("Failed to write or encode result: %s", err) } }
// Returns true if the given task can be deleted by the logged-in user; otherwise false and an error // describing the problem. func canDeleteTask(task Task, r *http.Request) (bool, error) { if !ctfeutil.UserHasAdminRights(r) { username := login.LoggedInAs(r) taskUser := task.GetCommonCols().Username if taskUser != username { return false, fmt.Errorf("Task is owned by %s but you are logged in as %s", taskUser, username) } } if task.GetCommonCols().TsStarted.Valid && !task.GetCommonCols().TsCompleted.Valid { return false, fmt.Errorf("Cannot delete currently running tasks.") } return true, nil }
func DeleteTaskHandler(prototype Task, w http.ResponseWriter, r *http.Request) { if !ctfeutil.UserHasEditRights(r) { skutil.ReportError(w, r, fmt.Errorf("Must have google or chromium account to delete tasks"), "") return } w.Header().Set("Content-Type", "application/json") vars := struct{ Id int64 }{} if err := json.NewDecoder(r.Body).Decode(&vars); err != nil { skutil.ReportError(w, r, err, "Failed to parse delete request") return } defer skutil.Close(r.Body) requireUsernameMatch := !ctfeutil.UserHasAdminRights(r) username := login.LoggedInAs(r) // Put all conditions in delete request; only if the delete fails, do a select to determine the cause. deleteQuery := fmt.Sprintf("DELETE FROM %s WHERE id = ? AND (ts_started IS NULL OR ts_completed IS NOT NULL)", prototype.TableName()) binds := []interface{}{vars.Id} if requireUsernameMatch { deleteQuery += " AND username = ?" binds = append(binds, username) } result, err := db.DB.Exec(deleteQuery, binds...) if err != nil { skutil.ReportError(w, r, err, "Failed to delete") return } // Check result to ensure that the row was deleted. if rowsDeleted, _ := result.RowsAffected(); rowsDeleted == 1 { glog.Infof("%s task with ID %d deleted by %s", prototype.GetTaskName(), vars.Id, username) return } // The code below determines the reason that no rows were deleted. rowQuery := fmt.Sprintf("SELECT * FROM %s WHERE id = ?", prototype.TableName()) data, err := prototype.Select(rowQuery, vars.Id) if err != nil { skutil.ReportError(w, r, err, "Unable to validate request.") return } tasks := AsTaskSlice(data) if len(tasks) != 1 { // Row already deleted; return success. return } if ok, err := canDeleteTask(tasks[0], r); !ok { skutil.ReportError(w, r, err, "") } else { skutil.ReportError(w, r, fmt.Errorf("Failed to delete; reason unknown"), "") return } }
// alertResetHandler deletes all the non-Bug alerts. // func alertResetHandler(w http.ResponseWriter, r *http.Request) { glog.Infof("AlertResetHandler: %q\n", r.URL.Path) if login.LoggedInAs(r) == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to change an alert status.") return } if r.Method != "POST" { http.NotFound(w, r) return } if err := alerting.Reset(); err != nil { glog.Errorln("Failed to delete all non-Bug alerts:", err) } http.Redirect(w, r, "/alerts/", 303) }
// builderRedirectHandler handles redirecting to the correct tradefed page. func builderRedirectHandler(w http.ResponseWriter, r *http.Request) { if login.LoggedInAs(r) == "" { r.Header.Set("Referer", r.URL.String()) http.Redirect(w, r, login.LoginURL(w, r), 302) return } vars := mux.Vars(r) codename := vars["codename"] target, err := codenameDB.Get([]byte(codename), nil) if err != nil { util.ReportError(w, r, err, "Not a valid target codename.") return } w.Header().Set("Content-Type", "text/html") if _, err := w.Write([]byte(fmt.Sprintf(BUILDER_REDIRECT_TEMPLATE, codename, target, target, target))); err != nil { glog.Errorf("Failed to write response: %s", err) } }
func polyIgnoresDeleteHandler(w http.ResponseWriter, r *http.Request) { user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to add an ignore rule.") return } id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 0) if err != nil { util.ReportError(w, r, err, "ID must be valid integer.") return } if _, err = storages.IgnoreStore.Delete(int(id), user); err != nil { util.ReportError(w, r, err, "Unable to delete ignore rule.") } else { // If delete worked just list the current ignores and return them. polyIgnoresJSONHandler(w, r) } }
// redirectHandler handles redirecting to the correct internal build page. func redirectHandler(w http.ResponseWriter, r *http.Request) { if login.LoggedInAs(r) == "" { r.Header.Set("Referer", r.URL.String()) http.Redirect(w, r, login.LoginURL(w, r), 302) return } vars := mux.Vars(r) codename := vars["codename"] buildNumberStr := vars["buildNumber"] target, err := codenameDB.Get([]byte(codename), nil) if err != nil { util.ReportError(w, r, err, "Not a valid target codename.") return } buildNumber, err := strconv.Atoi(buildNumberStr) if err != nil { util.ReportError(w, r, err, "Not a valid build number.") return } build, err := db.GetBuildFromDB(FAKE_MASTER, string(codename), buildNumber) if err != nil { util.ReportError(w, r, err, "Could not find a matching build.") } result := "" if id, err := build.GetStringProperty("androidinternal_buildid"); err == nil { result = fmt.Sprintf(LAUNCH_CONTROL_BUILD_REDIRECT_TEMPLATE, codename, target, id, id, target, id, id, target) } else if link, err := build.GetStringProperty("testResultsLink"); err == nil { result = fmt.Sprintf(TEST_RESULTS_REDIRECT_TEMPLATE, target, link, link) } else if cl, err := build.GetStringProperty("changeListNumber"); err == nil { link = fmt.Sprintf("http://cl/%s", cl) result = fmt.Sprintf(TEST_RESULTS_REDIRECT_TEMPLATE, target, link, link) } if result == "" { glog.Errorf("No redirect for %#v", build) util.ReportError(w, r, fmt.Errorf("No redirect for this build."), "") return } w.Header().Set("Content-Type", "text/html") if _, err := w.Write([]byte(result)); err != nil { util.ReportError(w, r, err, "Failed to write response") } }
// failureClearJSONHandler removes digests from the local cache. func failureClearJSONHandler(w http.ResponseWriter, r *http.Request) { user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to clear digests.") return } digests := []string{} dec := json.NewDecoder(r.Body) if err := dec.Decode(&digests); err != nil { util.ReportError(w, r, err, "Unable to decode digest list.") return } purgeGS := r.URL.Query().Get("purge") == "true" if err := storages.DiffStore.PurgeDigests(digests, purgeGS); err != nil { util.ReportError(w, r, err, "Unable to clear digests.") } failureListJSONHandler(w, r) }
func polyIgnoresUpdateHandler(w http.ResponseWriter, r *http.Request) { user := login.LoggedInAs(r) if user == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to update an ignore rule.") return } id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 0) if err != nil { util.ReportError(w, r, err, "ID must be valid integer.") return } req := &IgnoresRequest{} if err := parseJson(r, req); err != nil { util.ReportError(w, r, err, "Failed to parse submitted data.") return } if req.Filter == "" { util.ReportError(w, r, fmt.Errorf("Invalid Filter: %q", req.Filter), "Filters can't be empty.") return } d, err := human.ParseDuration(req.Duration) if err != nil { util.ReportError(w, r, err, "Failed to parse duration") return } ignoreRule := ignore.NewIgnoreRule(user, time.Now().Add(d), req.Filter, req.Note) if err != nil { util.ReportError(w, r, err, "Failed to create ignore rule.") return } ignoreRule.ID = int(id) err = storages.IgnoreStore.Update(int(id), ignoreRule) if err != nil { util.ReportError(w, r, err, "Unable to update ignore rule.") } else { // If update worked just list the current ignores and return them. polyIgnoresJSONHandler(w, r) } }
func modeJsonHandler(w http.ResponseWriter, r *http.Request) { if !login.IsAGoogler(r) { util.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "You must be logged in with an @google.com account to do that.") return } var mode struct { Mode string `json:"mode"` } defer util.Close(r.Body) if err := json.NewDecoder(r.Body).Decode(&mode); err != nil { util.ReportError(w, r, err, "Failed to decode request body.") return } if err := arb.SetMode(mode.Mode, login.LoggedInAs(r), "[Placeholder Message]"); err != nil { util.ReportError(w, r, err, "Failed to set AutoRoll mode.") return } // Return the ARB status. statusJsonHandler(w, r) }
func addBuilderCommentHandler(w http.ResponseWriter, r *http.Request) { defer timer.New("addBuilderCommentHandler").Stop() if !userHasEditRights(r) { util.ReportError(w, r, fmt.Errorf("User does not have edit rights."), "User does not have edit rights.") return } w.Header().Set("Content-Type", "application/json") cache, err := getCommitCache(w, r) if err != nil { return } builder := mux.Vars(r)["builder"] comment := struct { Comment string `json:"comment"` Flaky bool `json:"flaky"` IgnoreFailure bool `json:"ignoreFailure"` }{} if err := json.NewDecoder(r.Body).Decode(&comment); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add comment: %v", err)) return } defer util.Close(r.Body) c := buildbot.BuilderComment{ Builder: builder, User: login.LoggedInAs(r), Timestamp: time.Now().UTC(), Flaky: comment.Flaky, IgnoreFailure: comment.IgnoreFailure, Message: comment.Comment, } if err := cache.AddBuilderComment(builder, &c); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to add builder comment: %v", err)) return } }
func UserHasEditRights(r *http.Request) bool { return strings.HasSuffix(login.LoggedInAs(r), "@google.com") || strings.HasSuffix(login.LoggedInAs(r), "@chromium.org") }
func userHasEditRights(r *http.Request) bool { return strings.HasSuffix(login.LoggedInAs(r), "@google.com") }
// Handler serves the /annotate/ endpoint for changing the status of an // alert cluster. It also writes a new types.Activity log record to the database. // // Expects a POST of JSON of the following form: // // { // Id: 20 - The id of the alerting cluster. // Status: "Ignore" - The new Status value. // Message: "SKP Update" - The new Messge value. // } // // Returns JSON of the form: // // { // "Bug": "http://" // } // // Where bug, if set, is the URL the user should be directed to to log a bug report. func Handler(w http.ResponseWriter, r *http.Request) { glog.Infof("Annotate Handler: %q\n", r.URL.Path) if login.LoggedInAs(r) == "" { util.ReportError(w, r, fmt.Errorf("Not logged in."), "You must be logged in to change an alert status.") return } if r.Method != "POST" { http.NotFound(w, r) return } if r.Body == nil { util.ReportError(w, r, fmt.Errorf("Missing POST Body."), "POST with no request body.") return } req := struct { Id int64 Status string Message string }{} dec := json.NewDecoder(r.Body) if err := dec.Decode(&req); err != nil { util.ReportError(w, r, err, "Unable to decode posted JSON.") return } if !util.In(req.Status, types.ValidStatusValues) { util.ReportError(w, r, fmt.Errorf("Invalid status value: %s", req.Status), "Unknown value.") return } // Store the updated values in the ClusterSummary. c, err := alerting.Get(req.Id) if err != nil { util.ReportError(w, r, err, "Failed to load cluster summary.") return } c.Status = req.Status c.Message = req.Message if err := alerting.Write(c); err != nil { util.ReportError(w, r, err, "Failed to save cluster summary.") return } // Write a new Activity record. // TODO(jcgregorio) Move into alerting.Write(). a := &types.Activity{ UserID: login.LoggedInAs(r), Action: "Perf Alert: " + req.Status, URL: fmt.Sprintf("https://perf.skia.org/cl/%d", req.Id), } if err := activitylog.Write(a); err != nil { util.ReportError(w, r, err, "Failed to save activity.") return } retval := map[string]string{} if req.Status == "Bug" { q := url.Values{ "labels": []string{"FromSkiaPerf,Type-Defect,Priority-Medium"}, "comment": []string{fmt.Sprintf(ISSUE_COMMENT_TEMPLATE, req.Id)}, } retval["Bug"] = "https://bugs.chromium.org/p/skia/issues/entry?" + q.Encode() } w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) if err := enc.Encode(retval); err != nil { glog.Errorf("Failed to write or encode output: %s", err) } }
// stateHandler handles the GET of the JSON. func stateHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") allAvailable := packageInfo.AllAvailable() allInstalled := packageInfo.AllInstalled() // Update allInstalled to add in missing applications. // // Loop over 'config' and make sure each server and application is // represented, adding in "appName/" placeholders as package names where // appropriate. This is to bootstrap the case where an app is configured to // be available for a server, but no package for that application has been // installed yet. serversSeen := map[string]bool{} for name, installed := range allInstalled { installedNames := appNames(installed.Names) for _, expected := range config.Servers[name].AppNames { if !util.In(expected, installedNames) { installed.Names = append(installed.Names, expected+"/") } } allInstalled[name] = installed serversSeen[name] = true } // Now loop over config.Servers and find servers that don't have // any installed applications. Add them to allInstalled. for name, expected := range config.Servers { if _, ok := serversSeen[name]; ok { continue } installed := []string{} for _, appName := range expected.AppNames { installed = append(installed, appName+"/") } allInstalled[name].Names = installed } if r.Method == "POST" { if login.LoggedInAs(r) == "" { util.ReportError(w, r, fmt.Errorf("You must be logged on to push."), "") return } push := PushNewPackage{} dec := json.NewDecoder(r.Body) defer util.Close(r.Body) if err := dec.Decode(&push); err != nil { util.ReportError(w, r, fmt.Errorf("Failed to decode push request"), "Failed to decode push request") return } if installedPackages, ok := allInstalled[push.Server]; !ok { util.ReportError(w, r, fmt.Errorf("Unknown server name"), "Unknown server name") return } else { // Find a string starting with the same appname, replace it with // push.Name. Leave all other package names unchanged. appName := strings.Split(push.Name, "/")[0] newInstalled := []string{} for _, oldName := range installedPackages.Names { goodName := oldName if strings.Split(oldName, "/")[0] == appName { goodName = push.Name } newInstalled = append(newInstalled, goodName) } glog.Infof("Updating %s with %#v giving %#v", push.Server, push.Name, newInstalled) if err := packageInfo.PutInstalled(push.Server, newInstalled, installedPackages.Generation); err != nil { util.ReportError(w, r, err, "Failed to update server.") return } resp, err := client.Get(fmt.Sprintf("http://%s:10114/pullpullpull", push.Server)) if err != nil || resp == nil { glog.Infof("Failed to trigger an instant pull for server %s: %v %v", push.Server, err, resp) } else { util.Close(resp.Body) } allInstalled[push.Server].Names = newInstalled } } // The response to either a GET or a POST is an up to date ServersUI. servers := serversFromAllInstalled(allInstalled) enc := json.NewEncoder(w) err := enc.Encode(AllUI{ Servers: servers, Packages: allAvailable, IP: ip.Get(), Status: serviceStatus(servers), }) if err != nil { glog.Errorf("Failed to write or encode output: %s", err) return } }