blob: 619aa05793ca47e84e001fc751823a58ab3861db [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 McIntyrea2020cb2014-12-23 16:56:40 +000027 ret[switch_name] = 'Found %d ports' % len(s.switch_get_port_names())
Steve McIntyrea2a8f792014-12-17 17:34:32 +000028 s.switch_disconnect()
29 del(s)
Steve McIntyrea2020cb2014-12-23 16:56:40 +000030 return ret
Steve McIntyrec68a18e2014-12-17 16:29:28 +000031
Steve McIntyre091e2ac2014-12-16 19:20:07 +000032 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000033 def perform_db_query(self, state, command, data):
Steve McIntyre091e2ac2014-12-16 19:20:07 +000034 print 'perform_db_query'
Steve McIntyref1c04f92014-12-16 18:23:15 +000035 print command
36 print data
37 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000038 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000039 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000040 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000041 ret = db.all_switches()
42 elif command == 'db.all_ports':
43 ret = db.all_ports()
44 elif command == 'db.all_vlans':
45 ret = db.all_vlans()
46 elif command == 'db.get_switch_by_id':
47 ret = db.get_switch_by_id(data['switch_id'])
48 elif command == 'db.get_switch_id_by_name':
49 ret = db.get_switch_id_by_name(data['name'])
50 elif command == 'db.get_switch_name_by_id':
51 ret = db.get_switch_name_by_id(data['switch_id'])
52 elif command == 'db.get_port_by_id':
53 ret = db.get_port_by_id(data['port_id'])
54 elif command == 'db.get_ports_by_switch':
55 ret = db.get_ports_by_switch(data['switch_id'])
56 elif command == 'db.get_port_by_switch_and_name':
57 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
58 elif command == 'db.get_current_vlan_id_by_port':
59 ret = db.get_current_vlan_id_by_port(data['port_id'])
60 elif command == 'db.get_base_vlan_id_by_port':
61 ret = db.get_base_vlan_id_by_port(data['port_id'])
62 elif command == 'db.get_ports_by_current_vlan':
63 ret = db.get_ports_by_current_vlan(data['vlan_id'])
64 elif command == 'db.get_ports_by_base_vlan':
65 ret = db.get_ports_by_base_vlan(data['vlan_id'])
66 elif command == 'db.get_vlan_by_id':
67 ret = db.get_vlan_by_id(data['vlan_id'])
68 elif command == 'db.get_vlan_id_by_name':
69 ret = db.get_vlan_id_by_name(data['name'])
70 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +000071 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000072 elif command == 'db.get_vlan_name_by_id':
73 ret = db.get_vlan_name_by_id(data['vlan_id'])
74 else:
Steve McIntyree749fef2014-12-17 16:35:45 +000075 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +000076
Steve McIntyre5da37fa2014-12-17 13:14:44 +000077 except InputError:
78 raise
79
Steve McIntyre798af842014-12-23 22:29:46 +000080# except:
81# raise InputError("Invalid input in query")
Steve McIntyref1c04f92014-12-16 18:23:15 +000082
83 return ret
84
Steve McIntyre53c7ad92014-12-16 19:21:13 +000085 # Simple helper wrapper for all the read-only daemon state queries
86 def perform_daemon_query(self, state, command, data):
87 print 'perform_daemon_query'
88 print command
89 print data
90 ret = {}
91 try:
92 if command == 'daemon.status':
93 # data ignored
94 ret['running'] = 'ok'
95 elif command == 'daemon.version':
96 # data ignored
97 ret['version'] = state.version
98 elif command == 'daemon.statistics':
99 ret['uptime'] = time.time() - state.starttime
Steve McIntyre88b79df2014-12-23 13:45:08 +0000100 elif command == 'daemon.probe_switches':
101 ret = self.probe_switches(state)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000102 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000103 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000104
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000105 except InputError:
106 raise
107
Steve McIntyre798af842014-12-23 22:29:46 +0000108# except:
109# raise InputError("Invalid input in query")
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000110
111 return ret
112
Steve McIntyree749fef2014-12-17 16:35:45 +0000113 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000114 def perform_db_update(self, state, command, data):
115 print 'perform_db_update'
116 print command
117 print data
118 ret = {}
119 db = state.db
120 try:
121 if command == 'db.create_switch':
122 ret = db.create_switch(data['name'])
123 elif command == 'db.create_port':
124 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000125 state.default_vlan_id,
126 state.default_vlan_id)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000127 elif command == 'db.delete_switch':
128 ret = db.delete_switch(data['switch_id'])
129 elif command == 'db.delete_port':
130 ret = db.delete_port(data['port_id'])
131 elif command == 'db.set_port_is_locked':
132 ret = db.set_port_is_locked(data['port_id'], data['is_locked'])
133 elif command == 'db.set_base_vlan':
134 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
135 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000136 raise InputError("Unknown db_update command \"%s\"" % command)
137
138 except InputError:
139 raise
140
Steve McIntyre798af842014-12-23 22:29:46 +0000141# except:
142# raise InputError("Invalid input in query")
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000143
144 return ret
145
146 # Helper wrapper for API functions that modify both database state
147 # and on-switch VLAN state
148 def perform_vlan_update(self, state, command, data):
149 print 'perform_vlan_update'
150 print command
151 print data
152 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000153
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000154 try:
155 # All of these are complex commands, so call helpers
156 # rather than inline the code here
157 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000158 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000159 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000160 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000161 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000162 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000163 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000164 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000165 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000166 ret = self.restore_base_vlan(state, int(data['port_id']))
167 elif command == 'api.auto_import_switch':
168 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000169 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000170 raise InputError("Unknown query command \"%s\"" % command)
171
Steve McIntyre3256b182014-12-19 15:38:15 +0000172 except InputError as e:
173 print 'got error %s' % e
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000174 raise
175
Steve McIntyre798af842014-12-23 22:29:46 +0000176# except:
177# raise InputError("Invalid input in query")
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000178
179 return ret
180
181
Steve McIntyre3256b182014-12-19 15:38:15 +0000182 # Complex call
183 # 1. create the VLAN in the DB
184 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000185 # a. Create the VLAN
186 # b. Add the VLAN to all trunk ports (if needed)
187 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000188 #
189 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000190 # fine. If things fail, we attempt to roll back by rebooting
191 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000192 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000193
194 print 'create_vlan'
195 db = state.db
196 config = state.config
197
Steve McIntyre3256b182014-12-19 15:38:15 +0000198 # 1. Database record first
199 try:
200 print 'Adding DB record first: name %s, tag %d, is_base_vlan %d' % (name, tag, is_base_vlan)
201 vlan_id = db.create_vlan(name, tag, is_base_vlan)
202 print 'Added VLAN tag %d, name %s to the database, created VLAN ID %d' % (tag, name, vlan_id)
203 except InputError:
204 print 'DB creation failed'
205 raise
206
Steve McIntyre153157d2014-12-19 18:05:20 +0000207 # Keep track of which switches we've configured, for later use
208 switches_done = []
209
Steve McIntyre3256b182014-12-19 15:38:15 +0000210 # 2. Now the switches
211 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000212 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000213 trunk_ports = []
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000214 switch_name = switch.name
Steve McIntyre3256b182014-12-19 15:38:15 +0000215 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000216 print 'Adding new VLAN to switch %s' % switch_name
Steve McIntyre3256b182014-12-19 15:38:15 +0000217 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000218 s = self.get_switch_driver(switch_name, config)
219 s.switch_connect(config.switches[switch_name].username,
220 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000221 config.switches[switch_name].enable_password)
Steve McIntyre3256b182014-12-19 15:38:15 +0000222
Steve McIntyre153157d2014-12-19 18:05:20 +0000223 # Mark this switch as one we've touched, for
224 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000225 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000226
Steve McIntyre3256b182014-12-19 15:38:15 +0000227 # 2a. Create the VLAN on the switch
228 s.vlan_create(tag)
229 s.vlan_set_name(tag, name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000230 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000231
232 # 2b. Do we need to worry about trunk ports on this switch?
233 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
234 print 'This switch does not need special trunk port handling'
235 else:
236 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000237 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyre3256b182014-12-19 15:38:15 +0000238 if trunk_ports is None:
239 print "But it has no trunk ports defined"
240 trunk_ports = []
241 else:
242 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
243
244 # Modify any trunk ports as needed
245 for port in trunk_ports:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000246 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000247 s.port_add_trunk_to_vlan(port, tag)
248
Steve McIntyre3256b182014-12-19 15:38:15 +0000249 # And now we're done with this switch
250 s.switch_disconnect()
251 del s
252
253 except IOError:
254 raise
255
256 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000257 # Bugger. Looks like one of the switch calls above
258 # failed. To undo the changes safely, we'll need to reset
259 # all the switches we managed to configure. This could
260 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000261 for switch_name in switches_done:
262 s = self.get_switch_driver(switch_name, config)
263 s.switch_connect(config.switches[switch_name].username,
264 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000265 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000266 s.switch_restart() # Will implicitly also close the connection
267 del s
268
Steve McIntyre3256b182014-12-19 15:38:15 +0000269 # Undo the database change
270 print 'Switch access failed. Deleting the new VLAN entry in the database'
271 db.delete_vlan(vlan_id)
272 raise
273
Steve McIntyre153157d2014-12-19 18:05:20 +0000274 # If we've got this far, things were successful. Save config
275 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000276 for switch_name in switches_done:
277 s = self.get_switch_driver(switch_name, config)
278 s.switch_connect(config.switches[switch_name].username,
279 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000280 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000281 s.switch_save_running_config()
282 s.switch_disconnect()
283 del s
284
Steve McIntyre3256b182014-12-19 15:38:15 +0000285 return vlan_id # If we're successful
286
Steve McIntyrefeb64522014-12-19 18:53:02 +0000287 # Complex call
288 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
289 # 2. Iterate through all switches:
290 # a. Remove the VLAN from all trunk ports (if needed)
291 # b. Remove the VLAN
292 # 3. If all went OK, save config on the switches
293 # 4. Remove the VLAN in the DB
294 #
295 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000296 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000297
298 print 'delete_vlan'
299 db = state.db
300 config = state.config
301
Steve McIntyrefeb64522014-12-19 18:53:02 +0000302 # 1. Check for database records first
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000303 print 'Checking for ports using VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000304 vlan = db.get_vlan_by_id(vlan_id)
305 if vlan is None:
306 raise InputError("VLAN ID %d does not exist" % vlan_id)
307 vlan_tag = vlan.tag
308 ports = db.get_ports_by_current_vlan(vlan_id)
309 if ports is not None:
310 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
311 (vlan_id, len(ports)))
312 ports = db.get_ports_by_base_vlan(vlan_id)
313 if ports is not None:
314 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
315 (vlan_id, len(ports)))
316
317 # Keep track of which switches we've configured, for later use
318 switches_done = []
319
320 # 2. Now the switches
321 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000322 for switch in db.all_switches():
323 switch_name = switch.name
Steve McIntyrefeb64522014-12-19 18:53:02 +0000324 trunk_ports = []
325 try:
326 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000327 s = self.get_switch_driver(switch_name, config)
328 s.switch_connect(config.switches[switch_name].username,
329 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000330 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000331
332 # Mark this switch as one we've touched, for
333 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000334 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000335
336 # 2a. Do we need to worry about trunk ports on this switch?
337 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
338 print 'This switch does not need special trunk port handling'
339 else:
340 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000341 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000342 if trunk_ports is None:
343 print "But it has no trunk ports defined"
344 trunk_ports = []
345 else:
346 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
347
348 # Modify any trunk ports as needed
349 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000350 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000351 print 'Removed VLAN tag %d from switch %s port %s' % (vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000352
353 # 2b. Remove the VLAN from the switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000354 print 'Removing VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000355 s.vlan_destroy(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000356 print 'Removed VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000357
358 # And now we're done with this switch
359 s.switch_disconnect()
360 del s
361
362 except IOError:
363 raise
364
365 except IOError:
366 # Bugger. Looks like one of the switch calls above
367 # failed. To undo the changes safely, we'll need to reset
368 # all the switches we managed to configure. This could
369 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000370 for switch_name in switches_done:
371 s = self.get_switch_driver(switch_name, config)
372 s.switch_connect(config.switches[switch_name].username,
373 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000374 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000375 s.switch_restart() # Will implicitly also close the connection
376 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000377 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000378
379 # 3. If we've got this far, things were successful. Save
380 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000381 for switch_name in switches_done:
382 s = self.get_switch_driver(switch_name, config)
383 s.switch_connect(config.switches[switch_name].username,
384 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000385 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000386 s.switch_save_running_config()
387 s.switch_disconnect()
388 del s
389
390 # 4. Finally, remove the VLAN in the DB
391 try:
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000392 print 'Removing DB record: VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000393 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000394 print 'Removed VLAN ID %d from the database OK' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000395 except InputError:
396 print 'DB deletion failed'
397 raise
398
399 return vlan_id # If we're successful
400
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000401 # Complex call, depends on existing state a lot
402 # 1. Check validity of inputs
403 # 2. Switch mode and other config on the port.
404 # a. If switching trunk->access, remove all trunk VLANs from it
405 # (if needed) and switch back to the base VLAN for the
406 # port. Next, switch to access mode.
407 # b. If switching access->trunk, switch back to the base VLAN
408 # for the port. Next, switch mode. Then add all trunk VLANs
409 # to it (if needed)
410 # 3. If all went OK, save config on the switch
411 # 4. Change details of the port in the DB
412 #
413 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000414 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000415
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000416 print 'set_port_mode'
417 db = state.db
418 config = state.config
419
420 # 1. Sanity-check inputs
421 if mode is not 'access' and mode is not 'trunk':
422 raise InputError("Port mode %s is not a valid option: try 'access' or 'trunk" % mode)
423 port = db.get_port_by_id(port_id)
424 if port is None:
425 raise InputError("Port ID %d does not exist" % port_id)
426 if mode == 'trunk' and port.is_trunk:
427 raise InputError("Port ID %d is already in trunk mode")
428 if mode == 'access' and not port.is_trunk:
429 raise InputError("Port ID %d is already in access mode")
430 base_vlan_tag = db.get_vlan_tag_by_id(port.base_vlan_id)
431
432 # Get the right driver
433 switch_name = db.get_switch_name_by_id(port.switch_id)
434 s = self.get_switch_driver(switch_name, config)
435
436 # 2. Now start configuring the switch
437 try:
438 s.switch_connect(config.switches[switch_name].username,
439 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000440 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000441 except:
442 print 'Failed to talk to switch %s!' % switch_name
443 raise
444
445 try:
446 if port.is_trunk:
447 # 2a. We're going from a trunk port to an access port
448 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
449 print 'This switch does not need special trunk port handling'
450 else:
451 print 'This switch needs special trunk port handling'
452 vlans = s.port_get_trunk_vlan_list(port.name)
453 if vlans is None:
454 print "But it has no VLANs defined on port %s" % port.name
455 vlans = []
456 else:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000457 print 'Found %d vlans that need dropping on port %s' % (len(vlans), port.name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000458
459 for vlan in vlans:
460 s.port_remove_trunk_from_vlan(port.name, vlan)
461 s.port_add_trunk_to_vlan(port.name, base_vlan_tag)
462 s.port_set_mode(port.name, "access")
463
464 else:
465 # 2b. We're going from an access port to a trunk port
466 s.port_set_access_vlan(port.name, base_vlan_tag)
467 s.port_set_mode(port.name, "trunk")
468 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
469 print 'This switch does not need special trunk port handling'
470 else:
471 vlans = db.all_vlans()
472 for vlan in vlans:
473 s.port_add_trunk_to_vlan(port.name, vlan.tag)
474
475 except IOError:
476 # Bugger. Looks like one of the switch calls above
477 # failed. To undo the changes safely, we'll need to reset
478 # all the config on this switch
479 s.switch_restart() # Will implicitly also close the connection
480 del s
481 raise
482
483 # 3. All seems to have worked so far!
484 s.switch_save_running_config()
485 s.switch_disconnect()
486 del s
487
488 # 4. And update the DB
489 db.set_port_mode(port_id, mode)
490
491 return port_id # If we're successful
492
493 # Complex call, updating both DB and switch state
494 # 1. Check validity of inputs
495 # 2. Update the port config on the switch
496 # 3. If all went OK, save config on the switch
497 # 4. Change details of the port in the DB
498 #
499 # If things fail, we attempt to roll back by rebooting the switch
500 def set_current_vlan(self, state, port_id, vlan_id):
501
502 print 'set_current_vlan'
503 db = state.db
504 config = state.config
505
506 # 1. Sanity checks!
507 port = db.get_port_by_id(port_id)
508 if port is None:
509 raise InputError("Port ID %d does not exist" % port_id)
510 if port.is_trunk:
511 raise InputError("Port ID %d is not an access port" % port_id)
512 if port.is_locked:
513 raise InputError("Port ID %d is locked" % port_id)
514
515 vlan = db.get_vlan_by_id(vlan_id)
516 if vlan is None:
517 raise InputError("VLAN ID %d does not exist" % vlan_id)
518
519 # Get the right driver
520 switch_name = db.get_switch_name_by_id(port.switch_id)
521 s = self.get_switch_driver(switch_name, config)
522
523 # 2. Now start configuring the switch
524 try:
525 s.switch_connect(config.switches[switch_name].username,
526 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000527 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000528 except:
529 print 'Failed to talk to switch %s!' % switch_name
530 raise
531
532 try:
533 s.port_set_access_vlan(port.name, vlan.tag)
534 except IOError:
535 # Bugger. Looks like one of the switch calls above
536 # failed. To undo the changes safely, we'll need to reset
537 # all the config on this switch
538 s.switch_restart() # Will implicitly also close the connection
539 del s
540 raise
541
542 # 3. All seems to have worked so far!
543 s.switch_save_running_config()
544 s.switch_disconnect()
545 del s
546
547 # 4. And update the DB
548 db.set_current_vlan(port_id, vlan_id)
549
550 return port_id # If we're successful
551
552 # Complex call, updating both DB and switch state
553 # 1. Check validity of input
554 # 2. Update the port config on the switch
555 # 3. If all went OK, save config on the switch
556 # 4. Change details of the port in the DB
557 #
558 # If things fail, we attempt to roll back by rebooting the switch
559 def restore_base_vlan(self, state, port_id):
560
561 print 'restore_base_vlan'
562 db = state.db
563 config = state.config
564
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000565 # 1. Sanity checks!
566 port = db.get_port_by_id(port_id)
567 if port is None:
568 raise InputError("Port ID %d does not exist" % port_id)
569 if port.is_trunk:
570 raise InputError("Port ID %d is not an access port" % port_id)
571 if port.is_locked:
572 raise InputError("Port ID %d is locked" % port_id)
573
574 # Bail out early if we're *already* on the base VLAN. This is
575 # not an error
576 if port.current_vlan_id == port.base_vlan_id:
577 return port_id
578
579 vlan = db.get_vlan_by_id(port.base_vlan_id)
580
581 # Get the right driver
582 switch_name = db.get_switch_name_by_id(port.switch_id)
583 s = self.get_switch_driver(switch_name, config)
584
585 # 2. Now start configuring the switch
586 try:
587 s.switch_connect(config.switches[switch_name].username,
588 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000589 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000590 except:
591 print 'Failed to talk to switch %s!' % switch_name
592 raise
593
594 try:
595 s.port_set_access_vlan(port.name, vlan.tag)
596 except IOError:
597 # Bugger. Looks like one of the switch calls above
598 # failed. To undo the changes safely, we'll need to reset
599 # all the config on this switch
600 s.switch_restart() # Will implicitly also close the connection
601 del s
602 raise
603
604 # 3. All seems to have worked so far!
605 s.switch_save_running_config()
606 s.switch_disconnect()
607 del s
608
609 # 4. And update the DB
610 db.set_current_vlan(port_id, port.base_vlan_id)
611
612 return port_id # If we're successful
613
614 # Complex call, updating both DB and switch state
615 # * Check validity of input
616 # * Read all the config from the switch (switch, ports, VLANs)
617 # * Create initial DB entries to match each of those
618 # * Merge VLANs across all switches
619 # * Set up ports appropriately
620 #
621 def auto_import_switch(self, state, switch_name):
622
623 print 'auto_import_switch'
624 db = state.db
625 config = state.config
626
627 # 1. Sanity checks!
628 switch_id = db.get_switch_id_by_name(switch_name)
629 if switch_id is not None:
630 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
631
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000632 if not switch_name in config.switches:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000633 raise InputError("Switch name %s not defined in config" % switch_name)
634
Steve McIntyrefc511242014-12-23 22:28:30 +0000635 print 'args look ok'
636
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000637 # 2. Now start reading config from the switch
638 try:
639 s = self.get_switch_driver(switch_name, config)
640 s.switch_connect(config.switches[switch_name].username,
641 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000642 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000643 except:
644 print 'Failed to talk to switch %s!' % switch_name
645 raise
646
647 # DON'T create the switch record in the DB first - we'll want
Steve McIntyrefc511242014-12-23 22:28:30 +0000648 # to create VLANs on *other* switches, and it's easier to do
649 # that before we've added our new switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000650
651 new_vlan_tags = []
652
653 # Grab the VLANs defined on this switch
654 vlan_tags = s.vlan_get_list()
Steve McIntyrefc511242014-12-23 22:28:30 +0000655
656 print ' found %d vlans on the switch' % len(vlan_tags)
657
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000658 for vlan_tag in vlan_tags:
659 vlan_name = s.vlan_get_name(vlan_tag)
660
661 # If a VLAN is already in the database, then that's easy -
662 # we can just ignore it. However, we have to check that
663 # there is not a different name for the existing VLAN tag
Steve McIntyreb1529072014-12-23 17:17:22 +0000664 # - bail out if so... UNLESS we're looking at the default
665 # VLAN
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000666 #
667 # If this VLAN tag is not already in the DB, we'll need to
668 # add it there and to all the other switches (and their
669 # trunk ports!) too.
Steve McIntyrefc511242014-12-23 22:28:30 +0000670 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
Steve McIntyreb1529072014-12-23 17:17:22 +0000671 if vlan_id is not state.default_vlan_id:
Steve McIntyreb1529072014-12-23 17:17:22 +0000672 if vlan_id is not None:
673 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
674 if vlan_name != vlan_db_name:
675 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 +0000676
Steve McIntyreb1529072014-12-23 17:17:22 +0000677 else:
678 # OK, we'll need to set up the new VLAN now. It can't
679 # be a base VLAN - switches don't have such a concept!
680 # Rather than create individually here, add to a
681 # list. *Only* once we've worked through all the
682 # switch's VLANs successfully (checking for existing
683 # records and possible clashes!) should we start
684 # committing changes
685 new_vlan_tags.append(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000686
687 # Now create the VLAN DB entries
688 for vlan_tag in new_vlan_tags:
689 vlan_name = s.vlan_get_name(vlan_tag)
690 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
691
692 # *Now* add this switch itself to the database, after we've
693 # worked on all the other switches
694 switch_id = db.create_switch(switch_name)
695
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000696 # And now the ports
697 trunk_ports = []
698 ports = s.switch_get_port_names()
Steve McIntyrefc511242014-12-23 22:28:30 +0000699 print ' found %d ports on the switch' % len(ports)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000700 for port_name in ports:
Steve McIntyrefc511242014-12-23 22:28:30 +0000701 print ' trying to import port %s' % port_name
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000702 port_id = None
703 port_mode = s.port_get_mode(port_name)
704 if port_mode == 'access':
705 # Access ports are easy - just create the port, and
706 # set both the current and base VLANs to the current
707 # VLAN on the switch. We'll end up changing this after
708 # import if needed.
709 port_vlan = s.port_get_access_vlan(port_name)
710 port_id = db.create_port(switch_id, port_name, port_vlan, port_vlan)
Steve McIntyrefc511242014-12-23 22:28:30 +0000711 print ' access port, VLAN %d' % int(port_vlan)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000712 # Nothing further needed
713 elif port_mode == 'trunk':
Steve McIntyrefc511242014-12-23 22:28:30 +0000714 print ' trunk port'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000715 # Trunk ports are a little more involved. First,
716 # create the port in the DB, setting the VLANs to the
717 # first VLAN found on the trunk port. This will *also*
718 # be in access mode by default, and unlocked.
719 port_vlans = s.port_get_trunk_vlan_list(port_name)
Steve McIntyrefc511242014-12-23 22:28:30 +0000720 print ' trunk port has VLANs:'
721 print port_vlans
722 if port_vlans is None or 'ALL' in port_vlans:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000723 port_vlans = (state.default_vlan_id,) # easy for our purposes
724 port_id = db.create_port(switch_id, port_name,
725 port_vlans[0], port_vlans[0])
726 # Append to a list of trunk ports that we will need to
727 # modify once we're done
728 trunk_ports.append(port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000729 else:
730 raise CriticalError("Unrecognised port port mode %s???" % port_mode)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000731
Steve McIntyrefc511242014-12-23 22:28:30 +0000732 print " Added port %s, got port ID %d" % (port_name, port_id)
733
734 # Now, on each trunk port on the switch, we need to add all
735 # the VLANs already configured across our system
736 if not 'TrunkWildCardVlans' in s.switch_get_capabilities():
737 for port_id in trunk_ports:
738 port = db.get_port_by_id(port_id)
739 db.set_port_mode(port_id, "trunk")
740
741 for vlan in db.all_vlans():
742 if vlan.vlan_id is not state.default_vlan_id:
743 print "Adding allowed VLAN tag %d to trunk port %s" % (vlan.tag, port.name)
744 s.port_add_trunk_to_vlan(port.name, vlan.tag)
745
746
747 # Done with this switch \o/
748 s.switch_save_running_config()
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000749 s.switch_disconnect()
750 del s
751
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000752 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000753 ret['switch_id'] = switch_id
754 ret['num_ports_added'] = len(ports)
755 ret['num_vlans_added'] = len(new_vlan_tags)
756 return ret # If we're successful