blob: 4d31f39e1164130e38a444e0d90609245c305e0a [file] [log] [blame]
Serge Bazanski28f49072018-10-06 11:31:18 +01001package graph
2
3import (
Serge Bazanski2c6c6d12018-10-06 11:55:04 +01004 "context"
Serge Bazanski28f49072018-10-06 11:31:18 +01005 "fmt"
Serge Bazanski16e4ba22018-10-07 00:22:52 +01006 "sync"
Serge Bazanski28f49072018-10-06 11:31:18 +01007
Serge Bazanski2c6c6d12018-10-06 11:55:04 +01008 "github.com/digitalocean/go-netbox/netbox/client"
9 "github.com/digitalocean/go-netbox/netbox/client/dcim"
Serge Bazanski9c5eac62018-10-06 12:13:57 +010010 "github.com/digitalocean/go-netbox/netbox/models"
Serge Bazanski28f49072018-10-06 11:31:18 +010011 "github.com/golang/glog"
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010012
Sergiusz Bazanski61594bb2019-07-21 15:20:51 +020013 pb "code.hackerspace.pl/hscloud/dc/topo/proto"
Serge Bazanski28f49072018-10-06 11:31:18 +010014)
15
16type MachinePort struct {
Serge Bazanskic7be4a12018-10-06 13:18:05 +010017 Machine *Machine
Serge Bazanski28f49072018-10-06 11:31:18 +010018 OtherEnd *SwitchPort
19 Name string
20}
21
22type SwitchPort struct {
Serge Bazanskic7be4a12018-10-06 13:18:05 +010023 Switch *Switch
Serge Bazanski28f49072018-10-06 11:31:18 +010024 OtherEnd *MachinePort
25 Name string
26}
27
28type Machine struct {
29 Name string
30 Complete bool
31
32 Ports map[string]*MachinePort
33}
34
35type Switch struct {
36 Name string
37 Complete bool
38
39 Ports map[string]*SwitchPort
40}
41
42type Graph struct {
43 Switches map[string]*Switch
44 Machines map[string]*Machine
Serge Bazanski16e4ba22018-10-07 00:22:52 +010045 Mu sync.RWMutex
Serge Bazanski28f49072018-10-06 11:31:18 +010046}
47
48func New() *Graph {
49 return &Graph{
50 Switches: make(map[string]*Switch),
51 Machines: make(map[string]*Machine),
52 }
53}
54
55func (g *Graph) RemoveMachine(name string) {
56 glog.Infof("Removed machine %q", name)
57}
58
59func (g *Graph) RemoveSwitch(name string) {
60 glog.Infof("Removed switch %q", name)
61}
62
Serge Bazanski477ffe72018-10-25 05:36:18 -070063func (g *Graph) LoadConfig(conf *pb.Config) error {
Serge Bazanski28f49072018-10-06 11:31:18 +010064 loadedMachines := make(map[string]bool)
65 loadedSwitches := make(map[string]bool)
66
67 // Add new machines and switches.
68 for _, machinepb := range conf.Machine {
69 if machinepb.Name == "" {
70 return fmt.Errorf("empty machine name")
71 }
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010072 if loadedMachines[machinepb.Name] {
73 return fmt.Errorf("duplicate machine name: %v", machinepb.Name)
74 }
Serge Bazanski28f49072018-10-06 11:31:18 +010075 machine, ok := g.Machines[machinepb.Name]
76 if !ok {
77 machine = &Machine{
78 Name: machinepb.Name,
79 Ports: make(map[string]*MachinePort),
80 }
81 for _, portpb := range machinepb.ManagedPort {
82 machine.Ports[portpb.Name] = &MachinePort{
Serge Bazanskic7be4a12018-10-06 13:18:05 +010083 Name: portpb.Name,
84 Machine: machine,
Serge Bazanski28f49072018-10-06 11:31:18 +010085 }
86 }
87 g.Machines[machinepb.Name] = machine
88 glog.Infof("Added machine %q with %d managed ports", machine.Name, len(machine.Ports))
89 }
90 machine.Complete = false
91 loadedMachines[machinepb.Name] = true
92 }
93 for _, switchpb := range conf.Switch {
94 if switchpb.Name == "" {
95 return fmt.Errorf("empty switch name")
96 }
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010097 if loadedSwitches[switchpb.Name] {
98 return fmt.Errorf("duplicate switch name: %v", switchpb.Name)
99 }
100 if loadedMachines[switchpb.Name] {
101 return fmt.Errorf("switch name collides with machine name: %v", switchpb.Name)
102 }
Serge Bazanski28f49072018-10-06 11:31:18 +0100103 sw, ok := g.Switches[switchpb.Name]
104 if !ok {
105 sw = &Switch{
106 Name: switchpb.Name,
107 Ports: make(map[string]*SwitchPort),
108 }
109 for _, portpb := range switchpb.ManagedPort {
110 sw.Ports[portpb.Name] = &SwitchPort{
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100111 Name: portpb.Name,
112 Switch: sw,
Serge Bazanski28f49072018-10-06 11:31:18 +0100113 }
114 }
115 g.Switches[switchpb.Name] = sw
116 glog.Infof("Added switch %q with %d managed ports", sw.Name, len(sw.Ports))
117 }
118 sw.Complete = false
119 loadedSwitches[switchpb.Name] = true
120 }
121
122 // Remove old machines and switches.
123 removeMachines := make(map[string]bool)
124 removeSwitches := make(map[string]bool)
125 for name, _ := range g.Switches {
126 if !loadedSwitches[name] {
127 removeSwitches[name] = true
128 }
129 }
130 for name, _ := range g.Machines {
131 if !loadedMachines[name] {
132 removeMachines[name] = true
133 }
134 }
135 for name, _ := range removeMachines {
136 g.RemoveMachine(name)
137 }
138 for name, _ := range removeSwitches {
139 g.RemoveSwitch(name)
140 }
141 return nil
142
143}
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100144
145func (g *Graph) FeedFromNetbox(ctx context.Context, nb *client.NetBox) error {
Serge Bazanski9c5eac62018-10-06 12:13:57 +0100146 // Clear all connections first, because it's easier that way.
147 for _, machine := range g.Machines {
148 for _, port := range machine.Ports {
149 port.OtherEnd = nil
150 }
151 }
152 for _, sw := range g.Switches {
153 for _, port := range sw.Ports {
154 port.OtherEnd = nil
155 }
156 }
157
158 // Load new connections.
159 // Iterating over just machines should be fine if all connections are
160 // guaranteed to be between machines and switches (which is the model for
161 // now).
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100162 for _, machine := range g.Machines {
163 req := &dcim.DcimInterfaceConnectionsListParams{
164 Device: &machine.Name,
165 Context: ctx,
166 }
167 res, err := nb.Dcim.DcimInterfaceConnectionsList(req, nil)
168 if err != nil {
169 return fmt.Errorf("while querying information about %q: %v", machine.Name, err)
170 }
171 for _, connection := range res.Payload.Results {
Serge Bazanski9c5eac62018-10-06 12:13:57 +0100172 ia := connection.InterfaceA
173 ib := connection.InterfaceB
174 if ia == nil || ib == nil {
175 continue
176 }
177
178 // Find which way this thing actually connects.
179 var thisSide, otherSide *models.PeerInterface
180 if ia.Device.Name == machine.Name {
181 thisSide = ia
182 otherSide = ib
183 } else if ib.Device.Name == machine.Name {
184 thisSide = ib
185 otherSide = ia
186 } else {
187 glog.Warning("Netbox connectivity for %q reported a link without it involced..?", machine.Name)
188 continue
189 }
190
191 thisPort, ok := machine.Ports[*thisSide.Name]
192 if !ok {
193 continue
194 }
195 sw, ok := g.Switches[otherSide.Device.Name]
196 if !ok {
197 glog.Warningf("Machine %q port %q is managed but connected to unknown device %q", machine.Name, thisPort.Name, otherSide.Device.Name)
198 continue
199 }
200 otherPort, ok := sw.Ports[*otherSide.Name]
201 if !ok {
202 glog.Warningf("Machine %q port %q is managed but connected to unmanaged port %q on %q", machine.Name, thisPort.Name, otherSide.Name, sw.Name)
203 continue
204 }
205
206 // Connect the two together!
207 thisPort.OtherEnd = otherPort
208 otherPort.OtherEnd = thisPort
209 glog.Infof("Connected: %s/%s <-> %s/%s", machine.Name, thisPort.Name, sw.Name, otherPort.Name)
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100210 }
211 }
212 return nil
213}