package main import ( "context" "fmt" "net/http" "sort" "strings" "code.hackerspace.pl/hscloud/go/mirko" "github.com/gobuffalo/packr" "github.com/q3k/statusz" "vbom.ml/util/sortorder" "code.hackerspace.pl/hscloud/go/svc/topo/graph" "code.hackerspace.pl/hscloud/go/svc/topo/state" ipb "code.hackerspace.pl/hscloud/go/proto/infra" ) 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 = `
` const switchportsFragment = `
{{range .Ports }} {{ if .Managed }} {{ else }} {{ end}} {{ if eq .State "DOWN" }} {{ else }} {{ end }} {{ if .Managed }} {{ else }} {{ end }} {{end}}
Switch Port Link State Port Mode MTU Sync Status
{{ .Switch }} {{ .Name }}{{ .State }}{{ .State }}{{ .Mode }} {{ .MTU }}OKUnmanaged
` func (s *Service) Setup(m *mirko.Mirko) { assets := packr.NewBox("./assets") m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets))) 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") }