blob: 84eda5d2e141b30561684715ee21661b357845a3 [file] [log] [blame]
Serge Bazanski8d7843c2018-10-04 10:37:36 +01001package main
2
Serge Bazanski46765082018-10-06 12:32:01 +01003import (
Serge Bazanskic7be4a12018-10-06 13:18:05 +01004 "context"
5 "fmt"
Serge Bazanski46765082018-10-06 12:32:01 +01006 "net/http"
Serge Bazanski69518ab2018-10-06 17:48:23 +01007 "sort"
Serge Bazanskide869df2018-10-06 13:55:49 +01008 "strings"
Serge Bazanski8d7843c2018-10-04 10:37:36 +01009
Sergiusz Bazanski838cf652019-07-16 23:31:25 +020010 "vbom.ml/util/sortorder"
11
Serge Bazanskiab55cca2018-10-25 12:35:55 +010012 "code.hackerspace.pl/hscloud/go/mirko"
Serge Bazanski8fab2be2018-10-25 23:37:37 +020013 "code.hackerspace.pl/hscloud/go/statusz"
Sergiusz Bazanski838cf652019-07-16 23:31:25 +020014
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +020015 dpb "code.hackerspace.pl/hscloud/dc/proto"
Sergiusz Bazanski61594bb2019-07-21 15:20:51 +020016 "code.hackerspace.pl/hscloud/dc/topo/assets"
17 "code.hackerspace.pl/hscloud/dc/topo/graph"
18 "code.hackerspace.pl/hscloud/dc/topo/state"
Serge Bazanski46765082018-10-06 12:32:01 +010019)
20
Serge Bazanski46765082018-10-06 12:32:01 +010021type Service struct {
Serge Bazanskifa5d5562018-10-14 08:39:30 -070022 gr *graph.Graph
23 stm *state.StateManager
Serge Bazanski46765082018-10-06 12:32:01 +010024}
25
Serge Bazanskifa5d5562018-10-14 08:39:30 -070026func NewService(gr *graph.Graph, stm *state.StateManager) *Service {
Serge Bazanski46765082018-10-06 12:32:01 +010027 return &Service{
Serge Bazanskifa5d5562018-10-14 08:39:30 -070028 gr: gr,
29 stm: stm,
Serge Bazanski46765082018-10-06 12:32:01 +010030 }
31}
32
Serge Bazanskic7be4a12018-10-06 13:18:05 +010033const topologyFragment = `
34 <script src="/assets/viz.js"></script>
35 <script>
36
37 var viz = new Viz({ workerURL: '/assets/full.render.js' });
38 var xmlhttp = new XMLHttpRequest();
39 xmlhttp.onreadystatechange = function() {
40 if (this.readyState == 4 && this.status == 200) {
41 var dot = this.responseText;
42 viz.renderSVGElement(dot)
43 .then(function(element) {
Serge Bazanski69518ab2018-10-06 17:48:23 +010044 document.getElementById("graph").appendChild(element);
Serge Bazanskic7be4a12018-10-06 13:18:05 +010045 });
46 }
47 };
48 xmlhttp.open('GET', '/debug/graphviz');
49 xmlhttp.send();
50
51 </script>
Serge Bazanski69518ab2018-10-06 17:48:23 +010052 <div id="graph" style="text-align: center;"></div>
Serge Bazanskic7be4a12018-10-06 13:18:05 +010053`
54
Serge Bazanski16e4ba22018-10-07 00:22:52 +010055const switchportsFragment = `
56 <style type="text/css">
57 .table td,th {
58 background-color: #eee;
59 padding: 0.2em 0.4em 0.2em 0.4em;
60 }
61 .table th {
62 background-color: #c0c0c0;
63 }
64 .table {
65 background-color: #fff;
66 border-spacing: 0.2em;
67 margin-left: auto;
68 margin-right: auto;
69 }
70 </style>
71 <div>
72 <table class="table">
73 <tr>
74 <th>Switch</th>
75 <th>Port</th>
76 <th>Link State</th>
77 <th>Port Mode</th>
78 <th>MTU</th>
79 <th>Sync Status</th>
80 </tr>
81 {{range .Ports }}
82 {{ if .Managed }}
83 <tr>
84 {{ else }}
85 <tr style="opacity: 0.5">
86 {{ end}}
87 <td style="text-align: right;">{{ .Switch }}</td>
88 <td>{{ .Name }}</td>
89 {{ if eq .State "DOWN" }}
90 <td style="background-color: #ff3030;">{{ .State }}</td>
91 {{ else }}
92 <td>{{ .State }}</td>
93 {{ end }}
94 <td>{{ .Mode }}</td>
95 <td>{{ .MTU }}</td>
96 {{ if .Managed }}
97 <td style="background-color: #30ff30;">OK</td>
98 {{ else }}
99 <td><i>Unmanaged</i></td>
100 {{ end }}
101 </tr>
102 {{end}}
103 </table>
104 </div>
105`
106
Serge Bazanskifa5d5562018-10-14 08:39:30 -0700107func (s *Service) Setup(m *mirko.Mirko) {
Sergiusz Bazanski838cf652019-07-16 23:31:25 +0200108 m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109 data, ok := assets.Data[r.RequestURI]
110 if !ok {
111 http.NotFound(w, r)
112 return
113 }
114
115 if strings.HasSuffix(r.RequestURI, ".js") {
116 w.Header().Set("Content-Type", "text/javascript")
117 }
118
119 w.Write(data)
120 })))
Serge Bazanskifa5d5562018-10-14 08:39:30 -0700121 m.HTTPMux().HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100122 statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100123 statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100124 return nil
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100125 })
Serge Bazanski8d7843c2018-10-04 10:37:36 +0100126}
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100127
128func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
129 managedPorts := make(map[string]bool)
130 s.gr.Mu.RLock()
131 for _, sw := range s.gr.Switches {
132 for _, port := range sw.Ports {
133 managedPorts[sw.Name+"|"+port.Name] = true
134 }
135 }
136 s.gr.Mu.RUnlock()
137
138 s.stm.Mu.RLock()
139 defer s.stm.Mu.RUnlock()
140
141 res := struct {
142 Ports []*struct {
143 Switch string
144 Name string
145 State string
146 Mode string
147 Managed bool
148 MTU string
149 }
150 }{}
151 for _, sw := range s.stm.Switches {
152 for _, po := range sw.Ports {
153 state := "INVALID"
154 switch po.Proto.LinkState {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200155 case dpb.SwitchPort_LINKSTATE_DOWN:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100156 state = "DOWN"
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200157 case dpb.SwitchPort_LINKSTATE_UP:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100158 state = "UP"
159 }
160 mode := "INVALID"
161 switch po.Proto.PortMode {
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200162 case dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100163 mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200164 case dpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100165 mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200166 case dpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100167 mode = "GENERIC"
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200168 case dpb.SwitchPort_PORTMODE_ROUTED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100169 mode = "ROUTED"
Sergiusz Bazanski5b5b7c32019-07-21 16:07:49 +0200170 case dpb.SwitchPort_PORTMODE_MANGLED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100171 mode = "MANGLED"
172 }
173
174 managed := managedPorts[sw.Name+"|"+po.Proto.Name]
175 res.Ports = append(res.Ports, &struct {
176 Switch string
177 Name string
178 State string
179 Mode string
180 Managed bool
181 MTU string
182 }{
183 Switch: sw.Name,
184 Name: po.Proto.Name,
185 State: state,
186 Mode: mode,
187 Managed: managed,
188 MTU: fmt.Sprintf("%d", po.Proto.Mtu),
189 })
190 }
191 }
192
193 return res
194}
195
196func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
197 fmt.Fprintf(w, "graph G {\n")
198 fmt.Fprintf(w, " ranksep = 2\n")
199 fmt.Fprintf(w, " splines = polyline\n")
200 fmt.Fprintf(w, " rankdir = LR\n")
201 for _, machine := range s.gr.Machines {
202 portNames := []string{}
203 for _, port := range machine.Ports {
204 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
205 portNames = append(portNames, name)
206 }
207 ports := strings.Join(portNames, "|")
208 fmt.Fprintf(w, " %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
209 }
210 for _, sw := range s.gr.Switches {
211 portNames := []string{}
212 portsOrdered := []*graph.SwitchPort{}
213 for _, port := range sw.Ports {
214 portsOrdered = append(portsOrdered, port)
215 }
216 sort.Slice(portsOrdered, func(i, j int) bool {
217 return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
218 })
219 for _, port := range portsOrdered {
220 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
221 portNames = append(portNames, name)
222 }
223 ports := strings.Join(portNames, "|")
224 fmt.Fprintf(w, " %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
225 }
226 for _, machine := range s.gr.Machines {
227 for _, port := range machine.Ports {
228 if port.OtherEnd == nil {
229 continue
230 }
231 fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
232 }
233 }
234 fmt.Fprintf(w, "}\n")
235}