blob: 911924f777c4c9b5899a525c68ebf1a5de60ec00 [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"
6
Serge Bazanski2c6c6d12018-10-06 11:55:04 +01007 "github.com/digitalocean/go-netbox/netbox/client"
8 "github.com/digitalocean/go-netbox/netbox/client/dcim"
Serge Bazanski9c5eac62018-10-06 12:13:57 +01009 "github.com/digitalocean/go-netbox/netbox/models"
Serge Bazanski28f49072018-10-06 11:31:18 +010010 "github.com/golang/glog"
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010011
12 confpb "code.hackerspace.pl/q3k/topo/proto/config"
Serge Bazanski28f49072018-10-06 11:31:18 +010013)
14
15type MachinePort struct {
Serge Bazanskic7be4a12018-10-06 13:18:05 +010016 Machine *Machine
Serge Bazanski28f49072018-10-06 11:31:18 +010017 OtherEnd *SwitchPort
18 Name string
19}
20
21type SwitchPort struct {
Serge Bazanskic7be4a12018-10-06 13:18:05 +010022 Switch *Switch
Serge Bazanski28f49072018-10-06 11:31:18 +010023 OtherEnd *MachinePort
24 Name string
25}
26
27type Machine struct {
28 Name string
29 Complete bool
30
31 Ports map[string]*MachinePort
32}
33
34type Switch struct {
35 Name string
36 Complete bool
37
38 Ports map[string]*SwitchPort
39}
40
41type Graph struct {
42 Switches map[string]*Switch
43 Machines map[string]*Machine
44}
45
46func New() *Graph {
47 return &Graph{
48 Switches: make(map[string]*Switch),
49 Machines: make(map[string]*Machine),
50 }
51}
52
53func (g *Graph) RemoveMachine(name string) {
54 glog.Infof("Removed machine %q", name)
55}
56
57func (g *Graph) RemoveSwitch(name string) {
58 glog.Infof("Removed switch %q", name)
59}
60
61func (g *Graph) LoadConfig(conf *confpb.Config) error {
62 loadedMachines := make(map[string]bool)
63 loadedSwitches := make(map[string]bool)
64
65 // Add new machines and switches.
66 for _, machinepb := range conf.Machine {
67 if machinepb.Name == "" {
68 return fmt.Errorf("empty machine name")
69 }
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010070 if loadedMachines[machinepb.Name] {
71 return fmt.Errorf("duplicate machine name: %v", machinepb.Name)
72 }
Serge Bazanski28f49072018-10-06 11:31:18 +010073 machine, ok := g.Machines[machinepb.Name]
74 if !ok {
75 machine = &Machine{
76 Name: machinepb.Name,
77 Ports: make(map[string]*MachinePort),
78 }
79 for _, portpb := range machinepb.ManagedPort {
80 machine.Ports[portpb.Name] = &MachinePort{
Serge Bazanskic7be4a12018-10-06 13:18:05 +010081 Name: portpb.Name,
82 Machine: machine,
Serge Bazanski28f49072018-10-06 11:31:18 +010083 }
84 }
85 g.Machines[machinepb.Name] = machine
86 glog.Infof("Added machine %q with %d managed ports", machine.Name, len(machine.Ports))
87 }
88 machine.Complete = false
89 loadedMachines[machinepb.Name] = true
90 }
91 for _, switchpb := range conf.Switch {
92 if switchpb.Name == "" {
93 return fmt.Errorf("empty switch name")
94 }
Serge Bazanski2c6c6d12018-10-06 11:55:04 +010095 if loadedSwitches[switchpb.Name] {
96 return fmt.Errorf("duplicate switch name: %v", switchpb.Name)
97 }
98 if loadedMachines[switchpb.Name] {
99 return fmt.Errorf("switch name collides with machine name: %v", switchpb.Name)
100 }
Serge Bazanski28f49072018-10-06 11:31:18 +0100101 sw, ok := g.Switches[switchpb.Name]
102 if !ok {
103 sw = &Switch{
104 Name: switchpb.Name,
105 Ports: make(map[string]*SwitchPort),
106 }
107 for _, portpb := range switchpb.ManagedPort {
108 sw.Ports[portpb.Name] = &SwitchPort{
Serge Bazanskic7be4a12018-10-06 13:18:05 +0100109 Name: portpb.Name,
110 Switch: sw,
Serge Bazanski28f49072018-10-06 11:31:18 +0100111 }
112 }
113 g.Switches[switchpb.Name] = sw
114 glog.Infof("Added switch %q with %d managed ports", sw.Name, len(sw.Ports))
115 }
116 sw.Complete = false
117 loadedSwitches[switchpb.Name] = true
118 }
119
120 // Remove old machines and switches.
121 removeMachines := make(map[string]bool)
122 removeSwitches := make(map[string]bool)
123 for name, _ := range g.Switches {
124 if !loadedSwitches[name] {
125 removeSwitches[name] = true
126 }
127 }
128 for name, _ := range g.Machines {
129 if !loadedMachines[name] {
130 removeMachines[name] = true
131 }
132 }
133 for name, _ := range removeMachines {
134 g.RemoveMachine(name)
135 }
136 for name, _ := range removeSwitches {
137 g.RemoveSwitch(name)
138 }
139 return nil
140
141}
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100142
143func (g *Graph) FeedFromNetbox(ctx context.Context, nb *client.NetBox) error {
Serge Bazanski9c5eac62018-10-06 12:13:57 +0100144 // Clear all connections first, because it's easier that way.
145 for _, machine := range g.Machines {
146 for _, port := range machine.Ports {
147 port.OtherEnd = nil
148 }
149 }
150 for _, sw := range g.Switches {
151 for _, port := range sw.Ports {
152 port.OtherEnd = nil
153 }
154 }
155
156 // Load new connections.
157 // Iterating over just machines should be fine if all connections are
158 // guaranteed to be between machines and switches (which is the model for
159 // now).
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100160 for _, machine := range g.Machines {
161 req := &dcim.DcimInterfaceConnectionsListParams{
162 Device: &machine.Name,
163 Context: ctx,
164 }
165 res, err := nb.Dcim.DcimInterfaceConnectionsList(req, nil)
166 if err != nil {
167 return fmt.Errorf("while querying information about %q: %v", machine.Name, err)
168 }
169 for _, connection := range res.Payload.Results {
Serge Bazanski9c5eac62018-10-06 12:13:57 +0100170 ia := connection.InterfaceA
171 ib := connection.InterfaceB
172 if ia == nil || ib == nil {
173 continue
174 }
175
176 // Find which way this thing actually connects.
177 var thisSide, otherSide *models.PeerInterface
178 if ia.Device.Name == machine.Name {
179 thisSide = ia
180 otherSide = ib
181 } else if ib.Device.Name == machine.Name {
182 thisSide = ib
183 otherSide = ia
184 } else {
185 glog.Warning("Netbox connectivity for %q reported a link without it involced..?", machine.Name)
186 continue
187 }
188
189 thisPort, ok := machine.Ports[*thisSide.Name]
190 if !ok {
191 continue
192 }
193 sw, ok := g.Switches[otherSide.Device.Name]
194 if !ok {
195 glog.Warningf("Machine %q port %q is managed but connected to unknown device %q", machine.Name, thisPort.Name, otherSide.Device.Name)
196 continue
197 }
198 otherPort, ok := sw.Ports[*otherSide.Name]
199 if !ok {
200 glog.Warningf("Machine %q port %q is managed but connected to unmanaged port %q on %q", machine.Name, thisPort.Name, otherSide.Name, sw.Name)
201 continue
202 }
203
204 // Connect the two together!
205 thisPort.OtherEnd = otherPort
206 otherPort.OtherEnd = thisPort
207 glog.Infof("Connected: %s/%s <-> %s/%s", machine.Name, thisPort.Name, sw.Name, otherPort.Name)
Serge Bazanski2c6c6d12018-10-06 11:55:04 +0100208 }
209 }
210 return nil
211}