blob: 0cf34ac4d57360151455136c152675d73897451d [file] [log] [blame]
package webug
import (
"context"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
"k8s.io/klog/v2"
"source.monogon.dev/cloud/bmaas/bmdb/model"
"source.monogon.dev/cloud/bmaas/bmdb/reflection"
)
// baseParams are passed to all rendered templates, and are consumed by tags in
// templates/base.gohtml.
type baseParams struct {
// Address to display in page header.
BMDBAddress string
// Schema version to display in page header.
BMDBSchema string
}
// makeBase builds baseParams from information about the current connection.
func (s *server) makeBase() baseParams {
address := fmt.Sprintf("%s@%s", s.conn.DatabaseName, s.conn.Address)
if s.conn.InMemory {
address += " (in memory)"
}
return baseParams{
BMDBAddress: address,
BMDBSchema: s.curSchema().Version,
}
}
// viewMachines renders a list of all machines in the BMDB.
func (s *server) viewMachines(w http.ResponseWriter, r *http.Request, args ...string) {
start := time.Now()
res, err := s.curSchema().GetMachines(r.Context(), &reflection.GetMachinesOpts{Strict: true})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "could not dump BMDB: %v", err)
return
}
duration := time.Since(start)
tagCount := make(map[string]int)
for _, d := range res.Data {
for _, t := range d.Tags {
tagCount[t.Type.Name()]++
}
}
type params struct {
Base baseParams
Query string
Machines []*reflection.Machine
NMachines int
RenderTime time.Duration
TagCount map[string]int
}
err = templates.ExecuteTemplate(w, "machines.gohtml", &params{
Base: s.makeBase(),
Query: res.Query,
Machines: res.Data,
NMachines: len(res.Data),
RenderTime: duration,
TagCount: tagCount,
})
if err != nil {
klog.Errorf("Template rendering failed: %v", err)
}
}
// viewMachineDetail renders a detailed page for a single machine.
func (s *server) viewMachineDetail(w http.ResponseWriter, r *http.Request, args ...string) {
mid, err := uuid.Parse(args[0])
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
fmt.Fprintf(w, "invalid machine UUID")
return
}
opts := reflection.GetMachinesOpts{
FilterMachine: &mid,
Strict: true,
ExpiredBackoffs: true,
}
res, err := s.curSchema().GetMachines(r.Context(), &opts)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "could not dump BMDB: %v", err)
return
}
if len(res.Data) == 0 {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "machine not found")
return
}
machine := res.Data[0]
// Params to pass to template.
type sessionOrError struct {
Session *model.Session
Error string
}
type params struct {
Base baseParams
Machine *reflection.Machine
HistoryError string
History []model.WorkHistory
Sessions map[string]sessionOrError
}
p := params{
Base: s.makeBase(),
Machine: machine,
Sessions: make(map[string]sessionOrError),
}
// History retrieval is performed with strict consistency guarantees, and thus
// might block. Make sure we don't block the entire page.
subQueriesCtx, subQueriesCtxC := context.WithTimeout(r.Context(), time.Second)
defer subQueriesCtxC()
history, err := s.conn.ListHistoryOf(subQueriesCtx, mid)
if err != nil {
p.HistoryError = err.Error()
}
// Same for sessions.
for name, work := range machine.Work {
sessions, err := s.conn.GetSession(subQueriesCtx, work.SessionID)
if err != nil {
p.Sessions[name] = sessionOrError{Error: err.Error()}
} else {
if len(sessions) == 0 {
// This can happen if the session literally just disappeared.
//
// TODO(q3k): put all of these operations in a DB TX so that we don't end up with
// possible inconsistencies?
p.Sessions[name] = sessionOrError{Error: "not found"}
continue
}
p.Sessions[name] = sessionOrError{Session: &sessions[0]}
}
}
p.History = make([]model.WorkHistory, len(history))
for i := 0; i < len(history); i += 1 {
p.History[i] = history[len(history)-(i+1)]
}
if err := templates.ExecuteTemplate(w, "machine.gohtml", &p); err != nil {
klog.Errorf("Template rendering failed: %v", err)
}
}
// viewProviderRedirects redirects a given provider and provider_id into a
// provider's web portal for more detailed information about an underlying
// machine.
func (s *server) viewProviderRedirect(w http.ResponseWriter, r *http.Request, args ...string) {
providerUrls := map[string]string{
"Equinix": "https://console.equinix.com/devices/%s/overview",
}
if providerUrls[args[0]] == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Usage: /provider/Equinix/<id>")
return
}
url := fmt.Sprintf(providerUrls[args[0]], args[1])
http.Redirect(w, r, url, http.StatusFound)
}
// viewSession shows detailed information about a BMDB session.
func (s *server) viewSession(w http.ResponseWriter, r *http.Request, args ...string) {
// TODO(q3k): implement this once we add session info to work history so that
// this can actually display something useful.
fmt.Fprintf(w, "underconstruction.gif")
}