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