forked from hswaw/hscloud
Serge Bazanski
97b5cd7b58
This is a mega-change, but attempting to split this up further is probably not worth the effort. Summary: 1. Bump up bazel, rules_go, and others. 2. Switch to new go target naming (bye bye go_default_library) 3. Move go deps to go.mod/go.sum, use make gazelle generate from that 4. Bump up Python deps a bit And also whatever was required to actually get things to work - loads of small useless changes. Tested to work on NixOS and Ubuntu 20.04: $ bazel build //... $ bazel test //... Change-Id: I8364bdaa1406b9ae4d0385a6b607f3e7989f98a9 Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1583 Reviewed-by: q3k <q3k@hackerspace.pl>
235 lines
5.7 KiB
Go
235 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/fvbommel/sortorder"
|
|
|
|
"code.hackerspace.pl/hscloud/go/mirko"
|
|
"code.hackerspace.pl/hscloud/go/statusz"
|
|
|
|
dpb "code.hackerspace.pl/hscloud/dc/proto"
|
|
"code.hackerspace.pl/hscloud/dc/topo/assets"
|
|
"code.hackerspace.pl/hscloud/dc/topo/graph"
|
|
"code.hackerspace.pl/hscloud/dc/topo/state"
|
|
)
|
|
|
|
type Service struct {
|
|
gr *graph.Graph
|
|
stm *state.StateManager
|
|
}
|
|
|
|
func NewService(gr *graph.Graph, stm *state.StateManager) *Service {
|
|
return &Service{
|
|
gr: gr,
|
|
stm: stm,
|
|
}
|
|
}
|
|
|
|
const topologyFragment = `
|
|
<script src="/assets/viz.js"></script>
|
|
<script>
|
|
|
|
var viz = new Viz({ workerURL: '/assets/full.render.js' });
|
|
var xmlhttp = new XMLHttpRequest();
|
|
xmlhttp.onreadystatechange = function() {
|
|
if (this.readyState == 4 && this.status == 200) {
|
|
var dot = this.responseText;
|
|
viz.renderSVGElement(dot)
|
|
.then(function(element) {
|
|
document.getElementById("graph").appendChild(element);
|
|
});
|
|
}
|
|
};
|
|
xmlhttp.open('GET', '/debug/graphviz');
|
|
xmlhttp.send();
|
|
|
|
</script>
|
|
<div id="graph" style="text-align: center;"></div>
|
|
`
|
|
|
|
const switchportsFragment = `
|
|
<style type="text/css">
|
|
.table td,th {
|
|
background-color: #eee;
|
|
padding: 0.2em 0.4em 0.2em 0.4em;
|
|
}
|
|
.table th {
|
|
background-color: #c0c0c0;
|
|
}
|
|
.table {
|
|
background-color: #fff;
|
|
border-spacing: 0.2em;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
</style>
|
|
<div>
|
|
<table class="table">
|
|
<tr>
|
|
<th>Switch</th>
|
|
<th>Port</th>
|
|
<th>Link State</th>
|
|
<th>Port Mode</th>
|
|
<th>MTU</th>
|
|
<th>Sync Status</th>
|
|
</tr>
|
|
{{range .Ports }}
|
|
{{ if .Managed }}
|
|
<tr>
|
|
{{ else }}
|
|
<tr style="opacity: 0.5">
|
|
{{ end}}
|
|
<td style="text-align: right;">{{ .Switch }}</td>
|
|
<td>{{ .Name }}</td>
|
|
{{ if eq .State "DOWN" }}
|
|
<td style="background-color: #ff3030;">{{ .State }}</td>
|
|
{{ else }}
|
|
<td>{{ .State }}</td>
|
|
{{ end }}
|
|
<td>{{ .Mode }}</td>
|
|
<td>{{ .MTU }}</td>
|
|
{{ if .Managed }}
|
|
<td style="background-color: #30ff30;">OK</td>
|
|
{{ else }}
|
|
<td><i>Unmanaged</i></td>
|
|
{{ end }}
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
</div>
|
|
`
|
|
|
|
func (s *Service) Setup(m *mirko.Mirko) {
|
|
m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
data, ok := assets.Data[r.RequestURI]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.RequestURI, ".js") {
|
|
w.Header().Set("Content-Type", "text/javascript")
|
|
}
|
|
|
|
w.Write(data)
|
|
})))
|
|
m.HTTPMux().HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
|
|
statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
|
|
statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
|
|
managedPorts := make(map[string]bool)
|
|
s.gr.Mu.RLock()
|
|
for _, sw := range s.gr.Switches {
|
|
for _, port := range sw.Ports {
|
|
managedPorts[sw.Name+"|"+port.Name] = true
|
|
}
|
|
}
|
|
s.gr.Mu.RUnlock()
|
|
|
|
s.stm.Mu.RLock()
|
|
defer s.stm.Mu.RUnlock()
|
|
|
|
res := struct {
|
|
Ports []*struct {
|
|
Switch string
|
|
Name string
|
|
State string
|
|
Mode string
|
|
Managed bool
|
|
MTU string
|
|
}
|
|
}{}
|
|
for _, sw := range s.stm.Switches {
|
|
for _, po := range sw.Ports {
|
|
state := "INVALID"
|
|
switch po.Proto.LinkState {
|
|
case dpb.SwitchPort_LINKSTATE_DOWN:
|
|
state = "DOWN"
|
|
case dpb.SwitchPort_LINKSTATE_UP:
|
|
state = "UP"
|
|
}
|
|
mode := "INVALID"
|
|
switch po.Proto.PortMode {
|
|
case dpb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
|
|
mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
|
|
case dpb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
|
|
mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
|
|
case dpb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
|
|
mode = "GENERIC"
|
|
case dpb.SwitchPort_PORTMODE_ROUTED:
|
|
mode = "ROUTED"
|
|
case dpb.SwitchPort_PORTMODE_MANGLED:
|
|
mode = "MANGLED"
|
|
}
|
|
|
|
managed := managedPorts[sw.Name+"|"+po.Proto.Name]
|
|
res.Ports = append(res.Ports, &struct {
|
|
Switch string
|
|
Name string
|
|
State string
|
|
Mode string
|
|
Managed bool
|
|
MTU string
|
|
}{
|
|
Switch: sw.Name,
|
|
Name: po.Proto.Name,
|
|
State: state,
|
|
Mode: mode,
|
|
Managed: managed,
|
|
MTU: fmt.Sprintf("%d", po.Proto.Mtu),
|
|
})
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "graph G {\n")
|
|
fmt.Fprintf(w, " ranksep = 2\n")
|
|
fmt.Fprintf(w, " splines = polyline\n")
|
|
fmt.Fprintf(w, " rankdir = LR\n")
|
|
for _, machine := range s.gr.Machines {
|
|
portNames := []string{}
|
|
for _, port := range machine.Ports {
|
|
name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
|
|
portNames = append(portNames, name)
|
|
}
|
|
ports := strings.Join(portNames, "|")
|
|
fmt.Fprintf(w, " %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
|
|
}
|
|
for _, sw := range s.gr.Switches {
|
|
portNames := []string{}
|
|
portsOrdered := []*graph.SwitchPort{}
|
|
for _, port := range sw.Ports {
|
|
portsOrdered = append(portsOrdered, port)
|
|
}
|
|
sort.Slice(portsOrdered, func(i, j int) bool {
|
|
return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
|
|
})
|
|
for _, port := range portsOrdered {
|
|
name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
|
|
portNames = append(portNames, name)
|
|
}
|
|
ports := strings.Join(portNames, "|")
|
|
fmt.Fprintf(w, " %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
|
|
}
|
|
for _, machine := range s.gr.Machines {
|
|
for _, port := range machine.Ports {
|
|
if port.OtherEnd == nil {
|
|
continue
|
|
}
|
|
fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "}\n")
|
|
}
|