1
0
Fork 0

implement basic status checking

master
Serge Bazanski 2018-10-07 00:22:52 +01:00
parent a758ef510b
commit 16e4ba2ee7
7 changed files with 265 additions and 126 deletions

9
Gopkg.lock generated
View File

@ -1,6 +1,14 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:2af0bc306f77bef606f6f6033652f0de3cc80e77008f4da52197b4b0a6be5853"
name = "code.hackerspace.pl/q3k/hspki"
packages = ["."]
pruneopts = "UT"
revision = "624295da664fea57a1e3bd8cee639ae04f4c0ccf"
[[projects]] [[projects]]
digest = "1:d1665c44bd5db19aaee18d1b6233c99b0b9a986e8bccb24ef54747547a48027f" digest = "1:d1665c44bd5db19aaee18d1b6233c99b0b9a986e8bccb24ef54747547a48027f"
name = "github.com/PuerkitoBio/purell" name = "github.com/PuerkitoBio/purell"
@ -358,6 +366,7 @@
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
input-imports = [ input-imports = [
"code.hackerspace.pl/q3k/hspki",
"github.com/digitalocean/go-netbox/netbox", "github.com/digitalocean/go-netbox/netbox",
"github.com/digitalocean/go-netbox/netbox/client", "github.com/digitalocean/go-netbox/netbox/client",
"github.com/digitalocean/go-netbox/netbox/client/dcim", "github.com/digitalocean/go-netbox/netbox/client/dcim",

View File

@ -7,18 +7,11 @@ message Config {
message Switch { message Switch {
string name = 1; string name = 1;
string control_address = 2;
enum Connector {
CONNECTOR_INVALID = 0;
CONNECTOR_M6220 = 1;
CONNECTOR_ARISTA = 2;
};
Connector connector = 2;
string address = 3;
message SwitchPort { message SwitchPort {
string name = 1; string name = 1;
}; };
repeated SwitchPort managed_port = 4; repeated SwitchPort managed_port = 3;
message Segment { message Segment {
enum Type { enum Type {
TYPE_INVALID = 0; TYPE_INVALID = 0;
@ -27,7 +20,7 @@ message Switch {
Type segment_type = 1; Type segment_type = 1;
int32 vlan_id = 2; int32 vlan_id = 2;
}; };
repeated Segment available_segment = 5; repeated Segment available_segment = 4;
}; };
message Machine { message Machine {

View File

@ -3,6 +3,7 @@ package graph
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"github.com/digitalocean/go-netbox/netbox/client" "github.com/digitalocean/go-netbox/netbox/client"
"github.com/digitalocean/go-netbox/netbox/client/dcim" "github.com/digitalocean/go-netbox/netbox/client/dcim"
@ -41,6 +42,7 @@ type Switch struct {
type Graph struct { type Graph struct {
Switches map[string]*Switch Switches map[string]*Switch
Machines map[string]*Machine Machines map[string]*Machine
Mu sync.RWMutex
} }
func New() *Graph { func New() *Graph {

View File

@ -46,6 +46,12 @@ func main() {
config := confpb.Config{} config := confpb.Config{}
proto.UnmarshalText(string(data), &config) proto.UnmarshalText(string(data), &config)
stm := state.NewManager()
err = stm.FetchState(ctx, &config)
if err != nil {
glog.Exitf("Initial state fetch failed: %v", err)
}
gr := graph.New() gr := graph.New()
err = gr.LoadConfig(&config) err = gr.LoadConfig(&config)
if err != nil { if err != nil {
@ -59,8 +65,6 @@ func main() {
glog.Exitf("Initial netbox feed failed: %v", err) glog.Exitf("Initial netbox feed failed: %v", err)
} }
stm := state.NewManager()
sconf := ServiceConfig{ sconf := ServiceConfig{
DebugListen: flagDebugListen, DebugListen: flagDebugListen,
} }

View File

@ -20,34 +20,6 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Switch_Connector int32
const (
Switch_CONNECTOR_INVALID Switch_Connector = 0
Switch_CONNECTOR_M6220 Switch_Connector = 1
Switch_CONNECTOR_ARISTA Switch_Connector = 2
)
var Switch_Connector_name = map[int32]string{
0: "CONNECTOR_INVALID",
1: "CONNECTOR_M6220",
2: "CONNECTOR_ARISTA",
}
var Switch_Connector_value = map[string]int32{
"CONNECTOR_INVALID": 0,
"CONNECTOR_M6220": 1,
"CONNECTOR_ARISTA": 2,
}
func (x Switch_Connector) String() string {
return proto.EnumName(Switch_Connector_name, int32(x))
}
func (Switch_Connector) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_3eaf2c85e69e9ea4, []int{1, 0}
}
type Switch_Segment_Type int32 type Switch_Segment_Type int32
const ( const (
@ -122,10 +94,9 @@ func (m *Config) GetMachine() []*Machine {
type Switch struct { type Switch struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Connector Switch_Connector `protobuf:"varint,2,opt,name=connector,proto3,enum=Switch_Connector" json:"connector,omitempty"` ControlAddress string `protobuf:"bytes,2,opt,name=control_address,json=controlAddress,proto3" json:"control_address,omitempty"`
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` ManagedPort []*Switch_SwitchPort `protobuf:"bytes,3,rep,name=managed_port,json=managedPort,proto3" json:"managed_port,omitempty"`
ManagedPort []*Switch_SwitchPort `protobuf:"bytes,4,rep,name=managed_port,json=managedPort,proto3" json:"managed_port,omitempty"` AvailableSegment []*Switch_Segment `protobuf:"bytes,4,rep,name=available_segment,json=availableSegment,proto3" json:"available_segment,omitempty"`
AvailableSegment []*Switch_Segment `protobuf:"bytes,5,rep,name=available_segment,json=availableSegment,proto3" json:"available_segment,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -163,16 +134,9 @@ func (m *Switch) GetName() string {
return "" return ""
} }
func (m *Switch) GetConnector() Switch_Connector { func (m *Switch) GetControlAddress() string {
if m != nil { if m != nil {
return m.Connector return m.ControlAddress
}
return Switch_CONNECTOR_INVALID
}
func (m *Switch) GetAddress() string {
if m != nil {
return m.Address
} }
return "" return ""
} }
@ -364,7 +328,6 @@ func (m *Machine_Port) GetName() string {
} }
func init() { func init() {
proto.RegisterEnum("Switch_Connector", Switch_Connector_name, Switch_Connector_value)
proto.RegisterEnum("Switch_Segment_Type", Switch_Segment_Type_name, Switch_Segment_Type_value) proto.RegisterEnum("Switch_Segment_Type", Switch_Segment_Type_name, Switch_Segment_Type_value)
proto.RegisterType((*Config)(nil), "Config") proto.RegisterType((*Config)(nil), "Config")
proto.RegisterType((*Switch)(nil), "Switch") proto.RegisterType((*Switch)(nil), "Switch")
@ -377,30 +340,26 @@ func init() {
func init() { proto.RegisterFile("config.proto", fileDescriptor_3eaf2c85e69e9ea4) } func init() { proto.RegisterFile("config.proto", fileDescriptor_3eaf2c85e69e9ea4) }
var fileDescriptor_3eaf2c85e69e9ea4 = []byte{ var fileDescriptor_3eaf2c85e69e9ea4 = []byte{
// 394 bytes of a gzipped FileDescriptorProto // 335 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xc1, 0x6e, 0x9b, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x41, 0x4f, 0xc2, 0x30,
0x10, 0x86, 0x8b, 0x4d, 0xa0, 0x8c, 0x49, 0x82, 0xb7, 0xa9, 0xba, 0xe2, 0x52, 0xc4, 0xa5, 0x3e, 0x14, 0x76, 0x30, 0x37, 0x79, 0x0c, 0x98, 0x8d, 0x89, 0xcb, 0x2e, 0x92, 0x5d, 0xe0, 0xb4, 0x18,
0xd1, 0x88, 0xaa, 0xed, 0xa5, 0x17, 0x44, 0x73, 0x40, 0x0a, 0x24, 0xda, 0xa0, 0x48, 0x3d, 0xa1, 0x8c, 0xf1, 0xe2, 0x85, 0xa8, 0x07, 0x12, 0x20, 0xa4, 0x12, 0x12, 0x4f, 0x4b, 0xd9, 0x2a, 0x2c,
0x0d, 0x6c, 0x1d, 0x54, 0xb3, 0x20, 0x40, 0xae, 0x7c, 0xef, 0x23, 0xf6, 0x81, 0x2a, 0x2f, 0x8b, 0x6e, 0xed, 0xb2, 0x35, 0x18, 0xee, 0xfe, 0x53, 0xff, 0x88, 0xa1, 0x2d, 0x98, 0x20, 0x9e, 0xda,
0xa9, 0x5c, 0xf7, 0x04, 0xff, 0x3f, 0xdf, 0xcc, 0xc0, 0xcc, 0x80, 0x99, 0xd7, 0xfc, 0x7b, 0xb9, 0xef, 0x7b, 0xdf, 0xeb, 0x7b, 0xef, 0x7b, 0x05, 0x27, 0xe6, 0xec, 0x3d, 0x5d, 0x85, 0x45, 0xc9,
0xf6, 0x9a, 0xb6, 0xee, 0x6b, 0x37, 0x06, 0x2d, 0x14, 0x1a, 0xbd, 0x05, 0xad, 0xfb, 0x59, 0xf6, 0x05, 0x0f, 0x26, 0x60, 0x3d, 0x49, 0x8c, 0x6e, 0xc0, 0xaa, 0x3e, 0x53, 0x11, 0xaf, 0x3d, 0xa3,
0xf9, 0x33, 0x56, 0x9c, 0xf9, 0x6a, 0xe1, 0xeb, 0xde, 0x83, 0x90, 0x44, 0xda, 0xc8, 0x05, 0xbd, 0x5b, 0xef, 0x37, 0x07, 0x76, 0xf8, 0x2a, 0x21, 0xd6, 0x34, 0x0a, 0xc0, 0xce, 0x49, 0xbc, 0x4e,
0xa2, 0xf9, 0x73, 0xc9, 0x19, 0x9e, 0x09, 0xe2, 0xa5, 0x17, 0x0f, 0x9a, 0x8c, 0x01, 0xf7, 0xf7, 0x19, 0xf5, 0x6a, 0x52, 0x71, 0x11, 0x4e, 0x14, 0xc6, 0xfb, 0x40, 0xf0, 0x5d, 0x03, 0x4b, 0xa5,
0x1c, 0xb4, 0x21, 0x0d, 0x21, 0x50, 0x39, 0xad, 0x18, 0x56, 0x1c, 0x65, 0x65, 0x10, 0xf1, 0x8e, 0x21, 0x04, 0x26, 0x23, 0x39, 0xf5, 0x8c, 0xae, 0xd1, 0x6f, 0x60, 0x79, 0x47, 0x3d, 0xe8, 0xc4,
0xde, 0x83, 0x91, 0xd7, 0x9c, 0xb3, 0xbc, 0xaf, 0x5b, 0x3c, 0x73, 0x94, 0xd5, 0x85, 0xbf, 0x94, 0x9c, 0x89, 0x92, 0x67, 0x11, 0x49, 0x92, 0x92, 0x56, 0x95, 0x57, 0x93, 0xe1, 0xb6, 0xa6, 0x87,
0x6d, 0xbc, 0x70, 0x0c, 0x90, 0x89, 0x41, 0x18, 0x74, 0x5a, 0x14, 0x2d, 0xeb, 0x3a, 0x3c, 0x17, 0x8a, 0x45, 0xf7, 0xe0, 0xe4, 0x84, 0x91, 0x15, 0x4d, 0xa2, 0x82, 0x97, 0xc2, 0xab, 0xcb, 0x82,
0x75, 0x46, 0x89, 0x3e, 0x82, 0x59, 0x51, 0x4e, 0xd7, 0xac, 0xc8, 0x9a, 0xba, 0xed, 0xb1, 0x2a, 0x48, 0xb7, 0xa4, 0x8f, 0x19, 0x2f, 0x05, 0x6e, 0x6a, 0xdd, 0x0e, 0xa0, 0x47, 0xb8, 0x24, 0x1b,
0x3e, 0x09, 0x8d, 0xd5, 0x86, 0xc7, 0x7d, 0xdd, 0xf6, 0x64, 0x21, 0xb9, 0xbd, 0x40, 0x5f, 0x60, 0x92, 0x66, 0x64, 0x99, 0xd1, 0xa8, 0xa2, 0xab, 0x9c, 0x32, 0xe1, 0x99, 0x32, 0xb7, 0x73, 0xc8,
0x49, 0xb7, 0xb4, 0xdc, 0xd0, 0xa7, 0x0d, 0xcb, 0x3a, 0xb6, 0xae, 0x18, 0xef, 0xf1, 0x99, 0xc8, 0x55, 0x34, 0x76, 0x0f, 0x4a, 0xcd, 0xf8, 0x5d, 0x80, 0xdf, 0x87, 0x4f, 0xf5, 0xef, 0x7f, 0x19,
0xbd, 0x3c, 0xe4, 0x0e, 0x36, 0xb1, 0x0e, 0xa4, 0x74, 0x6c, 0x07, 0x60, 0x2a, 0x7c, 0xea, 0x0f, 0x60, 0x6b, 0x35, 0x7a, 0x00, 0x47, 0x57, 0x88, 0xc4, 0xb6, 0x50, 0xba, 0xf6, 0xe0, 0xea, 0xa8,
0xed, 0x5f, 0x0a, 0xe8, 0x92, 0x46, 0x9f, 0xc1, 0x94, 0x1d, 0xb2, 0x7e, 0xd7, 0x0c, 0xdc, 0x85, 0x4c, 0x38, 0xdf, 0x16, 0x14, 0x37, 0xb5, 0x72, 0x07, 0xd0, 0x35, 0xd8, 0x9b, 0x8c, 0xb0, 0x28,
0x7f, 0x75, 0xd4, 0xc6, 0x4b, 0x77, 0x0d, 0x23, 0x0b, 0x49, 0xee, 0x05, 0x7a, 0x03, 0xfa, 0x76, 0x4d, 0xe4, 0xf0, 0xe7, 0xd8, 0xda, 0xc1, 0x51, 0x12, 0xf4, 0xc0, 0x94, 0x02, 0x17, 0x9c, 0xf9,
0x43, 0x79, 0x56, 0x16, 0x62, 0x48, 0x67, 0x44, 0xdb, 0xcb, 0xa8, 0x70, 0xdf, 0x81, 0x2a, 0x00, 0xdb, 0xec, 0x25, 0x1a, 0x4d, 0x17, 0xc3, 0xf1, 0xe8, 0xd9, 0x3d, 0x43, 0x2d, 0x68, 0x48, 0x66,
0x0b, 0xcc, 0xf4, 0xdb, 0xfd, 0x4d, 0x16, 0x25, 0x8f, 0xc1, 0x6d, 0xf4, 0xd5, 0x7a, 0x81, 0xce, 0x31, 0x1e, 0x4e, 0x5d, 0x23, 0xf8, 0x00, 0x5b, 0x3b, 0x7f, 0xd2, 0xe5, 0xdb, 0x23, 0xf3, 0xd4,
0xc1, 0x10, 0xce, 0xe3, 0x6d, 0x90, 0x58, 0x8a, 0x1b, 0x83, 0x71, 0x98, 0x27, 0x7a, 0x0d, 0xcb, 0xb6, 0x5a, 0xfb, 0x6d, 0x85, 0x7f, 0x7c, 0xf3, 0x7d, 0x30, 0xff, 0x9b, 0x79, 0x69, 0xc9, 0x8f,
0xf0, 0x2e, 0x49, 0x6e, 0xc2, 0xf4, 0x8e, 0xfc, 0x95, 0xf2, 0x0a, 0x2e, 0x27, 0x3b, 0xfe, 0xe4, 0x72, 0xf7, 0x13, 0x00, 0x00, 0xff, 0xff, 0x58, 0xe3, 0x1a, 0xb1, 0x38, 0x02, 0x00, 0x00,
0xfb, 0xd7, 0x96, 0x82, 0xae, 0xc0, 0x9a, 0xcc, 0x80, 0x44, 0x0f, 0x69, 0x60, 0xcd, 0xdc, 0x1f,
0xa0, 0xcb, 0x55, 0x9f, 0x5c, 0xeb, 0xf5, 0xd1, 0x2e, 0x86, 0xf3, 0x38, 0x1f, 0xcf, 0xc3, 0xfb,
0x67, 0x0d, 0xb6, 0x0d, 0xea, 0xff, 0x46, 0xf8, 0xa4, 0x89, 0xcb, 0xfc, 0xf0, 0x27, 0x00, 0x00,
0xff, 0xff, 0x30, 0x75, 0x6a, 0x03, 0xa9, 0x02, 0x00, 0x00,
} }

View File

@ -14,6 +14,8 @@ import (
"code.hackerspace.pl/q3k/topo/graph" "code.hackerspace.pl/q3k/topo/graph"
"code.hackerspace.pl/q3k/topo/state" "code.hackerspace.pl/q3k/topo/state"
pb "code.hackerspace.pl/q3k/topo/proto/control"
) )
type ServiceConfig struct { type ServiceConfig struct {
@ -34,9 +36,6 @@ func NewService(gr *graph.Graph, stm *state.StateManager, c ServiceConfig) *Serv
} }
} }
type TopologyStatus struct {
}
const topologyFragment = ` const topologyFragment = `
<script src="/assets/viz.js"></script> <script src="/assets/viz.js"></script>
<script> <script>
@ -59,58 +58,179 @@ const topologyFragment = `
<div id="graph" style="text-align: center;"></div> <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) Run() { func (s *Service) Run() {
assets := packr.NewBox("./assets") assets := packr.NewBox("./assets")
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets))) http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/debug/status", http.StatusSeeOther) http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
}) })
http.HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
http.HandleFunc("/debug/graphviz", func(w http.ResponseWriter, r *http.Request) { statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
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{} { statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
return &TopologyStatus{} return nil
}) })
glog.Infof("Debug listening on %s....", s.config.DebugListen) glog.Infof("Debug listening on %s....", s.config.DebugListen)
http.ListenAndServe(s.config.DebugListen, nil) http.ListenAndServe(s.config.DebugListen, 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 pb.SwitchPort_LINKSTATE_DOWN:
state = "DOWN"
case pb.SwitchPort_LINKSTATE_UP:
state = "UP"
}
mode := "INVALID"
switch po.Proto.PortMode {
case pb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
case pb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
case pb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
mode = "GENERIC"
case pb.SwitchPort_PORTMODE_ROUTED:
mode = "ROUTED"
case pb.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")
}

View File

@ -1,7 +1,16 @@
package state package state
import ( import (
"context"
"fmt"
"sync"
"google.golang.org/grpc"
cpb "code.hackerspace.pl/q3k/topo/proto/config"
pb "code.hackerspace.pl/q3k/topo/proto/control" pb "code.hackerspace.pl/q3k/topo/proto/control"
"code.hackerspace.pl/q3k/hspki"
) )
type SwitchportState struct { type SwitchportState struct {
@ -11,14 +20,57 @@ type SwitchportState struct {
type SwitchState struct { type SwitchState struct {
Name string Name string
Ports []*SwitchportState Ports []*SwitchportState
Stub pb.SwitchControlClient
}
func (s *SwitchState) Fetch(ctx context.Context) error {
req := pb.GetPortsRequest{}
res, err := s.Stub.GetPorts(ctx, &req)
if err != nil {
return fmt.Errorf("GetPorts: %v", err)
}
s.Ports = make([]*SwitchportState, len(res.Ports))
for i, port := range res.Ports {
s.Ports[i] = &SwitchportState{port}
}
return nil
} }
type StateManager struct { type StateManager struct {
Switches map[string]*SwitchState Switches map[string]*SwitchState
Conns map[string]*grpc.ClientConn
Mu sync.RWMutex
} }
func NewManager() *StateManager { func NewManager() *StateManager {
return &StateManager{ return &StateManager{
Switches: make(map[string]*SwitchState), Switches: make(map[string]*SwitchState),
Conns: make(map[string]*grpc.ClientConn),
} }
} }
func (s *StateManager) FetchState(ctx context.Context, conf *cpb.Config) error {
s.Mu.Lock()
defer s.Mu.Unlock()
for _, sw := range conf.Switch {
conn, ok := s.Conns[sw.ControlAddress]
if !ok {
var err error
conn, err = grpc.Dial(sw.ControlAddress, hspki.WithClientHSPKI())
if err != nil {
return fmt.Errorf("when connecting to switch %s: %v", sw.Name, err)
}
s.Conns[sw.ControlAddress] = conn
}
s.Switches[sw.Name] = &SwitchState{
Name: sw.Name,
Stub: pb.NewSwitchControlClient(conn),
}
err := s.Switches[sw.Name].Fetch(ctx)
if err != nil {
return fmt.Errorf("%q.Fetch: %v", sw.Name, err)
}
}
return nil
}