blob: 571eb9723e7d98c8ba9f7ac19f23d36acd2cb84c [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 Bazanskiab55cca2018-10-25 12:35:55 +010010 "code.hackerspace.pl/hscloud/go/mirko"
Serge Bazanski8fab2be2018-10-25 23:37:37 +020011 ipb "code.hackerspace.pl/hscloud/go/proto/infra"
12 "code.hackerspace.pl/hscloud/go/statusz"
Serge Bazanskiab55cca2018-10-25 12:35:55 +010013 "code.hackerspace.pl/hscloud/go/svc/topo/graph"
14 "code.hackerspace.pl/hscloud/go/svc/topo/state"
Serge Bazanski8fab2be2018-10-25 23:37:37 +020015 "github.com/gobuffalo/packr"
16 "vbom.ml/util/sortorder"
Serge Bazanski46765082018-10-06 12:32:01 +010017)
18
Serge Bazanski46765082018-10-06 12:32:01 +010019type Service struct {
Serge Bazanskifa5d5562018-10-14 08:39:30 -070020 gr *graph.Graph
21 stm *state.StateManager
Serge Bazanski46765082018-10-06 12:32:01 +010022}
23
Serge Bazanskifa5d5562018-10-14 08:39:30 -070024func NewService(gr *graph.Graph, stm *state.StateManager) *Service {
Serge Bazanski46765082018-10-06 12:32:01 +010025 return &Service{
Serge Bazanskifa5d5562018-10-14 08:39:30 -070026 gr: gr,
27 stm: stm,
Serge Bazanski46765082018-10-06 12:32:01 +010028 }
29}
30
Serge Bazanskic7be4a12018-10-06 13:18:05 +010031const topologyFragment = `
32 <script src="/assets/viz.js"></script>
33 <script>
34
35 var viz = new Viz({ workerURL: '/assets/full.render.js' });
36 var xmlhttp = new XMLHttpRequest();
37 xmlhttp.onreadystatechange = function() {
38 if (this.readyState == 4 && this.status == 200) {
39 var dot = this.responseText;
40 viz.renderSVGElement(dot)
41 .then(function(element) {
Serge Bazanski69518ab2018-10-06 17:48:23 +010042 document.getElementById("graph").appendChild(element);
Serge Bazanskic7be4a12018-10-06 13:18:05 +010043 });
44 }
45 };
46 xmlhttp.open('GET', '/debug/graphviz');
47 xmlhttp.send();
48
49 </script>
Serge Bazanski69518ab2018-10-06 17:48:23 +010050 <div id="graph" style="text-align: center;"></div>
Serge Bazanskic7be4a12018-10-06 13:18:05 +010051`
52
Serge Bazanski16e4ba22018-10-07 00:22:52 +010053const switchportsFragment = `
54 <style type="text/css">
55 .table td,th {
56 background-color: #eee;
57 padding: 0.2em 0.4em 0.2em 0.4em;
58 }
59 .table th {
60 background-color: #c0c0c0;
61 }
62 .table {
63 background-color: #fff;
64 border-spacing: 0.2em;
65 margin-left: auto;
66 margin-right: auto;
67 }
68 </style>
69 <div>
70 <table class="table">
71 <tr>
72 <th>Switch</th>
73 <th>Port</th>
74 <th>Link State</th>
75 <th>Port Mode</th>
76 <th>MTU</th>
77 <th>Sync Status</th>
78 </tr>
79 {{range .Ports }}
80 {{ if .Managed }}
81 <tr>
82 {{ else }}
83 <tr style="opacity: 0.5">
84 {{ end}}
85 <td style="text-align: right;">{{ .Switch }}</td>
86 <td>{{ .Name }}</td>
87 {{ if eq .State "DOWN" }}
88 <td style="background-color: #ff3030;">{{ .State }}</td>
89 {{ else }}
90 <td>{{ .State }}</td>
91 {{ end }}
92 <td>{{ .Mode }}</td>
93 <td>{{ .MTU }}</td>
94 {{ if .Managed }}
95 <td style="background-color: #30ff30;">OK</td>
96 {{ else }}
97 <td><i>Unmanaged</i></td>
98 {{ end }}
99 </tr>
100 {{end}}
101 </table>
102 </div>
103`
104
Serge Bazanskifa5d5562018-10-14 08:39:30 -0700105func (s *Service) Setup(m *mirko.Mirko) {
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100106 assets := packr.NewBox("./assets")
Serge Bazanskifa5d5562018-10-14 08:39:30 -0700107 m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
108 m.HTTPMux().HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100109 statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100110 statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100111 return nil
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100112 })
Serge Bazanski8d7843c2018-10-04 10:37:36 +0100113}
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100114
115func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
116 managedPorts := make(map[string]bool)
117 s.gr.Mu.RLock()
118 for _, sw := range s.gr.Switches {
119 for _, port := range sw.Ports {
120 managedPorts[sw.Name+"|"+port.Name] = true
121 }
122 }
123 s.gr.Mu.RUnlock()
124
125 s.stm.Mu.RLock()
126 defer s.stm.Mu.RUnlock()
127
128 res := struct {
129 Ports []*struct {
130 Switch string
131 Name string
132 State string
133 Mode string
134 Managed bool
135 MTU string
136 }
137 }{}
138 for _, sw := range s.stm.Switches {
139 for _, po := range sw.Ports {
140 state := "INVALID"
141 switch po.Proto.LinkState {
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100142 case ipb.SwitchPort_LINKSTATE_DOWN:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100143 state = "DOWN"
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100144 case ipb.SwitchPort_LINKSTATE_UP:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100145 state = "UP"
146 }
147 mode := "INVALID"
148 switch po.Proto.PortMode {
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100149 case ipb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100150 mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100151 case ipb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100152 mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100153 case ipb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100154 mode = "GENERIC"
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100155 case ipb.SwitchPort_PORTMODE_ROUTED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100156 mode = "ROUTED"
Serge Bazanski26f1ee82018-10-25 12:45:00 +0100157 case ipb.SwitchPort_PORTMODE_MANGLED:
Serge Bazanski16e4ba22018-10-07 00:22:52 +0100158 mode = "MANGLED"
159 }
160
161 managed := managedPorts[sw.Name+"|"+po.Proto.Name]
162 res.Ports = append(res.Ports, &struct {
163 Switch string
164 Name string
165 State string
166 Mode string
167 Managed bool
168 MTU string
169 }{
170 Switch: sw.Name,
171 Name: po.Proto.Name,
172 State: state,
173 Mode: mode,
174 Managed: managed,
175 MTU: fmt.Sprintf("%d", po.Proto.Mtu),
176 })
177 }
178 }
179
180 return res
181}
182
183func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
184 fmt.Fprintf(w, "graph G {\n")
185 fmt.Fprintf(w, " ranksep = 2\n")
186 fmt.Fprintf(w, " splines = polyline\n")
187 fmt.Fprintf(w, " rankdir = LR\n")
188 for _, machine := range s.gr.Machines {
189 portNames := []string{}
190 for _, port := range machine.Ports {
191 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
192 portNames = append(portNames, name)
193 }
194 ports := strings.Join(portNames, "|")
195 fmt.Fprintf(w, " %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
196 }
197 for _, sw := range s.gr.Switches {
198 portNames := []string{}
199 portsOrdered := []*graph.SwitchPort{}
200 for _, port := range sw.Ports {
201 portsOrdered = append(portsOrdered, port)
202 }
203 sort.Slice(portsOrdered, func(i, j int) bool {
204 return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
205 })
206 for _, port := range portsOrdered {
207 name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
208 portNames = append(portNames, name)
209 }
210 ports := strings.Join(portNames, "|")
211 fmt.Fprintf(w, " %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
212 }
213 for _, machine := range s.gr.Machines {
214 for _, port := range machine.Ports {
215 if port.OtherEnd == nil {
216 continue
217 }
218 fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
219 }
220 }
221 fmt.Fprintf(w, "}\n")
222}