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'.
[[projects]]
branch = "master"
digest = "1:2af0bc306f77bef606f6f6033652f0de3cc80e77008f4da52197b4b0a6be5853"
name = "code.hackerspace.pl/q3k/hspki"
packages = ["."]
pruneopts = "UT"
revision = "624295da664fea57a1e3bd8cee639ae04f4c0ccf"
[[projects]]
digest = "1:d1665c44bd5db19aaee18d1b6233c99b0b9a986e8bccb24ef54747547a48027f"
name = "github.com/PuerkitoBio/purell"
@ -358,6 +366,7 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"code.hackerspace.pl/q3k/hspki",
"github.com/digitalocean/go-netbox/netbox",
"github.com/digitalocean/go-netbox/netbox/client",
"github.com/digitalocean/go-netbox/netbox/client/dcim",

View File

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

View File

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

View File

@ -46,6 +46,12 @@ func main() {
config := confpb.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()
err = gr.LoadConfig(&config)
if err != nil {
@ -59,8 +65,6 @@ func main() {
glog.Exitf("Initial netbox feed failed: %v", err)
}
stm := state.NewManager()
sconf := ServiceConfig{
DebugListen: flagDebugListen,
}

View File

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

View File

@ -14,6 +14,8 @@ import (
"code.hackerspace.pl/q3k/topo/graph"
"code.hackerspace.pl/q3k/topo/state"
pb "code.hackerspace.pl/q3k/topo/proto/control"
)
type ServiceConfig struct {
@ -34,9 +36,6 @@ func NewService(gr *graph.Graph, stm *state.StateManager, c ServiceConfig) *Serv
}
}
type TopologyStatus struct {
}
const topologyFragment = `
<script src="/assets/viz.js"></script>
<script>
@ -59,58 +58,179 @@ const topologyFragment = `
<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() {
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", s.httpHandleGraphviz)
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("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
return &TopologyStatus{}
return nil
})
glog.Infof("Debug listening on %s....", s.config.DebugListen)
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
import (
"context"
"fmt"
"sync"
"google.golang.org/grpc"
cpb "code.hackerspace.pl/q3k/topo/proto/config"
pb "code.hackerspace.pl/q3k/topo/proto/control"
"code.hackerspace.pl/q3k/hspki"
)
type SwitchportState struct {
@ -11,14 +20,57 @@ type SwitchportState struct {
type SwitchState struct {
Name string
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 {
Switches map[string]*SwitchState
Conns map[string]*grpc.ClientConn
Mu sync.RWMutex
}
func NewManager() *StateManager {
return &StateManager{
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
}