blob: 03fd218be45510c10ea585a3f9386003a5c5ae6d [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
Serge Bazanskic7be4a12018-10-06 13:18:05 +010010 "github.com/gobuffalo/packr"
Serge Bazanski46765082018-10-06 12:32:01 +010011 "github.com/golang/glog"
Serge Bazanskic7be4a12018-10-06 13:18:05 +010012 "github.com/q3k/statusz"
Serge Bazanski69518ab2018-10-06 17:48:23 +010013 "vbom.ml/util/sortorder"
Serge Bazanskia758ef52018-10-06 17:54:25 +010014
15 "code.hackerspace.pl/q3k/topo/graph"
16 "code.hackerspace.pl/q3k/topo/state"
Serge Bazanski16e4ba22018-10-07 00:22:52 +010017
18 pb "code.hackerspace.pl/q3k/topo/proto/control"
Serge Bazanski46765082018-10-06 12:32:01 +010019)
20
21type ServiceConfig struct {
22 DebugListen string
23}
24
25type Service struct {
26 gr *graph.Graph
Serge Bazanskia758ef52018-10-06 17:54:25 +010027 stm *state.StateManager
Serge Bazanski46765082018-10-06 12:32:01 +010028 config ServiceConfig
29}
30
Serge Bazanskia758ef52018-10-06 17:54:25 +010031func NewService(gr *graph.Graph, stm *state.StateManager, c ServiceConfig) *Service {
Serge Bazanski46765082018-10-06 12:32:01 +010032 return &Service{
33 gr: gr,
Serge Bazanskia758ef52018-10-06 17:54:25 +010034 stm: stm,
Serge Bazanski46765082018-10-06 12:32:01 +010035 config: c,
36 }
37}
38
Serge Bazanskic7be4a12018-10-06 13:18:05 +010039const topologyFragment = `
40 <script src="/assets/viz.js"></script>
41 <script>
42
43 var viz = new Viz({ workerURL: '/assets/full.render.js' });
44 var xmlhttp = new XMLHttpRequest();
45 xmlhttp.onreadystatechange = function() {
46 if (this.readyState == 4 && this.status == 200) {
47 var dot = this.responseText;
48 viz.renderSVGElement(dot)
49 .then(function(element) {
Serge Bazanski69518ab2018-10-06 17:48:23 +010050 document.getElementById("graph").appendChild(element);
Serge Bazanskic7be4a12018-10-06 13:18:05 +010051 });
52 }
53 };
54 xmlhttp.open('GET', '/debug/graphviz');
55 xmlhttp.send();
56
57 </script>
Serge Bazanski69518ab2018-10-06 17:48:23 +010058 <div id="graph" style="text-align: center;"></div>
Serge Bazanskic7be4a12018-10-06 13:18:05 +010059`
60
Serge Bazanski16e4ba22018-10-07 00:22:52 +010061const switchportsFragment = `
62 <style type="text/css">
63 .table td,th {
64 background-color: #eee;
65 padding: 0.2em 0.4em 0.2em 0.4em;
66 }
67 .table th {
68 background-color: #c0c0c0;
69 }
70 .table {
71 background-color: #fff;
72 border-spacing: 0.2em;
73 margin-left: auto;
74 margin-right: auto;
75 }
76 </style>
77 <div>
78 <table class="table">
79 <tr>
80 <th>Switch</th>
81 <th>Port</th>
82 <th>Link State</th>
83 <th>Port Mode</th>
84 <th>MTU</th>
85 <th>Sync Status</th>
86 </tr>
87 {{range .Ports }}
88 {{ if .Managed }}
89 <tr>
90 {{ else }}
91 <tr style="opacity: 0.5">
92 {{ end}}
93 <td style="text-align: right;">{{ .Switch }}</td>
94 <td>{{ .Name }}</td>
95 {{ if eq .State "DOWN" }}
96 <td style="background-color: #ff3030;">{{ .State }}</td>
97 {{ else }}
98 <td>{{ .State }}</td>
99 {{ end }}
100 <td>{{ .Mode }}</td>
101 <td>{{ .MTU }}</td>
102 {{ if .Managed }}
103 <td style="background-color: #30ff30;">OK</td>
104 {{ else }}
105 <td><i>Unmanaged</i></td>
106 {{ end }}
107 </tr>
108 {{end}}
109 </table>
110 </div>
111`
112
Serge Bazanski46765082018-10-06 12:32:01 +0100113func (s *Service) Run() {
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100114 assets := packr.NewBox("./assets")
115 http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
Serge Bazanski46765082018-10-06 12:32:01 +0100116 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
117 http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
118 })
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100119 http.HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100120
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100121 statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100122 statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100123 return nil
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100124 })
Serge Bazanski46765082018-10-06 12:32:01 +0100125 glog.Infof("Debug listening on %s....", s.config.DebugListen)
126 http.ListenAndServe(s.config.DebugListen, nil)
Serge Bazanski8d7843c2018-10-04 10:37:36 +0100127}
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100128
129func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
130 managedPorts := make(map[string]bool)
131 s.gr.Mu.RLock()
132 for _, sw := range s.gr.Switches {
133 for _, port := range sw.Ports {
134 managedPorts[sw.Name+"|"+port.Name] = true
135 }
136 }
137 s.gr.Mu.RUnlock()
138
139 s.stm.Mu.RLock()
140 defer s.stm.Mu.RUnlock()
141
142 res := struct {
143 Ports []*struct {
144 Switch string
145 Name string
146 State string
147 Mode string
148 Managed bool
149 MTU string
150 }
151 }{}
152 for _, sw := range s.stm.Switches {
153 for _, po := range sw.Ports {
154 state := "INVALID"
155 switch po.Proto.LinkState {
156 case pb.SwitchPort_LINKSTATE_DOWN:
157 state = "DOWN"
158 case pb.SwitchPort_LINKSTATE_UP:
159 state = "UP"
160 }
161 mode := "INVALID"
162 switch po.Proto.PortMode {
163 case pb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
164 mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
165 case pb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
166 mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
167 case pb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
168 mode = "GENERIC"
169 case pb.SwitchPort_PORTMODE_ROUTED:
170 mode = "ROUTED"
171 case pb.SwitchPort_PORTMODE_MANGLED:
172 mode = "MANGLED"
173 }
174
175 managed := managedPorts[sw.Name+"|"+po.Proto.Name]
176 res.Ports = append(res.Ports, &struct {
177 Switch string
178 Name string
179 State string
180 Mode string
181 Managed bool
182 MTU string
183 }{
184 Switch: sw.Name,
185 Name: po.Proto.Name,
186 State: state,
187 Mode: mode,
188 Managed: managed,
189 MTU: fmt.Sprintf("%d", po.Proto.Mtu),
190 })
191 }
192 }
193
194 return res
195}
196
197func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
198 fmt.Fprintf(w, "graph G {\n")
199 fmt.Fprintf(w, " ranksep = 2\n")
200 fmt.Fprintf(w, " splines = polyline\n")
201 fmt.Fprintf(w, " rankdir = LR\n")
202 for _, machine := range s.gr.Machines {
203 portNames := []string{}
204 for _, port := range machine.Ports {
205 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
206 portNames = append(portNames, name)
207 }
208 ports := strings.Join(portNames, "|")
209 fmt.Fprintf(w, " %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
210 }
211 for _, sw := range s.gr.Switches {
212 portNames := []string{}
213 portsOrdered := []*graph.SwitchPort{}
214 for _, port := range sw.Ports {
215 portsOrdered = append(portsOrdered, port)
216 }
217 sort.Slice(portsOrdered, func(i, j int) bool {
218 return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
219 })
220 for _, port := range portsOrdered {
221 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
222 portNames = append(portNames, name)
223 }
224 ports := strings.Join(portNames, "|")
225 fmt.Fprintf(w, " %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
226 }
227 for _, machine := range s.gr.Machines {
228 for _, port := range machine.Ports {
229 if port.OtherEnd == nil {
230 continue
231 }
232 fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
233 }
234 }
235 fmt.Fprintf(w, "}\n")
236}