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/graph/graph.go b/dc/topo/graph/graph.go
new file mode 100644
index 0000000..4d31f39
--- /dev/null
+++ b/dc/topo/graph/graph.go
@@ -0,0 +1,213 @@
+package graph
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "github.com/digitalocean/go-netbox/netbox/client"
+ "github.com/digitalocean/go-netbox/netbox/client/dcim"
+ "github.com/digitalocean/go-netbox/netbox/models"
+ "github.com/golang/glog"
+
+ pb "code.hackerspace.pl/hscloud/dc/topo/proto"
+)
+
+type MachinePort struct {
+ Machine *Machine
+ OtherEnd *SwitchPort
+ Name string
+}
+
+type SwitchPort struct {
+ Switch *Switch
+ OtherEnd *MachinePort
+ Name string
+}
+
+type Machine struct {
+ Name string
+ Complete bool
+
+ Ports map[string]*MachinePort
+}
+
+type Switch struct {
+ Name string
+ Complete bool
+
+ Ports map[string]*SwitchPort
+}
+
+type Graph struct {
+ Switches map[string]*Switch
+ Machines map[string]*Machine
+ Mu sync.RWMutex
+}
+
+func New() *Graph {
+ return &Graph{
+ Switches: make(map[string]*Switch),
+ Machines: make(map[string]*Machine),
+ }
+}
+
+func (g *Graph) RemoveMachine(name string) {
+ glog.Infof("Removed machine %q", name)
+}
+
+func (g *Graph) RemoveSwitch(name string) {
+ glog.Infof("Removed switch %q", name)
+}
+
+func (g *Graph) LoadConfig(conf *pb.Config) error {
+ loadedMachines := make(map[string]bool)
+ loadedSwitches := make(map[string]bool)
+
+ // Add new machines and switches.
+ for _, machinepb := range conf.Machine {
+ if machinepb.Name == "" {
+ return fmt.Errorf("empty machine name")
+ }
+ if loadedMachines[machinepb.Name] {
+ return fmt.Errorf("duplicate machine name: %v", machinepb.Name)
+ }
+ machine, ok := g.Machines[machinepb.Name]
+ if !ok {
+ machine = &Machine{
+ Name: machinepb.Name,
+ Ports: make(map[string]*MachinePort),
+ }
+ for _, portpb := range machinepb.ManagedPort {
+ machine.Ports[portpb.Name] = &MachinePort{
+ Name: portpb.Name,
+ Machine: machine,
+ }
+ }
+ g.Machines[machinepb.Name] = machine
+ glog.Infof("Added machine %q with %d managed ports", machine.Name, len(machine.Ports))
+ }
+ machine.Complete = false
+ loadedMachines[machinepb.Name] = true
+ }
+ for _, switchpb := range conf.Switch {
+ if switchpb.Name == "" {
+ return fmt.Errorf("empty switch name")
+ }
+ if loadedSwitches[switchpb.Name] {
+ return fmt.Errorf("duplicate switch name: %v", switchpb.Name)
+ }
+ if loadedMachines[switchpb.Name] {
+ return fmt.Errorf("switch name collides with machine name: %v", switchpb.Name)
+ }
+ sw, ok := g.Switches[switchpb.Name]
+ if !ok {
+ sw = &Switch{
+ Name: switchpb.Name,
+ Ports: make(map[string]*SwitchPort),
+ }
+ for _, portpb := range switchpb.ManagedPort {
+ sw.Ports[portpb.Name] = &SwitchPort{
+ Name: portpb.Name,
+ Switch: sw,
+ }
+ }
+ g.Switches[switchpb.Name] = sw
+ glog.Infof("Added switch %q with %d managed ports", sw.Name, len(sw.Ports))
+ }
+ sw.Complete = false
+ loadedSwitches[switchpb.Name] = true
+ }
+
+ // Remove old machines and switches.
+ removeMachines := make(map[string]bool)
+ removeSwitches := make(map[string]bool)
+ for name, _ := range g.Switches {
+ if !loadedSwitches[name] {
+ removeSwitches[name] = true
+ }
+ }
+ for name, _ := range g.Machines {
+ if !loadedMachines[name] {
+ removeMachines[name] = true
+ }
+ }
+ for name, _ := range removeMachines {
+ g.RemoveMachine(name)
+ }
+ for name, _ := range removeSwitches {
+ g.RemoveSwitch(name)
+ }
+ return nil
+
+}
+
+func (g *Graph) FeedFromNetbox(ctx context.Context, nb *client.NetBox) error {
+ // Clear all connections first, because it's easier that way.
+ for _, machine := range g.Machines {
+ for _, port := range machine.Ports {
+ port.OtherEnd = nil
+ }
+ }
+ for _, sw := range g.Switches {
+ for _, port := range sw.Ports {
+ port.OtherEnd = nil
+ }
+ }
+
+ // Load new connections.
+ // Iterating over just machines should be fine if all connections are
+ // guaranteed to be between machines and switches (which is the model for
+ // now).
+ for _, machine := range g.Machines {
+ req := &dcim.DcimInterfaceConnectionsListParams{
+ Device: &machine.Name,
+ Context: ctx,
+ }
+ res, err := nb.Dcim.DcimInterfaceConnectionsList(req, nil)
+ if err != nil {
+ return fmt.Errorf("while querying information about %q: %v", machine.Name, err)
+ }
+ for _, connection := range res.Payload.Results {
+ ia := connection.InterfaceA
+ ib := connection.InterfaceB
+ if ia == nil || ib == nil {
+ continue
+ }
+
+ // Find which way this thing actually connects.
+ var thisSide, otherSide *models.PeerInterface
+ if ia.Device.Name == machine.Name {
+ thisSide = ia
+ otherSide = ib
+ } else if ib.Device.Name == machine.Name {
+ thisSide = ib
+ otherSide = ia
+ } else {
+ glog.Warning("Netbox connectivity for %q reported a link without it involced..?", machine.Name)
+ continue
+ }
+
+ thisPort, ok := machine.Ports[*thisSide.Name]
+ if !ok {
+ continue
+ }
+ sw, ok := g.Switches[otherSide.Device.Name]
+ if !ok {
+ glog.Warningf("Machine %q port %q is managed but connected to unknown device %q", machine.Name, thisPort.Name, otherSide.Device.Name)
+ continue
+ }
+ otherPort, ok := sw.Ports[*otherSide.Name]
+ if !ok {
+ glog.Warningf("Machine %q port %q is managed but connected to unmanaged port %q on %q", machine.Name, thisPort.Name, otherSide.Name, sw.Name)
+ continue
+ }
+
+ // Connect the two together!
+ thisPort.OtherEnd = otherPort
+ otherPort.OtherEnd = thisPort
+ glog.Infof("Connected: %s/%s <-> %s/%s", machine.Name, thisPort.Name, sw.Name, otherPort.Name)
+ }
+ }
+ return nil
+}