blob: 916299cfe3e1dfffbb5088d0b44fedcea067140f [file] [log] [blame]
package main
import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"github.com/gobuffalo/packr"
"github.com/golang/glog"
"github.com/q3k/statusz"
"vbom.ml/util/sortorder"
"code.hackerspace.pl/q3k/topo/graph"
"code.hackerspace.pl/q3k/topo/state"
)
type ServiceConfig struct {
DebugListen string
}
type Service struct {
gr *graph.Graph
stm *state.StateManager
config ServiceConfig
}
func NewService(gr *graph.Graph, stm *state.StateManager, c ServiceConfig) *Service {
return &Service{
gr: gr,
stm: stm,
config: c,
}
}
type TopologyStatus struct {
}
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>
`
func (s *Service) Run() {
assets := packr.NewBox("./assets")
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
})
http.HandleFunc("/debug/graphviz", func(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")
})
statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
return &TopologyStatus{}
})
glog.Infof("Debug listening on %s....", s.config.DebugListen)
http.ListenAndServe(s.config.DebugListen, nil)
}