go/svc/(dc stuff) -> dc/
We want to start keeping codebases separated per 'team'/intent, to then
have simple OWNER files/trees to specify review rules.
This means dc/ stuff can all be OWNED by q3k, and review will only
involve a +1 for style/readability, instead of a +2 for approval.
Change-Id: I05afbc4e1018944b841ec0d88cd24cc95bec8bf1
diff --git a/dc/topo/service.go b/dc/topo/service.go
new file mode 100644
index 0000000..8919939
--- /dev/null
+++ b/dc/topo/service.go
@@ -0,0 +1,235 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "sort"
+ "strings"
+
+ "vbom.ml/util/sortorder"
+
+ "code.hackerspace.pl/hscloud/go/mirko"
+ "code.hackerspace.pl/hscloud/go/statusz"
+ ipb "code.hackerspace.pl/hscloud/proto/infra"
+
+ "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 ipb.SwitchPort_LINKSTATE_DOWN:
+ state = "DOWN"
+ case ipb.SwitchPort_LINKSTATE_UP:
+ state = "UP"
+ }
+ mode := "INVALID"
+ switch po.Proto.PortMode {
+ case ipb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
+ mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
+ case ipb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
+ mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
+ case ipb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
+ mode = "GENERIC"
+ case ipb.SwitchPort_PORTMODE_ROUTED:
+ mode = "ROUTED"
+ case ipb.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")
+}