blob: 5ce629f8def64457b8368b3cb745ce4db8100a82 [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 McIntyref8aac9e2014-12-23 14:06:05 +000027 ret[switch_name] = 'Found switch %s with %d ports' % (switch_name, len(s.switch_get_port_names()))
Steve McIntyrea2a8f792014-12-17 17:34:32 +000028 s.switch_disconnect()
29 del(s)
Steve McIntyrec68a18e2014-12-17 16:29:28 +000030
Steve McIntyre091e2ac2014-12-16 19:20:07 +000031 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000032 def perform_db_query(self, state, command, data):
Steve McIntyre091e2ac2014-12-16 19:20:07 +000033 print 'perform_db_query'
Steve McIntyref1c04f92014-12-16 18:23:15 +000034 print command
35 print data
36 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000037 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000038 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000039 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000040 ret = db.all_switches()
41 elif command == 'db.all_ports':
42 ret = db.all_ports()
43 elif command == 'db.all_vlans':
44 ret = db.all_vlans()
45 elif command == 'db.get_switch_by_id':
46 ret = db.get_switch_by_id(data['switch_id'])
47 elif command == 'db.get_switch_id_by_name':
48 ret = db.get_switch_id_by_name(data['name'])
49 elif command == 'db.get_switch_name_by_id':
50 ret = db.get_switch_name_by_id(data['switch_id'])
51 elif command == 'db.get_port_by_id':
52 ret = db.get_port_by_id(data['port_id'])
53 elif command == 'db.get_ports_by_switch':
54 ret = db.get_ports_by_switch(data['switch_id'])
55 elif command == 'db.get_port_by_switch_and_name':
56 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
57 elif command == 'db.get_current_vlan_id_by_port':
58 ret = db.get_current_vlan_id_by_port(data['port_id'])
59 elif command == 'db.get_base_vlan_id_by_port':
60 ret = db.get_base_vlan_id_by_port(data['port_id'])
61 elif command == 'db.get_ports_by_current_vlan':
62 ret = db.get_ports_by_current_vlan(data['vlan_id'])
63 elif command == 'db.get_ports_by_base_vlan':
64 ret = db.get_ports_by_base_vlan(data['vlan_id'])
65 elif command == 'db.get_vlan_by_id':
66 ret = db.get_vlan_by_id(data['vlan_id'])
67 elif command == 'db.get_vlan_id_by_name':
68 ret = db.get_vlan_id_by_name(data['name'])
69 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +000070 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000071 elif command == 'db.get_vlan_name_by_id':
72 ret = db.get_vlan_name_by_id(data['vlan_id'])
73 else:
Steve McIntyree749fef2014-12-17 16:35:45 +000074 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +000075
Steve McIntyre5da37fa2014-12-17 13:14:44 +000076 except InputError:
77 raise
78
Steve McIntyref1c04f92014-12-16 18:23:15 +000079 except:
80 raise InputError("Invalid input in query")
81
82 return ret
83
Steve McIntyre53c7ad92014-12-16 19:21:13 +000084 # Simple helper wrapper for all the read-only daemon state queries
85 def perform_daemon_query(self, state, command, data):
86 print 'perform_daemon_query'
87 print command
88 print data
89 ret = {}
90 try:
91 if command == 'daemon.status':
92 # data ignored
93 ret['running'] = 'ok'
94 elif command == 'daemon.version':
95 # data ignored
96 ret['version'] = state.version
97 elif command == 'daemon.statistics':
98 ret['uptime'] = time.time() - state.starttime
Steve McIntyre88b79df2014-12-23 13:45:08 +000099 elif command == 'daemon.probe_switches':
100 ret = self.probe_switches(state)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000101 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000102 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000103
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000104 except InputError:
105 raise
106
107 except:
108 raise InputError("Invalid input in query")
109
110 return ret
111
Steve McIntyree749fef2014-12-17 16:35:45 +0000112 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000113 def perform_db_update(self, state, command, data):
114 print 'perform_db_update'
115 print command
116 print data
117 ret = {}
118 db = state.db
119 try:
120 if command == 'db.create_switch':
121 ret = db.create_switch(data['name'])
122 elif command == 'db.create_port':
123 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000124 state.default_vlan_id,
125 state.default_vlan_id)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000126 elif command == 'db.delete_switch':
127 ret = db.delete_switch(data['switch_id'])
128 elif command == 'db.delete_port':
129 ret = db.delete_port(data['port_id'])
130 elif command == 'db.set_port_is_locked':
131 ret = db.set_port_is_locked(data['port_id'], data['is_locked'])
132 elif command == 'db.set_base_vlan':
133 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
134 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000135 raise InputError("Unknown db_update command \"%s\"" % command)
136
137 except InputError:
138 raise
139
140 except:
141 raise InputError("Invalid input in query")
142
143 return ret
144
145 # Helper wrapper for API functions that modify both database state
146 # and on-switch VLAN state
147 def perform_vlan_update(self, state, command, data):
148 print 'perform_vlan_update'
149 print command
150 print data
151 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000152
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000153 try:
154 # All of these are complex commands, so call helpers
155 # rather than inline the code here
156 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000157 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000158 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000159 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000160 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000161 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000162 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000163 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000164 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000165 ret = self.restore_base_vlan(state, int(data['port_id']))
166 elif command == 'api.auto_import_switch':
167 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000168 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000169 raise InputError("Unknown query command \"%s\"" % command)
170
Steve McIntyre3256b182014-12-19 15:38:15 +0000171 except InputError as e:
172 print 'got error %s' % e
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000173 raise
174
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000175 except:
176 raise InputError("Invalid input in query")
177
178 return ret
179
180
Steve McIntyre3256b182014-12-19 15:38:15 +0000181 # Complex call
182 # 1. create the VLAN in the DB
183 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000184 # a. Create the VLAN
185 # b. Add the VLAN to all trunk ports (if needed)
186 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000187 #
188 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000189 # fine. If things fail, we attempt to roll back by rebooting
190 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000191 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000192
193 print 'create_vlan'
194 db = state.db
195 config = state.config
196
Steve McIntyre3256b182014-12-19 15:38:15 +0000197 # 1. Database record first
198 try:
199 print 'Adding DB record first: name %s, tag %d, is_base_vlan %d' % (name, tag, is_base_vlan)
200 vlan_id = db.create_vlan(name, tag, is_base_vlan)
201 print 'Added VLAN tag %d, name %s to the database, created VLAN ID %d' % (tag, name, vlan_id)
202 except InputError:
203 print 'DB creation failed'
204 raise
205
Steve McIntyre153157d2014-12-19 18:05:20 +0000206 # Keep track of which switches we've configured, for later use
207 switches_done = []
208
Steve McIntyre3256b182014-12-19 15:38:15 +0000209 # 2. Now the switches
210 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000211 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000212 trunk_ports = []
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000213 switch_name = switch.name
Steve McIntyre3256b182014-12-19 15:38:15 +0000214 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000215 print 'Adding new VLAN to switch %s' % switch_name
Steve McIntyre3256b182014-12-19 15:38:15 +0000216 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000217 s = self.get_switch_driver(switch_name, config)
218 s.switch_connect(config.switches[switch_name].username,
219 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000220 config.switches[switch_name].enable_password)
Steve McIntyre3256b182014-12-19 15:38:15 +0000221
Steve McIntyre153157d2014-12-19 18:05:20 +0000222 # Mark this switch as one we've touched, for
223 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000224 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000225
Steve McIntyre3256b182014-12-19 15:38:15 +0000226 # 2a. Create the VLAN on the switch
227 s.vlan_create(tag)
228 s.vlan_set_name(tag, name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000229 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000230
231 # 2b. Do we need to worry about trunk ports on this switch?
232 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
233 print 'This switch does not need special trunk port handling'
234 else:
235 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000236 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyre3256b182014-12-19 15:38:15 +0000237 if trunk_ports is None:
238 print "But it has no trunk ports defined"
239 trunk_ports = []
240 else:
241 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
242
243 # Modify any trunk ports as needed
244 for port in trunk_ports:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000245 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000246 s.port_add_trunk_to_vlan(port, tag)
247
Steve McIntyre3256b182014-12-19 15:38:15 +0000248 # And now we're done with this switch
249 s.switch_disconnect()
250 del s
251
252 except IOError:
253 raise
254
255 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000256 # Bugger. Looks like one of the switch calls above
257 # failed. To undo the changes safely, we'll need to reset
258 # all the switches we managed to configure. This could
259 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000260 for switch_name in switches_done:
261 s = self.get_switch_driver(switch_name, config)
262 s.switch_connect(config.switches[switch_name].username,
263 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000264 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000265 s.switch_restart() # Will implicitly also close the connection
266 del s
267
Steve McIntyre3256b182014-12-19 15:38:15 +0000268 # Undo the database change
269 print 'Switch access failed. Deleting the new VLAN entry in the database'
270 db.delete_vlan(vlan_id)
271 raise
272
Steve McIntyre153157d2014-12-19 18:05:20 +0000273 # If we've got this far, things were successful. Save config
274 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000275 for switch_name in switches_done:
276 s = self.get_switch_driver(switch_name, config)
277 s.switch_connect(config.switches[switch_name].username,
278 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000279 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000280 s.switch_save_running_config()
281 s.switch_disconnect()
282 del s
283
Steve McIntyre3256b182014-12-19 15:38:15 +0000284 return vlan_id # If we're successful
285
Steve McIntyrefeb64522014-12-19 18:53:02 +0000286 # Complex call
287 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
288 # 2. Iterate through all switches:
289 # a. Remove the VLAN from all trunk ports (if needed)
290 # b. Remove the VLAN
291 # 3. If all went OK, save config on the switches
292 # 4. Remove the VLAN in the DB
293 #
294 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000295 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000296
297 print 'delete_vlan'
298 db = state.db
299 config = state.config
300
Steve McIntyrefeb64522014-12-19 18:53:02 +0000301 # 1. Check for database records first
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000302 print 'Checking for ports using VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000303 vlan = db.get_vlan_by_id(vlan_id)
304 if vlan is None:
305 raise InputError("VLAN ID %d does not exist" % vlan_id)
306 vlan_tag = vlan.tag
307 ports = db.get_ports_by_current_vlan(vlan_id)
308 if ports is not None:
309 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
310 (vlan_id, len(ports)))
311 ports = db.get_ports_by_base_vlan(vlan_id)
312 if ports is not None:
313 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
314 (vlan_id, len(ports)))
315
316 # Keep track of which switches we've configured, for later use
317 switches_done = []
318
319 # 2. Now the switches
320 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000321 for switch in db.all_switches():
322 switch_name = switch.name
Steve McIntyrefeb64522014-12-19 18:53:02 +0000323 trunk_ports = []
324 try:
325 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000326 s = self.get_switch_driver(switch_name, config)
327 s.switch_connect(config.switches[switch_name].username,
328 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000329 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000330
331 # Mark this switch as one we've touched, for
332 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000333 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000334
335 # 2a. Do we need to worry about trunk ports on this switch?
336 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
337 print 'This switch does not need special trunk port handling'
338 else:
339 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000340 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000341 if trunk_ports is None:
342 print "But it has no trunk ports defined"
343 trunk_ports = []
344 else:
345 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
346
347 # Modify any trunk ports as needed
348 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000349 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000350 print 'Removed VLAN tag %d from switch %s port %s' % (vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000351
352 # 2b. Remove the VLAN from the switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000353 print 'Removing VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000354 s.vlan_destroy(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000355 print 'Removed VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000356
357 # And now we're done with this switch
358 s.switch_disconnect()
359 del s
360
361 except IOError:
362 raise
363
364 except IOError:
365 # Bugger. Looks like one of the switch calls above
366 # failed. To undo the changes safely, we'll need to reset
367 # all the switches we managed to configure. This could
368 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000369 for switch_name in switches_done:
370 s = self.get_switch_driver(switch_name, config)
371 s.switch_connect(config.switches[switch_name].username,
372 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000373 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000374 s.switch_restart() # Will implicitly also close the connection
375 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000376 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000377
378 # 3. If we've got this far, things were successful. Save
379 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000380 for switch_name in switches_done:
381 s = self.get_switch_driver(switch_name, config)
382 s.switch_connect(config.switches[switch_name].username,
383 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000384 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000385 s.switch_save_running_config()
386 s.switch_disconnect()
387 del s
388
389 # 4. Finally, remove the VLAN in the DB
390 try:
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000391 print 'Removing DB record: VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000392 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000393 print 'Removed VLAN ID %d from the database OK' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000394 except InputError:
395 print 'DB deletion failed'
396 raise
397
398 return vlan_id # If we're successful
399
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000400 # Complex call, depends on existing state a lot
401 # 1. Check validity of inputs
402 # 2. Switch mode and other config on the port.
403 # a. If switching trunk->access, remove all trunk VLANs from it
404 # (if needed) and switch back to the base VLAN for the
405 # port. Next, switch to access mode.
406 # b. If switching access->trunk, switch back to the base VLAN
407 # for the port. Next, switch mode. Then add all trunk VLANs
408 # to it (if needed)
409 # 3. If all went OK, save config on the switch
410 # 4. Change details of the port in the DB
411 #
412 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000413 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000414
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000415 print 'set_port_mode'
416 db = state.db
417 config = state.config
418
419 # 1. Sanity-check inputs
420 if mode is not 'access' and mode is not 'trunk':
421 raise InputError("Port mode %s is not a valid option: try 'access' or 'trunk" % mode)
422 port = db.get_port_by_id(port_id)
423 if port is None:
424 raise InputError("Port ID %d does not exist" % port_id)
425 if mode == 'trunk' and port.is_trunk:
426 raise InputError("Port ID %d is already in trunk mode")
427 if mode == 'access' and not port.is_trunk:
428 raise InputError("Port ID %d is already in access mode")
429 base_vlan_tag = db.get_vlan_tag_by_id(port.base_vlan_id)
430
431 # Get the right driver
432 switch_name = db.get_switch_name_by_id(port.switch_id)
433 s = self.get_switch_driver(switch_name, config)
434
435 # 2. Now start configuring the switch
436 try:
437 s.switch_connect(config.switches[switch_name].username,
438 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000439 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000440 except:
441 print 'Failed to talk to switch %s!' % switch_name
442 raise
443
444 try:
445 if port.is_trunk:
446 # 2a. We're going from a trunk port to an access port
447 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
448 print 'This switch does not need special trunk port handling'
449 else:
450 print 'This switch needs special trunk port handling'
451 vlans = s.port_get_trunk_vlan_list(port.name)
452 if vlans is None:
453 print "But it has no VLANs defined on port %s" % port.name
454 vlans = []
455 else:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000456 print 'Found %d vlans that need dropping on port %s' % (len(vlans), port.name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000457
458 for vlan in vlans:
459 s.port_remove_trunk_from_vlan(port.name, vlan)
460 s.port_add_trunk_to_vlan(port.name, base_vlan_tag)
461 s.port_set_mode(port.name, "access")
462
463 else:
464 # 2b. We're going from an access port to a trunk port
465 s.port_set_access_vlan(port.name, base_vlan_tag)
466 s.port_set_mode(port.name, "trunk")
467 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
468 print 'This switch does not need special trunk port handling'
469 else:
470 vlans = db.all_vlans()
471 for vlan in vlans:
472 s.port_add_trunk_to_vlan(port.name, vlan.tag)
473
474 except IOError:
475 # Bugger. Looks like one of the switch calls above
476 # failed. To undo the changes safely, we'll need to reset
477 # all the config on this switch
478 s.switch_restart() # Will implicitly also close the connection
479 del s
480 raise
481
482 # 3. All seems to have worked so far!
483 s.switch_save_running_config()
484 s.switch_disconnect()
485 del s
486
487 # 4. And update the DB
488 db.set_port_mode(port_id, mode)
489
490 return port_id # If we're successful
491
492 # Complex call, updating both DB and switch state
493 # 1. Check validity of inputs
494 # 2. Update the port config on the switch
495 # 3. If all went OK, save config on the switch
496 # 4. Change details of the port in the DB
497 #
498 # If things fail, we attempt to roll back by rebooting the switch
499 def set_current_vlan(self, state, port_id, vlan_id):
500
501 print 'set_current_vlan'
502 db = state.db
503 config = state.config
504
505 # 1. Sanity checks!
506 port = db.get_port_by_id(port_id)
507 if port is None:
508 raise InputError("Port ID %d does not exist" % port_id)
509 if port.is_trunk:
510 raise InputError("Port ID %d is not an access port" % port_id)
511 if port.is_locked:
512 raise InputError("Port ID %d is locked" % port_id)
513
514 vlan = db.get_vlan_by_id(vlan_id)
515 if vlan is None:
516 raise InputError("VLAN ID %d does not exist" % vlan_id)
517
518 # Get the right driver
519 switch_name = db.get_switch_name_by_id(port.switch_id)
520 s = self.get_switch_driver(switch_name, config)
521
522 # 2. Now start configuring the switch
523 try:
524 s.switch_connect(config.switches[switch_name].username,
525 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000526 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000527 except:
528 print 'Failed to talk to switch %s!' % switch_name
529 raise
530
531 try:
532 s.port_set_access_vlan(port.name, vlan.tag)
533 except IOError:
534 # Bugger. Looks like one of the switch calls above
535 # failed. To undo the changes safely, we'll need to reset
536 # all the config on this switch
537 s.switch_restart() # Will implicitly also close the connection
538 del s
539 raise
540
541 # 3. All seems to have worked so far!
542 s.switch_save_running_config()
543 s.switch_disconnect()
544 del s
545
546 # 4. And update the DB
547 db.set_current_vlan(port_id, vlan_id)
548
549 return port_id # If we're successful
550
551 # Complex call, updating both DB and switch state
552 # 1. Check validity of input
553 # 2. Update the port config on the switch
554 # 3. If all went OK, save config on the switch
555 # 4. Change details of the port in the DB
556 #
557 # If things fail, we attempt to roll back by rebooting the switch
558 def restore_base_vlan(self, state, port_id):
559
560 print 'restore_base_vlan'
561 db = state.db
562 config = state.config
563
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000564 # 1. Sanity checks!
565 port = db.get_port_by_id(port_id)
566 if port is None:
567 raise InputError("Port ID %d does not exist" % port_id)
568 if port.is_trunk:
569 raise InputError("Port ID %d is not an access port" % port_id)
570 if port.is_locked:
571 raise InputError("Port ID %d is locked" % port_id)
572
573 # Bail out early if we're *already* on the base VLAN. This is
574 # not an error
575 if port.current_vlan_id == port.base_vlan_id:
576 return port_id
577
578 vlan = db.get_vlan_by_id(port.base_vlan_id)
579
580 # Get the right driver
581 switch_name = db.get_switch_name_by_id(port.switch_id)
582 s = self.get_switch_driver(switch_name, config)
583
584 # 2. Now start configuring the switch
585 try:
586 s.switch_connect(config.switches[switch_name].username,
587 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000588 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000589 except:
590 print 'Failed to talk to switch %s!' % switch_name
591 raise
592
593 try:
594 s.port_set_access_vlan(port.name, vlan.tag)
595 except IOError:
596 # Bugger. Looks like one of the switch calls above
597 # failed. To undo the changes safely, we'll need to reset
598 # all the config on this switch
599 s.switch_restart() # Will implicitly also close the connection
600 del s
601 raise
602
603 # 3. All seems to have worked so far!
604 s.switch_save_running_config()
605 s.switch_disconnect()
606 del s
607
608 # 4. And update the DB
609 db.set_current_vlan(port_id, port.base_vlan_id)
610
611 return port_id # If we're successful
612
613 # Complex call, updating both DB and switch state
614 # * Check validity of input
615 # * Read all the config from the switch (switch, ports, VLANs)
616 # * Create initial DB entries to match each of those
617 # * Merge VLANs across all switches
618 # * Set up ports appropriately
619 #
620 def auto_import_switch(self, state, switch_name):
621
622 print 'auto_import_switch'
623 db = state.db
624 config = state.config
625
626 # 1. Sanity checks!
627 switch_id = db.get_switch_id_by_name(switch_name)
628 if switch_id is not None:
629 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
630
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000631 if not switch_name in config.switches:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000632 raise InputError("Switch name %s not defined in config" % switch_name)
633
634 # 2. Now start reading config from the switch
635 try:
636 s = self.get_switch_driver(switch_name, config)
637 s.switch_connect(config.switches[switch_name].username,
638 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000639 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000640 except:
641 print 'Failed to talk to switch %s!' % switch_name
642 raise
643
644 # DON'T create the switch record in the DB first - we'll want
645 # to create VLANs on *other* switches, and it's easier to do that before we've added our new switch
646
647 new_vlan_tags = []
648
649 # Grab the VLANs defined on this switch
650 vlan_tags = s.vlan_get_list()
651 for vlan_tag in vlan_tags:
652 vlan_name = s.vlan_get_name(vlan_tag)
653
654 # If a VLAN is already in the database, then that's easy -
655 # we can just ignore it. However, we have to check that
656 # there is not a different name for the existing VLAN tag
657 # - bail out if so...
658 #
659 # If this VLAN tag is not already in the DB, we'll need to
660 # add it there and to all the other switches (and their
661 # trunk ports!) too.
662 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
663 if vlan_id is not None:
664 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
665 if vlan_name != vlan_db_name:
666 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))
667
668 else:
669 # OK, we'll need to set up the new VLAN now. It can't
670 # be a base VLAN - switches don't have such a concept!
671 # Rather than create individually here, add to a
672 # list. *Only* once we've worked through all the
673 # switch's VLANs successfully (checking for existing
674 # records and possible clashes!) should we start
675 # committing changes
676 new_vlan_tags.append(vlan_tag)
677
678 # Now create the VLAN DB entries
679 for vlan_tag in new_vlan_tags:
680 vlan_name = s.vlan_get_name(vlan_tag)
681 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
682
683 # *Now* add this switch itself to the database, after we've
684 # worked on all the other switches
685 switch_id = db.create_switch(switch_name)
686
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000687 # And now the ports
688 trunk_ports = []
689 ports = s.switch_get_port_names()
690 for port_name in ports:
691 port_id = None
692 port_mode = s.port_get_mode(port_name)
693 if port_mode == 'access':
694 # Access ports are easy - just create the port, and
695 # set both the current and base VLANs to the current
696 # VLAN on the switch. We'll end up changing this after
697 # import if needed.
698 port_vlan = s.port_get_access_vlan(port_name)
699 port_id = db.create_port(switch_id, port_name, port_vlan, port_vlan)
700 # Nothing further needed
701 elif port_mode == 'trunk':
702 # Trunk ports are a little more involved. First,
703 # create the port in the DB, setting the VLANs to the
704 # first VLAN found on the trunk port. This will *also*
705 # be in access mode by default, and unlocked.
706 port_vlans = s.port_get_trunk_vlan_list(port_name)
707 if port_vlans == 'ALL':
708 port_vlans = (state.default_vlan_id,) # easy for our purposes
709 port_id = db.create_port(switch_id, port_name,
710 port_vlans[0], port_vlans[0])
711 # Append to a list of trunk ports that we will need to
712 # modify once we're done
713 trunk_ports.append(port_id)
714
715 # Done with this switch in the main loop here
716 s.switch_disconnect()
717 del s
718
719 # Now on each trunk port we need to add all the VLANs across
720 # our system, so it's ready to go. Each time we call this, it
721 # will connect, disconnect, save config etc.
722 for port_id in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000723 self.set_port_mode(state, port_id, 'trunk')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000724
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000725 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000726 ret['switch_id'] = switch_id
727 ret['num_ports_added'] = len(ports)
728 ret['num_vlans_added'] = len(new_vlan_tags)
729 return ret # If we're successful