forked from hswaw/hscloud
214 lines
5.2 KiB
Go
214 lines
5.2 KiB
Go
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
|
|
}
|