blob: 45f74facf630dd62dc42e0fa9bed89dd2d5fe569 [file] [log] [blame]
Steve McIntyref1c04f92014-12-16 18:23:15 +00001import logging
Steve McIntyre53c7ad92014-12-16 19:21:13 +00002import time
Steve McIntyref1c04f92014-12-16 18:23:15 +00003from errors import CriticalError, InputError, ConfigError, SocketError
4
5class VlanUtil:
6 """VLANd utility functions"""
7
Steve McIntyre5f6f85e2014-12-22 16:42:28 +00008 def get_switch_driver(self, switch_name, config):
9 logging.debug("Trying to find a driver for %s" % switch_name)
10 driver = config.switches[switch_name].driver
Steve McIntyref1c04f92014-12-16 18:23:15 +000011 logging.debug("Driver: %s" % driver)
12 module = __import__("drivers.%s" % driver, fromlist=[driver])
13 class_ = getattr(module, driver)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000014 return class_(switch_name)
Steve McIntyref1c04f92014-12-16 18:23:15 +000015
Steve McIntyre519158e2014-12-23 13:44:44 +000016 def probe_switches(self, state):
17 config = state.config
Steve McIntyree8d80582014-12-23 16:53:39 +000018 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000019 for switch_name in sorted(config.switches):
20 print "Found switch %s:" % (switch_name)
Steve McIntyree8d80582014-12-23 16:53:39 +000021 print " Probing..."
Steve McIntyre519158e2014-12-23 13:44:44 +000022
Steve McIntyre4b4ab652014-12-22 17:19:09 +000023 s = self.get_switch_driver(switch_name, config)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000024 s.switch_connect(config.switches[switch_name].username,
25 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +000026 config.switches[switch_name].enable_password)
Steve McIntyre27d4b582014-12-23 22:51:00 +000027 ret[switch_name] = 'Found %d ports: ' % len(s.switch_get_port_names())
28 for name in s.switch_get_port_names():
29 ret[switch_name] += '%s ' % name
Steve McIntyrea2a8f792014-12-17 17:34:32 +000030 s.switch_disconnect()
31 del(s)
Steve McIntyrea2020cb2014-12-23 16:56:40 +000032 return ret
Steve McIntyrec68a18e2014-12-17 16:29:28 +000033
Steve McIntyre091e2ac2014-12-16 19:20:07 +000034 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000035 def perform_db_query(self, state, command, data):
Steve McIntyre091e2ac2014-12-16 19:20:07 +000036 print 'perform_db_query'
Steve McIntyref1c04f92014-12-16 18:23:15 +000037 print command
38 print data
39 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000040 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000041 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000042 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000043 ret = db.all_switches()
44 elif command == 'db.all_ports':
45 ret = db.all_ports()
46 elif command == 'db.all_vlans':
47 ret = db.all_vlans()
48 elif command == 'db.get_switch_by_id':
49 ret = db.get_switch_by_id(data['switch_id'])
50 elif command == 'db.get_switch_id_by_name':
51 ret = db.get_switch_id_by_name(data['name'])
52 elif command == 'db.get_switch_name_by_id':
53 ret = db.get_switch_name_by_id(data['switch_id'])
54 elif command == 'db.get_port_by_id':
55 ret = db.get_port_by_id(data['port_id'])
56 elif command == 'db.get_ports_by_switch':
57 ret = db.get_ports_by_switch(data['switch_id'])
58 elif command == 'db.get_port_by_switch_and_name':
59 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
60 elif command == 'db.get_current_vlan_id_by_port':
61 ret = db.get_current_vlan_id_by_port(data['port_id'])
62 elif command == 'db.get_base_vlan_id_by_port':
63 ret = db.get_base_vlan_id_by_port(data['port_id'])
64 elif command == 'db.get_ports_by_current_vlan':
65 ret = db.get_ports_by_current_vlan(data['vlan_id'])
66 elif command == 'db.get_ports_by_base_vlan':
67 ret = db.get_ports_by_base_vlan(data['vlan_id'])
68 elif command == 'db.get_vlan_by_id':
69 ret = db.get_vlan_by_id(data['vlan_id'])
Steve McIntyre65533d72015-01-23 18:01:17 +000070 elif command == 'db.get_vlan_tag_by_id':
71 ret = db.get_vlan_tag_by_id(data['vlan_id'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000072 elif command == 'db.get_vlan_id_by_name':
73 ret = db.get_vlan_id_by_name(data['name'])
74 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +000075 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000076 elif command == 'db.get_vlan_name_by_id':
77 ret = db.get_vlan_name_by_id(data['vlan_id'])
78 else:
Steve McIntyree749fef2014-12-17 16:35:45 +000079 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +000080
Steve McIntyre5da37fa2014-12-17 13:14:44 +000081 except InputError:
82 raise
83
Steve McIntyre798af842014-12-23 22:29:46 +000084# except:
85# raise InputError("Invalid input in query")
Steve McIntyref1c04f92014-12-16 18:23:15 +000086
87 return ret
88
Steve McIntyre53c7ad92014-12-16 19:21:13 +000089 # Simple helper wrapper for all the read-only daemon state queries
90 def perform_daemon_query(self, state, command, data):
91 print 'perform_daemon_query'
92 print command
93 print data
94 ret = {}
95 try:
96 if command == 'daemon.status':
97 # data ignored
98 ret['running'] = 'ok'
99 elif command == 'daemon.version':
100 # data ignored
101 ret['version'] = state.version
102 elif command == 'daemon.statistics':
103 ret['uptime'] = time.time() - state.starttime
Steve McIntyre88b79df2014-12-23 13:45:08 +0000104 elif command == 'daemon.probe_switches':
105 ret = self.probe_switches(state)
Steve McIntyre06fe6422015-01-23 17:55:43 +0000106 elif command == 'daemon.shutdown':
107 # data ignored
108 ret['shutdown'] = 'Shutting down'
109 state.running = False
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000110 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000111 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000112
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000113 except InputError:
114 raise
115
Steve McIntyre798af842014-12-23 22:29:46 +0000116# except:
117# raise InputError("Invalid input in query")
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000118
119 return ret
120
Steve McIntyree749fef2014-12-17 16:35:45 +0000121 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000122 def perform_db_update(self, state, command, data):
123 print 'perform_db_update'
124 print command
125 print data
126 ret = {}
127 db = state.db
128 try:
129 if command == 'db.create_switch':
130 ret = db.create_switch(data['name'])
131 elif command == 'db.create_port':
132 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000133 state.default_vlan_id,
134 state.default_vlan_id)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000135 elif command == 'db.delete_switch':
136 ret = db.delete_switch(data['switch_id'])
137 elif command == 'db.delete_port':
138 ret = db.delete_port(data['port_id'])
139 elif command == 'db.set_port_is_locked':
140 ret = db.set_port_is_locked(data['port_id'], data['is_locked'])
141 elif command == 'db.set_base_vlan':
142 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
143 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000144 raise InputError("Unknown db_update command \"%s\"" % command)
145
146 except InputError:
147 raise
148
Steve McIntyre798af842014-12-23 22:29:46 +0000149# except:
150# raise InputError("Invalid input in query")
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000151
152 return ret
153
154 # Helper wrapper for API functions that modify both database state
155 # and on-switch VLAN state
156 def perform_vlan_update(self, state, command, data):
157 print 'perform_vlan_update'
158 print command
159 print data
160 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000161
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000162 try:
163 # All of these are complex commands, so call helpers
164 # rather than inline the code here
165 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000166 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000167 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000168 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000169 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000170 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000171 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000172 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000173 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000174 ret = self.restore_base_vlan(state, int(data['port_id']))
175 elif command == 'api.auto_import_switch':
176 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000177 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000178 raise InputError("Unknown query command \"%s\"" % command)
179
Steve McIntyre3256b182014-12-19 15:38:15 +0000180 except InputError as e:
181 print 'got error %s' % e
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000182 raise
183
Steve McIntyre798af842014-12-23 22:29:46 +0000184# except:
185# raise InputError("Invalid input in query")
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000186
187 return ret
188
189
Steve McIntyre3256b182014-12-19 15:38:15 +0000190 # Complex call
191 # 1. create the VLAN in the DB
192 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000193 # a. Create the VLAN
194 # b. Add the VLAN to all trunk ports (if needed)
195 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000196 #
197 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000198 # fine. If things fail, we attempt to roll back by rebooting
199 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000200 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000201
202 print 'create_vlan'
203 db = state.db
204 config = state.config
205
Steve McIntyre3256b182014-12-19 15:38:15 +0000206 # 1. Database record first
207 try:
208 print 'Adding DB record first: name %s, tag %d, is_base_vlan %d' % (name, tag, is_base_vlan)
209 vlan_id = db.create_vlan(name, tag, is_base_vlan)
210 print 'Added VLAN tag %d, name %s to the database, created VLAN ID %d' % (tag, name, vlan_id)
211 except InputError:
212 print 'DB creation failed'
213 raise
214
Steve McIntyre153157d2014-12-19 18:05:20 +0000215 # Keep track of which switches we've configured, for later use
216 switches_done = []
217
Steve McIntyre3256b182014-12-19 15:38:15 +0000218 # 2. Now the switches
219 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000220 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000221 trunk_ports = []
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000222 switch_name = switch.name
Steve McIntyre3256b182014-12-19 15:38:15 +0000223 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000224 print 'Adding new VLAN to switch %s' % switch_name
Steve McIntyre3256b182014-12-19 15:38:15 +0000225 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000226 s = self.get_switch_driver(switch_name, config)
227 s.switch_connect(config.switches[switch_name].username,
228 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000229 config.switches[switch_name].enable_password)
Steve McIntyre3256b182014-12-19 15:38:15 +0000230
Steve McIntyre153157d2014-12-19 18:05:20 +0000231 # Mark this switch as one we've touched, for
232 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000233 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000234
Steve McIntyre3256b182014-12-19 15:38:15 +0000235 # 2a. Create the VLAN on the switch
236 s.vlan_create(tag)
237 s.vlan_set_name(tag, name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000238 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000239
240 # 2b. Do we need to worry about trunk ports on this switch?
241 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
242 print 'This switch does not need special trunk port handling'
243 else:
244 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000245 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyre3256b182014-12-19 15:38:15 +0000246 if trunk_ports is None:
247 print "But it has no trunk ports defined"
248 trunk_ports = []
249 else:
250 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
251
252 # Modify any trunk ports as needed
253 for port in trunk_ports:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000254 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000255 s.port_add_trunk_to_vlan(port, tag)
256
Steve McIntyre3256b182014-12-19 15:38:15 +0000257 # And now we're done with this switch
258 s.switch_disconnect()
259 del s
260
261 except IOError:
262 raise
263
264 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000265 # Bugger. Looks like one of the switch calls above
266 # failed. To undo the changes safely, we'll need to reset
267 # all the switches we managed to configure. This could
268 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000269 for switch_name in switches_done:
270 s = self.get_switch_driver(switch_name, config)
271 s.switch_connect(config.switches[switch_name].username,
272 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000273 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000274 s.switch_restart() # Will implicitly also close the connection
275 del s
276
Steve McIntyre3256b182014-12-19 15:38:15 +0000277 # Undo the database change
278 print 'Switch access failed. Deleting the new VLAN entry in the database'
279 db.delete_vlan(vlan_id)
280 raise
281
Steve McIntyre153157d2014-12-19 18:05:20 +0000282 # If we've got this far, things were successful. Save config
283 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000284 for switch_name in switches_done:
285 s = self.get_switch_driver(switch_name, config)
286 s.switch_connect(config.switches[switch_name].username,
287 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000288 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000289 s.switch_save_running_config()
290 s.switch_disconnect()
291 del s
292
Steve McIntyre3256b182014-12-19 15:38:15 +0000293 return vlan_id # If we're successful
294
Steve McIntyrefeb64522014-12-19 18:53:02 +0000295 # Complex call
296 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
297 # 2. Iterate through all switches:
298 # a. Remove the VLAN from all trunk ports (if needed)
299 # b. Remove the VLAN
300 # 3. If all went OK, save config on the switches
301 # 4. Remove the VLAN in the DB
302 #
303 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000304 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000305
306 print 'delete_vlan'
307 db = state.db
308 config = state.config
309
Steve McIntyrefeb64522014-12-19 18:53:02 +0000310 # 1. Check for database records first
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000311 print 'Checking for ports using VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000312 vlan = db.get_vlan_by_id(vlan_id)
313 if vlan is None:
314 raise InputError("VLAN ID %d does not exist" % vlan_id)
315 vlan_tag = vlan.tag
316 ports = db.get_ports_by_current_vlan(vlan_id)
317 if ports is not None:
318 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
319 (vlan_id, len(ports)))
320 ports = db.get_ports_by_base_vlan(vlan_id)
321 if ports is not None:
322 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
323 (vlan_id, len(ports)))
324
325 # Keep track of which switches we've configured, for later use
326 switches_done = []
327
328 # 2. Now the switches
329 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000330 for switch in db.all_switches():
331 switch_name = switch.name
Steve McIntyrefeb64522014-12-19 18:53:02 +0000332 trunk_ports = []
333 try:
334 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000335 s = self.get_switch_driver(switch_name, config)
336 s.switch_connect(config.switches[switch_name].username,
337 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000338 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000339
340 # Mark this switch as one we've touched, for
341 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000342 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000343
344 # 2a. Do we need to worry about trunk ports on this switch?
345 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
346 print 'This switch does not need special trunk port handling'
347 else:
348 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000349 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000350 if trunk_ports is None:
351 print "But it has no trunk ports defined"
352 trunk_ports = []
353 else:
354 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
355
356 # Modify any trunk ports as needed
357 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000358 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000359 print 'Removed VLAN tag %d from switch %s port %s' % (vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000360
361 # 2b. Remove the VLAN from the switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000362 print 'Removing VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000363 s.vlan_destroy(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000364 print 'Removed VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000365
366 # And now we're done with this switch
367 s.switch_disconnect()
368 del s
369
370 except IOError:
371 raise
372
373 except IOError:
374 # Bugger. Looks like one of the switch calls above
375 # failed. To undo the changes safely, we'll need to reset
376 # all the switches we managed to configure. This could
377 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000378 for switch_name in switches_done:
379 s = self.get_switch_driver(switch_name, config)
380 s.switch_connect(config.switches[switch_name].username,
381 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000382 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000383 s.switch_restart() # Will implicitly also close the connection
384 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000385 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000386
387 # 3. If we've got this far, things were successful. Save
388 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000389 for switch_name in switches_done:
390 s = self.get_switch_driver(switch_name, config)
391 s.switch_connect(config.switches[switch_name].username,
392 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000393 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000394 s.switch_save_running_config()
395 s.switch_disconnect()
396 del s
397
398 # 4. Finally, remove the VLAN in the DB
399 try:
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000400 print 'Removing DB record: VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000401 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000402 print 'Removed VLAN ID %d from the database OK' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000403 except InputError:
404 print 'DB deletion failed'
405 raise
406
407 return vlan_id # If we're successful
408
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000409 # Complex call, depends on existing state a lot
410 # 1. Check validity of inputs
411 # 2. Switch mode and other config on the port.
412 # a. If switching trunk->access, remove all trunk VLANs from it
413 # (if needed) and switch back to the base VLAN for the
414 # port. Next, switch to access mode.
415 # b. If switching access->trunk, switch back to the base VLAN
416 # for the port. Next, switch mode. Then add all trunk VLANs
417 # to it (if needed)
418 # 3. If all went OK, save config on the switch
419 # 4. Change details of the port in the DB
420 #
421 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000422 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000423
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000424 print 'set_port_mode'
425 db = state.db
426 config = state.config
427
428 # 1. Sanity-check inputs
Steve McIntyre5b0de002015-01-23 18:05:13 +0000429 if mode != 'access' and mode != 'trunk':
430 raise InputError("Port mode '%s' is not a valid option: try 'access' or 'trunk'" % mode)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000431 port = db.get_port_by_id(port_id)
432 if port is None:
433 raise InputError("Port ID %d does not exist" % port_id)
434 if mode == 'trunk' and port.is_trunk:
435 raise InputError("Port ID %d is already in trunk mode")
436 if mode == 'access' and not port.is_trunk:
437 raise InputError("Port ID %d is already in access mode")
438 base_vlan_tag = db.get_vlan_tag_by_id(port.base_vlan_id)
439
440 # Get the right driver
441 switch_name = db.get_switch_name_by_id(port.switch_id)
442 s = self.get_switch_driver(switch_name, config)
443
444 # 2. Now start configuring the switch
445 try:
446 s.switch_connect(config.switches[switch_name].username,
447 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000448 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000449 except:
450 print 'Failed to talk to switch %s!' % switch_name
451 raise
452
453 try:
454 if port.is_trunk:
455 # 2a. We're going from a trunk port to an access port
456 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
457 print 'This switch does not need special trunk port handling'
458 else:
459 print 'This switch needs special trunk port handling'
460 vlans = s.port_get_trunk_vlan_list(port.name)
461 if vlans is None:
462 print "But it has no VLANs defined on port %s" % port.name
463 vlans = []
464 else:
Steve McIntyre97f5e872015-01-23 18:07:05 +0000465 print 'Found %d vlans that may need dropping on port %s' % (len(vlans), port.name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000466
467 for vlan in vlans:
Steve McIntyre97f5e872015-01-23 18:07:05 +0000468 if vlan != state.config.vland.default_vlan_tag:
469 s.port_remove_trunk_from_vlan(port.name, vlan)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000470 s.port_add_trunk_to_vlan(port.name, base_vlan_tag)
471 s.port_set_mode(port.name, "access")
472
473 else:
474 # 2b. We're going from an access port to a trunk port
475 s.port_set_access_vlan(port.name, base_vlan_tag)
476 s.port_set_mode(port.name, "trunk")
477 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
478 print 'This switch does not need special trunk port handling'
479 else:
480 vlans = db.all_vlans()
481 for vlan in vlans:
482 s.port_add_trunk_to_vlan(port.name, vlan.tag)
483
484 except IOError:
485 # Bugger. Looks like one of the switch calls above
486 # failed. To undo the changes safely, we'll need to reset
487 # all the config on this switch
488 s.switch_restart() # Will implicitly also close the connection
489 del s
490 raise
491
492 # 3. All seems to have worked so far!
493 s.switch_save_running_config()
494 s.switch_disconnect()
495 del s
496
497 # 4. And update the DB
498 db.set_port_mode(port_id, mode)
499
500 return port_id # If we're successful
501
502 # Complex call, updating both DB and switch state
503 # 1. Check validity of inputs
504 # 2. Update the port config on the switch
505 # 3. If all went OK, save config on the switch
506 # 4. Change details of the port in the DB
507 #
508 # If things fail, we attempt to roll back by rebooting the switch
509 def set_current_vlan(self, state, port_id, vlan_id):
510
511 print 'set_current_vlan'
512 db = state.db
513 config = state.config
514
515 # 1. Sanity checks!
516 port = db.get_port_by_id(port_id)
517 if port is None:
518 raise InputError("Port ID %d does not exist" % port_id)
519 if port.is_trunk:
520 raise InputError("Port ID %d is not an access port" % port_id)
521 if port.is_locked:
522 raise InputError("Port ID %d is locked" % port_id)
523
524 vlan = db.get_vlan_by_id(vlan_id)
525 if vlan is None:
526 raise InputError("VLAN ID %d does not exist" % vlan_id)
527
528 # Get the right driver
529 switch_name = db.get_switch_name_by_id(port.switch_id)
530 s = self.get_switch_driver(switch_name, config)
531
532 # 2. Now start configuring the switch
533 try:
534 s.switch_connect(config.switches[switch_name].username,
535 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000536 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000537 except:
538 print 'Failed to talk to switch %s!' % switch_name
539 raise
540
541 try:
542 s.port_set_access_vlan(port.name, vlan.tag)
543 except IOError:
544 # Bugger. Looks like one of the switch calls above
545 # failed. To undo the changes safely, we'll need to reset
546 # all the config on this switch
547 s.switch_restart() # Will implicitly also close the connection
548 del s
549 raise
550
551 # 3. All seems to have worked so far!
552 s.switch_save_running_config()
553 s.switch_disconnect()
554 del s
555
556 # 4. And update the DB
557 db.set_current_vlan(port_id, vlan_id)
558
559 return port_id # If we're successful
560
561 # Complex call, updating both DB and switch state
562 # 1. Check validity of input
563 # 2. Update the port config on the switch
564 # 3. If all went OK, save config on the switch
565 # 4. Change details of the port in the DB
566 #
567 # If things fail, we attempt to roll back by rebooting the switch
568 def restore_base_vlan(self, state, port_id):
569
570 print 'restore_base_vlan'
571 db = state.db
572 config = state.config
573
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000574 # 1. Sanity checks!
575 port = db.get_port_by_id(port_id)
576 if port is None:
577 raise InputError("Port ID %d does not exist" % port_id)
578 if port.is_trunk:
579 raise InputError("Port ID %d is not an access port" % port_id)
580 if port.is_locked:
581 raise InputError("Port ID %d is locked" % port_id)
582
583 # Bail out early if we're *already* on the base VLAN. This is
584 # not an error
585 if port.current_vlan_id == port.base_vlan_id:
586 return port_id
587
588 vlan = db.get_vlan_by_id(port.base_vlan_id)
589
590 # Get the right driver
591 switch_name = db.get_switch_name_by_id(port.switch_id)
592 s = self.get_switch_driver(switch_name, config)
593
594 # 2. Now start configuring the switch
595 try:
596 s.switch_connect(config.switches[switch_name].username,
597 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000598 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000599 except:
600 print 'Failed to talk to switch %s!' % switch_name
601 raise
602
603 try:
604 s.port_set_access_vlan(port.name, vlan.tag)
605 except IOError:
606 # Bugger. Looks like one of the switch calls above
607 # failed. To undo the changes safely, we'll need to reset
608 # all the config on this switch
609 s.switch_restart() # Will implicitly also close the connection
610 del s
611 raise
612
613 # 3. All seems to have worked so far!
614 s.switch_save_running_config()
615 s.switch_disconnect()
616 del s
617
618 # 4. And update the DB
619 db.set_current_vlan(port_id, port.base_vlan_id)
620
621 return port_id # If we're successful
622
623 # Complex call, updating both DB and switch state
624 # * Check validity of input
625 # * Read all the config from the switch (switch, ports, VLANs)
626 # * Create initial DB entries to match each of those
627 # * Merge VLANs across all switches
628 # * Set up ports appropriately
629 #
630 def auto_import_switch(self, state, switch_name):
631
632 print 'auto_import_switch'
633 db = state.db
634 config = state.config
635
636 # 1. Sanity checks!
637 switch_id = db.get_switch_id_by_name(switch_name)
638 if switch_id is not None:
639 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
640
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000641 if not switch_name in config.switches:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000642 raise InputError("Switch name %s not defined in config" % switch_name)
643
Steve McIntyrefc511242014-12-23 22:28:30 +0000644 print 'args look ok'
645
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000646 # 2. Now start reading config from the switch
647 try:
648 s = self.get_switch_driver(switch_name, config)
649 s.switch_connect(config.switches[switch_name].username,
650 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000651 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000652 except:
653 print 'Failed to talk to switch %s!' % switch_name
654 raise
655
656 # DON'T create the switch record in the DB first - we'll want
Steve McIntyrefc511242014-12-23 22:28:30 +0000657 # to create VLANs on *other* switches, and it's easier to do
658 # that before we've added our new switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000659
660 new_vlan_tags = []
661
662 # Grab the VLANs defined on this switch
663 vlan_tags = s.vlan_get_list()
Steve McIntyrefc511242014-12-23 22:28:30 +0000664
665 print ' found %d vlans on the switch' % len(vlan_tags)
666
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000667 for vlan_tag in vlan_tags:
668 vlan_name = s.vlan_get_name(vlan_tag)
669
670 # If a VLAN is already in the database, then that's easy -
671 # we can just ignore it. However, we have to check that
672 # there is not a different name for the existing VLAN tag
Steve McIntyreb1529072014-12-23 17:17:22 +0000673 # - bail out if so... UNLESS we're looking at the default
674 # VLAN
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000675 #
676 # If this VLAN tag is not already in the DB, we'll need to
677 # add it there and to all the other switches (and their
678 # trunk ports!) too.
Steve McIntyrefc511242014-12-23 22:28:30 +0000679 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
Steve McIntyreb1529072014-12-23 17:17:22 +0000680 if vlan_id is not state.default_vlan_id:
Steve McIntyreb1529072014-12-23 17:17:22 +0000681 if vlan_id is not None:
682 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
683 if vlan_name != vlan_db_name:
684 raise InputError("Can't add VLAN tag %d (name %s) for this switch - VLAN tag %d already exists in the database, but with a different name (%s)" % (vlan_tag, vlan_name, vlan_tag, vlan_db_name))
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000685
Steve McIntyreb1529072014-12-23 17:17:22 +0000686 else:
687 # OK, we'll need to set up the new VLAN now. It can't
688 # be a base VLAN - switches don't have such a concept!
689 # Rather than create individually here, add to a
690 # list. *Only* once we've worked through all the
691 # switch's VLANs successfully (checking for existing
692 # records and possible clashes!) should we start
693 # committing changes
694 new_vlan_tags.append(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000695
696 # Now create the VLAN DB entries
697 for vlan_tag in new_vlan_tags:
698 vlan_name = s.vlan_get_name(vlan_tag)
699 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
700
701 # *Now* add this switch itself to the database, after we've
702 # worked on all the other switches
703 switch_id = db.create_switch(switch_name)
704
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000705 # And now the ports
706 trunk_ports = []
707 ports = s.switch_get_port_names()
Steve McIntyrefc511242014-12-23 22:28:30 +0000708 print ' found %d ports on the switch' % len(ports)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000709 for port_name in ports:
Steve McIntyrefc511242014-12-23 22:28:30 +0000710 print ' trying to import port %s' % port_name
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000711 port_id = None
712 port_mode = s.port_get_mode(port_name)
713 if port_mode == 'access':
714 # Access ports are easy - just create the port, and
715 # set both the current and base VLANs to the current
716 # VLAN on the switch. We'll end up changing this after
717 # import if needed.
718 port_vlan = s.port_get_access_vlan(port_name)
Steve McIntyre6f17b102014-12-24 02:18:08 +0000719 port_vlan_id = db.get_vlan_id_by_tag(port_vlan)
720 port_id = db.create_port(switch_id, port_name,
721 port_vlan_id, port_vlan_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000722 print ' access port, VLAN %d' % int(port_vlan)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000723 # Nothing further needed
724 elif port_mode == 'trunk':
Steve McIntyrefc511242014-12-23 22:28:30 +0000725 print ' trunk port'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000726 # Trunk ports are a little more involved. First,
727 # create the port in the DB, setting the VLANs to the
728 # first VLAN found on the trunk port. This will *also*
729 # be in access mode by default, and unlocked.
730 port_vlans = s.port_get_trunk_vlan_list(port_name)
Steve McIntyrefc511242014-12-23 22:28:30 +0000731 print ' trunk port has VLANs:'
732 print port_vlans
Steve McIntyre6f17b102014-12-24 02:18:08 +0000733 if port_vlans == [] or port_vlans is None or 'ALL' in port_vlans:
734 port_vlans = (state.config.vland.default_vlan_tag,) # easy for our purposes
735 port_vlan_id = db.get_vlan_id_by_tag(port_vlans[0])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000736 port_id = db.create_port(switch_id, port_name,
Steve McIntyre6f17b102014-12-24 02:18:08 +0000737 port_vlan_id, port_vlan_id)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000738 # Append to a list of trunk ports that we will need to
739 # modify once we're done
740 trunk_ports.append(port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000741 else:
742 raise CriticalError("Unrecognised port port mode %s???" % port_mode)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000743
Steve McIntyrefc511242014-12-23 22:28:30 +0000744 print " Added port %s, got port ID %d" % (port_name, port_id)
745
Steve McIntyre574e3342015-01-23 18:08:33 +0000746 db.set_port_mode(port_id, port_mode)
747
Steve McIntyrefc511242014-12-23 22:28:30 +0000748 # Now, on each trunk port on the switch, we need to add all
749 # the VLANs already configured across our system
750 if not 'TrunkWildCardVlans' in s.switch_get_capabilities():
751 for port_id in trunk_ports:
752 port = db.get_port_by_id(port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000753
754 for vlan in db.all_vlans():
755 if vlan.vlan_id is not state.default_vlan_id:
756 print "Adding allowed VLAN tag %d to trunk port %s" % (vlan.tag, port.name)
757 s.port_add_trunk_to_vlan(port.name, vlan.tag)
758
759
760 # Done with this switch \o/
761 s.switch_save_running_config()
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000762 s.switch_disconnect()
763 del s
764
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000765 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000766 ret['switch_id'] = switch_id
767 ret['num_ports_added'] = len(ports)
768 ret['num_vlans_added'] = len(new_vlan_tags)
769 return ret # If we're successful