blob: f47ece713af6f9b1bdbaa6e9b7ee4218ea6366ba [file] [log] [blame]
Serge Bazanski77628312023-02-15 23:33:22 +01001package webug
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 "github.com/google/uuid"
10 "k8s.io/klog/v2"
11
12 "source.monogon.dev/cloud/bmaas/bmdb/model"
13 "source.monogon.dev/cloud/bmaas/bmdb/reflection"
14)
15
16// baseParams are passed to all rendered templates, and are consumed by tags in
17// templates/base.html.
18type baseParams struct {
19 // Address to display in page header.
20 BMDBAddress string
21 // Schema version to display in page header.
22 BMDBSchema string
23}
24
25// makeBase builds baseParams from information about the current connection.
26func (s *server) makeBase() baseParams {
27 address := fmt.Sprintf("%s@%s", s.conn.DatabaseName, s.conn.Address)
28 if s.conn.InMemory {
29 address += " (in memory)"
30 }
31 return baseParams{
32 BMDBAddress: address,
33 BMDBSchema: s.curSchema().Version,
34 }
35}
36
37// viewMachines renders a list of all machines in the BMDB.
38func (s *server) viewMachines(w http.ResponseWriter, r *http.Request, args ...string) {
39 start := time.Now()
40 res, err := s.curSchema().GetMachines(r.Context(), &reflection.GetMachinesOpts{Strict: true})
41 if err != nil {
42 w.WriteHeader(http.StatusInternalServerError)
43 fmt.Fprintf(w, "could not dump BMDB: %v", err)
44 return
45 }
46 duration := time.Since(start)
47
Tim Windelschmidtf984e1e2023-04-19 23:12:38 +020048 tagCount := make(map[string]int)
49 for _, d := range res.Data {
50 for _, t := range d.Tags {
51 tagCount[t.Type.Name()]++
52 }
53 }
54
Serge Bazanski77628312023-02-15 23:33:22 +010055 type params struct {
56 Base baseParams
57 Query string
58 Machines []*reflection.Machine
59 NMachines int
60 RenderTime time.Duration
Tim Windelschmidtf984e1e2023-04-19 23:12:38 +020061 TagCount map[string]int
Serge Bazanski77628312023-02-15 23:33:22 +010062 }
63 err = templates.ExecuteTemplate(w, "machines.html", &params{
64 Base: s.makeBase(),
65 Query: res.Query,
66 Machines: res.Data,
67 NMachines: len(res.Data),
68 RenderTime: duration,
Tim Windelschmidtf984e1e2023-04-19 23:12:38 +020069 TagCount: tagCount,
Serge Bazanski77628312023-02-15 23:33:22 +010070 })
71 if err != nil {
72 klog.Errorf("Template rendering failed: %v", err)
73 }
74}
75
76// viewMachineDetail renders a detailed page for a single machine.
77func (s *server) viewMachineDetail(w http.ResponseWriter, r *http.Request, args ...string) {
78 mid, err := uuid.Parse(args[0])
79 if err != nil {
80 w.WriteHeader(http.StatusUnprocessableEntity)
81 fmt.Fprintf(w, "invalid machine UUID")
82 return
83 }
84
85 opts := reflection.GetMachinesOpts{
86 FilterMachine: &mid,
87 Strict: true,
88 ExpiredBackoffs: true,
89 }
90 res, err := s.curSchema().GetMachines(r.Context(), &opts)
91 if err != nil {
92 w.WriteHeader(http.StatusInternalServerError)
93 fmt.Fprintf(w, "could not dump BMDB: %v", err)
94 return
95 }
96 if len(res.Data) == 0 {
97 w.WriteHeader(http.StatusNotFound)
98 fmt.Fprintf(w, "machine not found")
99 return
100 }
101 machine := res.Data[0]
102
103 // Params to pass to template.
104 type sessionOrError struct {
105 Session *model.Session
106 Error string
107 }
108 type params struct {
109 Base baseParams
110 Machine *reflection.Machine
111
112 HistoryError string
113 History []model.WorkHistory
114
115 Sessions map[string]sessionOrError
116 }
117 p := params{
118 Base: s.makeBase(),
119 Machine: machine,
120 Sessions: make(map[string]sessionOrError),
121 }
122
123 // History retrieval is performed with strict consistency guarantees, and thus
124 // might block. Make sure we don't block the entire page.
125 subQueriesCtx, subQueriesCtxC := context.WithTimeout(r.Context(), time.Second)
126 defer subQueriesCtxC()
127 history, err := s.conn.ListHistoryOf(subQueriesCtx, mid)
128 if err != nil {
129 p.HistoryError = err.Error()
130 }
131
132 // Same for sessions.
133 for name, work := range machine.Work {
134 sessions, err := s.conn.GetSession(subQueriesCtx, work.SessionID)
135 if err != nil {
136 p.Sessions[name] = sessionOrError{Error: err.Error()}
137 } else {
138 if len(sessions) == 0 {
139 // This can happen if the session literally just disappeared.
140 //
141 // TODO(q3k): put all of these operations in a DB TX so that we don't end up with
142 // possible inconsistencies?
143 p.Sessions[name] = sessionOrError{Error: "not found"}
144 continue
145 }
146 p.Sessions[name] = sessionOrError{Session: &sessions[0]}
147 }
148 }
149
150 p.History = make([]model.WorkHistory, len(history))
151 for i := 0; i < len(history); i += 1 {
152 p.History[i] = history[len(history)-(i+1)]
153 }
154 if err := templates.ExecuteTemplate(w, "machine.html", &p); err != nil {
155 klog.Errorf("Template rendering failed: %v", err)
156 }
157}
158
159// viewProviderRedirects redirects a given provider and provider_id into a
160// provider's web portal for more detailed information about an underlying
161// machine.
162func (s *server) viewProviderRedirect(w http.ResponseWriter, r *http.Request, args ...string) {
163 providerUrls := map[string]string{
164 "Equinix": "https://console.equinix.com/devices/%s/overview",
165 }
166 if providerUrls[args[0]] == "" {
167 w.WriteHeader(http.StatusNotFound)
168 fmt.Fprintf(w, "Usage: /provider/Equinix/<id>")
169 return
170 }
171 url := fmt.Sprintf(providerUrls[args[0]], args[1])
172 http.Redirect(w, r, url, http.StatusFound)
173}
174
175// viewSession shows detailed information about a BMDB session.
176func (s *server) viewSession(w http.ResponseWriter, r *http.Request, args ...string) {
177 // TODO(q3k): implement this once we add session info to work history so that
178 // this can actually display something useful.
179 fmt.Fprintf(w, "underconstruction.gif")
180}