hscloud/go/svc/topo/service.go

225 lines
5.5 KiB
Go
Raw Normal View History

2018-10-04 09:37:36 +00:00
package main
2018-10-06 11:32:01 +00:00
import (
2018-10-06 12:18:05 +00:00
"context"
"fmt"
2018-10-06 11:32:01 +00:00
"net/http"
2018-10-06 16:48:23 +00:00
"sort"
2018-10-06 12:55:49 +00:00
"strings"
2018-10-04 09:37:36 +00:00
2018-10-25 11:35:55 +00:00
"code.hackerspace.pl/hscloud/go/mirko"
2018-10-06 12:18:05 +00:00
"github.com/gobuffalo/packr"
"github.com/q3k/statusz"
2018-10-06 16:48:23 +00:00
"vbom.ml/util/sortorder"
2018-10-06 16:54:25 +00:00
2018-10-25 11:35:55 +00:00
"code.hackerspace.pl/hscloud/go/svc/topo/graph"
"code.hackerspace.pl/hscloud/go/svc/topo/state"
2018-10-06 23:22:52 +00:00
ipb "code.hackerspace.pl/hscloud/go/proto/infra"
2018-10-06 11:32:01 +00:00
)
type Service struct {
2018-10-14 15:39:30 +00:00
gr *graph.Graph
stm *state.StateManager
2018-10-06 11:32:01 +00:00
}
2018-10-14 15:39:30 +00:00
func NewService(gr *graph.Graph, stm *state.StateManager) *Service {
2018-10-06 11:32:01 +00:00
return &Service{
2018-10-14 15:39:30 +00:00
gr: gr,
stm: stm,
2018-10-06 11:32:01 +00:00
}
}
2018-10-06 12:18:05 +00:00
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) {
2018-10-06 16:48:23 +00:00
document.getElementById("graph").appendChild(element);
2018-10-06 12:18:05 +00:00
});
}
};
xmlhttp.open('GET', '/debug/graphviz');
xmlhttp.send();
</script>
2018-10-06 16:48:23 +00:00
<div id="graph" style="text-align: center;"></div>
2018-10-06 12:18:05 +00:00
`
2018-10-06 23:22:52 +00:00
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>
`
2018-10-14 15:39:30 +00:00
func (s *Service) Setup(m *mirko.Mirko) {
2018-10-06 12:18:05 +00:00
assets := packr.NewBox("./assets")
2018-10-14 15:39:30 +00:00
m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
m.HTTPMux().HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
2018-10-06 23:22:52 +00:00
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
2018-10-06 12:18:05 +00:00
}
2018-10-06 23:22:52 +00:00
}{}
for _, sw := range s.stm.Switches {
for _, po := range sw.Ports {
state := "INVALID"
switch po.Proto.LinkState {
case ipb.SwitchPort_LINKSTATE_DOWN:
2018-10-06 23:22:52 +00:00
state = "DOWN"
case ipb.SwitchPort_LINKSTATE_UP:
2018-10-06 23:22:52 +00:00
state = "UP"
2018-10-06 16:48:23 +00:00
}
2018-10-06 23:22:52 +00:00
mode := "INVALID"
switch po.Proto.PortMode {
case ipb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
2018-10-06 23:22:52 +00:00
mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
case ipb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
2018-10-06 23:22:52 +00:00
mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
case ipb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
2018-10-06 23:22:52 +00:00
mode = "GENERIC"
case ipb.SwitchPort_PORTMODE_ROUTED:
2018-10-06 23:22:52 +00:00
mode = "ROUTED"
case ipb.SwitchPort_PORTMODE_MANGLED:
2018-10-06 23:22:52 +00:00
mode = "MANGLED"
2018-10-06 12:18:05 +00:00
}
2018-10-06 23:22:52 +00:00
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)
2018-10-06 12:18:05 +00:00
}
2018-10-06 23:22:52 +00:00
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
2018-10-06 12:18:05 +00:00
}
2018-10-06 23:22:52 +00:00
fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
2018-10-06 12:18:05 +00:00
}
2018-10-06 23:22:52 +00:00
}
fmt.Fprintf(w, "}\n")
2018-10-04 09:37:36 +00:00
}