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