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 }