blob: 0198c13e7ac7fea25bdb83e49bbfc1f443f1dcf0 [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 McIntyrec68a18e2014-12-17 16:29:28 +000016 def get_all_switches(self, config):
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000017 for switch_name in sorted(config.switches):
18 print "Found switch %s:" % (switch_name)
Steve McIntyrea2a8f792014-12-17 17:34:32 +000019 print " Probing:"
Steve McIntyrec68a18e2014-12-17 16:29:28 +000020
Steve McIntyre4b4ab652014-12-22 17:19:09 +000021 s = self.get_switch_driver(switch_name, config)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000022 s.switch_connect(config.switches[switch_name].username,
23 config.switches[switch_name].password,
24 config.switches[switch_name].enablepassword)
Steve McIntyrea2a8f792014-12-17 17:34:32 +000025 print " Found details of switch:"
26 s._dump_list(s._systemdata)
27 print " Switch has %d ports:" % len(s.switch_get_port_names())
28 for port in s.switch_get_port_names():
29 print " %s" % port
30 if 0 == 1:
31 mode = s.port_get_mode(port)
32 if mode == "trunk":
33 print " port %s is in trunk mode, VLAN(s):" % port
34 vlans = s.port_get_trunk_vlan_list(port)
35 for vlan in vlans:
36 name = s.vlan_get_name(vlan)
37 print " %d (%s)" % (vlan, name)
38 else:
39 vlan = s.port_get_access_vlan(port)
40 name = s.vlan_get_name(vlan)
41 print " port %s is in access mode, VLAN %d (%s):" % (port, vlan, name)
42 s.switch_disconnect()
43 del(s)
Steve McIntyrec68a18e2014-12-17 16:29:28 +000044
Steve McIntyre091e2ac2014-12-16 19:20:07 +000045 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000046 def perform_db_query(self, state, command, data):
Steve McIntyre091e2ac2014-12-16 19:20:07 +000047 print 'perform_db_query'
Steve McIntyref1c04f92014-12-16 18:23:15 +000048 print command
49 print data
50 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000051 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000052 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000053 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000054 ret = db.all_switches()
55 elif command == 'db.all_ports':
56 ret = db.all_ports()
57 elif command == 'db.all_vlans':
58 ret = db.all_vlans()
59 elif command == 'db.get_switch_by_id':
60 ret = db.get_switch_by_id(data['switch_id'])
61 elif command == 'db.get_switch_id_by_name':
62 ret = db.get_switch_id_by_name(data['name'])
63 elif command == 'db.get_switch_name_by_id':
64 ret = db.get_switch_name_by_id(data['switch_id'])
65 elif command == 'db.get_port_by_id':
66 ret = db.get_port_by_id(data['port_id'])
67 elif command == 'db.get_ports_by_switch':
68 ret = db.get_ports_by_switch(data['switch_id'])
69 elif command == 'db.get_port_by_switch_and_name':
70 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
71 elif command == 'db.get_current_vlan_id_by_port':
72 ret = db.get_current_vlan_id_by_port(data['port_id'])
73 elif command == 'db.get_base_vlan_id_by_port':
74 ret = db.get_base_vlan_id_by_port(data['port_id'])
75 elif command == 'db.get_ports_by_current_vlan':
76 ret = db.get_ports_by_current_vlan(data['vlan_id'])
77 elif command == 'db.get_ports_by_base_vlan':
78 ret = db.get_ports_by_base_vlan(data['vlan_id'])
79 elif command == 'db.get_vlan_by_id':
80 ret = db.get_vlan_by_id(data['vlan_id'])
81 elif command == 'db.get_vlan_id_by_name':
82 ret = db.get_vlan_id_by_name(data['name'])
83 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +000084 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000085 elif command == 'db.get_vlan_name_by_id':
86 ret = db.get_vlan_name_by_id(data['vlan_id'])
87 else:
Steve McIntyree749fef2014-12-17 16:35:45 +000088 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +000089
Steve McIntyre5da37fa2014-12-17 13:14:44 +000090 except InputError:
91 raise
92
Steve McIntyref1c04f92014-12-16 18:23:15 +000093 except:
94 raise InputError("Invalid input in query")
95
96 return ret
97
Steve McIntyre53c7ad92014-12-16 19:21:13 +000098 # Simple helper wrapper for all the read-only daemon state queries
99 def perform_daemon_query(self, state, command, data):
100 print 'perform_daemon_query'
101 print command
102 print data
103 ret = {}
104 try:
105 if command == 'daemon.status':
106 # data ignored
107 ret['running'] = 'ok'
108 elif command == 'daemon.version':
109 # data ignored
110 ret['version'] = state.version
111 elif command == 'daemon.statistics':
112 ret['uptime'] = time.time() - state.starttime
113 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000114 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000115
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000116 except InputError:
117 raise
118
119 except:
120 raise InputError("Invalid input in query")
121
122 return ret
123
Steve McIntyree749fef2014-12-17 16:35:45 +0000124 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000125 def perform_db_update(self, state, command, data):
126 print 'perform_db_update'
127 print command
128 print data
129 ret = {}
130 db = state.db
131 try:
132 if command == 'db.create_switch':
133 ret = db.create_switch(data['name'])
134 elif command == 'db.create_port':
135 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000136 state.default_vlan_id,
137 state.default_vlan_id)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000138 elif command == 'db.delete_switch':
139 ret = db.delete_switch(data['switch_id'])
140 elif command == 'db.delete_port':
141 ret = db.delete_port(data['port_id'])
142 elif command == 'db.set_port_is_locked':
143 ret = db.set_port_is_locked(data['port_id'], data['is_locked'])
144 elif command == 'db.set_base_vlan':
145 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
146 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000147 raise InputError("Unknown db_update command \"%s\"" % command)
148
149 except InputError:
150 raise
151
152 except:
153 raise InputError("Invalid input in query")
154
155 return ret
156
157 # Helper wrapper for API functions that modify both database state
158 # and on-switch VLAN state
159 def perform_vlan_update(self, state, command, data):
160 print 'perform_vlan_update'
161 print command
162 print data
163 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000164
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000165 try:
166 # All of these are complex commands, so call helpers
167 # rather than inline the code here
168 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000169 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000170 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000171 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000172 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000173 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000174 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000175 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000176 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000177 ret = self.restore_base_vlan(state, int(data['port_id']))
178 elif command == 'api.auto_import_switch':
179 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000180 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000181 raise InputError("Unknown query command \"%s\"" % command)
182
Steve McIntyre3256b182014-12-19 15:38:15 +0000183 except InputError as e:
184 print 'got error %s' % e
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000185 raise
186
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000187 except:
188 raise InputError("Invalid input in query")
189
190 return ret
191
192
Steve McIntyre3256b182014-12-19 15:38:15 +0000193 # Complex call
194 # 1. create the VLAN in the DB
195 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000196 # a. Create the VLAN
197 # b. Add the VLAN to all trunk ports (if needed)
198 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000199 #
200 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000201 # fine. If things fail, we attempt to roll back by rebooting
202 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000203 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000204
205 print 'create_vlan'
206 db = state.db
207 config = state.config
208
Steve McIntyre3256b182014-12-19 15:38:15 +0000209 # 1. Database record first
210 try:
211 print 'Adding DB record first: name %s, tag %d, is_base_vlan %d' % (name, tag, is_base_vlan)
212 vlan_id = db.create_vlan(name, tag, is_base_vlan)
213 print 'Added VLAN tag %d, name %s to the database, created VLAN ID %d' % (tag, name, vlan_id)
214 except InputError:
215 print 'DB creation failed'
216 raise
217
Steve McIntyre153157d2014-12-19 18:05:20 +0000218 # Keep track of which switches we've configured, for later use
219 switches_done = []
220
Steve McIntyre3256b182014-12-19 15:38:15 +0000221 # 2. Now the switches
222 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000223 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000224 trunk_ports = []
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000225 switch_name = switch.name
Steve McIntyre3256b182014-12-19 15:38:15 +0000226 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000227 print 'Adding new VLAN to switch %s' % switch_name
Steve McIntyre3256b182014-12-19 15:38:15 +0000228 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000229 s = self.get_switch_driver(switch_name, config)
230 s.switch_connect(config.switches[switch_name].username,
231 config.switches[switch_name].password,
232 config.switches[switch_name].enablepassword)
Steve McIntyre3256b182014-12-19 15:38:15 +0000233
Steve McIntyre153157d2014-12-19 18:05:20 +0000234 # Mark this switch as one we've touched, for
235 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000236 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000237
Steve McIntyre3256b182014-12-19 15:38:15 +0000238 # 2a. Create the VLAN on the switch
239 s.vlan_create(tag)
240 s.vlan_set_name(tag, name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000241 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000242
243 # 2b. Do we need to worry about trunk ports on this switch?
244 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
245 print 'This switch does not need special trunk port handling'
246 else:
247 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000248 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyre3256b182014-12-19 15:38:15 +0000249 if trunk_ports is None:
250 print "But it has no trunk ports defined"
251 trunk_ports = []
252 else:
253 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
254
255 # Modify any trunk ports as needed
256 for port in trunk_ports:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000257 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000258 s.port_add_trunk_to_vlan(port, tag)
259
Steve McIntyre3256b182014-12-19 15:38:15 +0000260 # And now we're done with this switch
261 s.switch_disconnect()
262 del s
263
264 except IOError:
265 raise
266
267 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000268 # Bugger. Looks like one of the switch calls above
269 # failed. To undo the changes safely, we'll need to reset
270 # all the switches we managed to configure. This could
271 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000272 for switch_name in switches_done:
273 s = self.get_switch_driver(switch_name, config)
274 s.switch_connect(config.switches[switch_name].username,
275 config.switches[switch_name].password,
276 config.switches[switch_name].enablepassword)
Steve McIntyre153157d2014-12-19 18:05:20 +0000277 s.switch_restart() # Will implicitly also close the connection
278 del s
279
Steve McIntyre3256b182014-12-19 15:38:15 +0000280 # Undo the database change
281 print 'Switch access failed. Deleting the new VLAN entry in the database'
282 db.delete_vlan(vlan_id)
283 raise
284
Steve McIntyre153157d2014-12-19 18:05:20 +0000285 # If we've got this far, things were successful. Save config
286 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000287 for switch_name in switches_done:
288 s = self.get_switch_driver(switch_name, config)
289 s.switch_connect(config.switches[switch_name].username,
290 config.switches[switch_name].password,
291 config.switches[switch_name].enablepassword)
Steve McIntyre153157d2014-12-19 18:05:20 +0000292 s.switch_save_running_config()
293 s.switch_disconnect()
294 del s
295
Steve McIntyre3256b182014-12-19 15:38:15 +0000296 return vlan_id # If we're successful
297
Steve McIntyrefeb64522014-12-19 18:53:02 +0000298 # Complex call
299 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
300 # 2. Iterate through all switches:
301 # a. Remove the VLAN from all trunk ports (if needed)
302 # b. Remove the VLAN
303 # 3. If all went OK, save config on the switches
304 # 4. Remove the VLAN in the DB
305 #
306 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000307 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000308
309 print 'delete_vlan'
310 db = state.db
311 config = state.config
312
Steve McIntyrefeb64522014-12-19 18:53:02 +0000313 # 1. Check for database records first
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000314 print 'Checking for ports using VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000315 vlan = db.get_vlan_by_id(vlan_id)
316 if vlan is None:
317 raise InputError("VLAN ID %d does not exist" % vlan_id)
318 vlan_tag = vlan.tag
319 ports = db.get_ports_by_current_vlan(vlan_id)
320 if ports is not None:
321 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
322 (vlan_id, len(ports)))
323 ports = db.get_ports_by_base_vlan(vlan_id)
324 if ports is not None:
325 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
326 (vlan_id, len(ports)))
327
328 # Keep track of which switches we've configured, for later use
329 switches_done = []
330
331 # 2. Now the switches
332 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000333 for switch in db.all_switches():
334 switch_name = switch.name
Steve McIntyrefeb64522014-12-19 18:53:02 +0000335 trunk_ports = []
336 try:
337 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000338 s = self.get_switch_driver(switch_name, config)
339 s.switch_connect(config.switches[switch_name].username,
340 config.switches[switch_name].password,
341 config.switches[switch_name].enablepassword)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000342
343 # Mark this switch as one we've touched, for
344 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000345 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000346
347 # 2a. Do we need to worry about trunk ports on this switch?
348 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
349 print 'This switch does not need special trunk port handling'
350 else:
351 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000352 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000353 if trunk_ports is None:
354 print "But it has no trunk ports defined"
355 trunk_ports = []
356 else:
357 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
358
359 # Modify any trunk ports as needed
360 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000361 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000362 print 'Removed VLAN tag %d from switch %s port %s' % (vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000363
364 # 2b. Remove the VLAN from the switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000365 print 'Removing VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000366 s.vlan_destroy(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000367 print 'Removed VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000368
369 # And now we're done with this switch
370 s.switch_disconnect()
371 del s
372
373 except IOError:
374 raise
375
376 except IOError:
377 # Bugger. Looks like one of the switch calls above
378 # failed. To undo the changes safely, we'll need to reset
379 # all the switches we managed to configure. This could
380 # take some time!
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,
385 config.switches[switch_name].enablepassword)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000386 s.switch_restart() # Will implicitly also close the connection
387 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000388 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000389
390 # 3. If we've got this far, things were successful. Save
391 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000392 for switch_name in switches_done:
393 s = self.get_switch_driver(switch_name, config)
394 s.switch_connect(config.switches[switch_name].username,
395 config.switches[switch_name].password,
396 config.switches[switch_name].enablepassword)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000397 s.switch_save_running_config()
398 s.switch_disconnect()
399 del s
400
401 # 4. Finally, remove the VLAN in the DB
402 try:
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000403 print 'Removing DB record: VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000404 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000405 print 'Removed VLAN ID %d from the database OK' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000406 except InputError:
407 print 'DB deletion failed'
408 raise
409
410 return vlan_id # If we're successful
411
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000412 # Complex call, depends on existing state a lot
413 # 1. Check validity of inputs
414 # 2. Switch mode and other config on the port.
415 # a. If switching trunk->access, remove all trunk VLANs from it
416 # (if needed) and switch back to the base VLAN for the
417 # port. Next, switch to access mode.
418 # b. If switching access->trunk, switch back to the base VLAN
419 # for the port. Next, switch mode. Then add all trunk VLANs
420 # to it (if needed)
421 # 3. If all went OK, save config on the switch
422 # 4. Change details of the port in the DB
423 #
424 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000425 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000426
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000427 print 'set_port_mode'
428 db = state.db
429 config = state.config
430
431 # 1. Sanity-check inputs
432 if mode is not 'access' and mode is not 'trunk':
433 raise InputError("Port mode %s is not a valid option: try 'access' or 'trunk" % mode)
434 port = db.get_port_by_id(port_id)
435 if port is None:
436 raise InputError("Port ID %d does not exist" % port_id)
437 if mode == 'trunk' and port.is_trunk:
438 raise InputError("Port ID %d is already in trunk mode")
439 if mode == 'access' and not port.is_trunk:
440 raise InputError("Port ID %d is already in access mode")
441 base_vlan_tag = db.get_vlan_tag_by_id(port.base_vlan_id)
442
443 # Get the right driver
444 switch_name = db.get_switch_name_by_id(port.switch_id)
445 s = self.get_switch_driver(switch_name, config)
446
447 # 2. Now start configuring the switch
448 try:
449 s.switch_connect(config.switches[switch_name].username,
450 config.switches[switch_name].password,
451 config.switches[switch_name].enablepassword)
452 except:
453 print 'Failed to talk to switch %s!' % switch_name
454 raise
455
456 try:
457 if port.is_trunk:
458 # 2a. We're going from a trunk port to an access port
459 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
460 print 'This switch does not need special trunk port handling'
461 else:
462 print 'This switch needs special trunk port handling'
463 vlans = s.port_get_trunk_vlan_list(port.name)
464 if vlans is None:
465 print "But it has no VLANs defined on port %s" % port.name
466 vlans = []
467 else:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000468 print 'Found %d vlans that need dropping on port %s' % (len(vlans), port.name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000469
470 for vlan in vlans:
471 s.port_remove_trunk_from_vlan(port.name, vlan)
472 s.port_add_trunk_to_vlan(port.name, base_vlan_tag)
473 s.port_set_mode(port.name, "access")
474
475 else:
476 # 2b. We're going from an access port to a trunk port
477 s.port_set_access_vlan(port.name, base_vlan_tag)
478 s.port_set_mode(port.name, "trunk")
479 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
480 print 'This switch does not need special trunk port handling'
481 else:
482 vlans = db.all_vlans()
483 for vlan in vlans:
484 s.port_add_trunk_to_vlan(port.name, vlan.tag)
485
486 except IOError:
487 # Bugger. Looks like one of the switch calls above
488 # failed. To undo the changes safely, we'll need to reset
489 # all the config on this switch
490 s.switch_restart() # Will implicitly also close the connection
491 del s
492 raise
493
494 # 3. All seems to have worked so far!
495 s.switch_save_running_config()
496 s.switch_disconnect()
497 del s
498
499 # 4. And update the DB
500 db.set_port_mode(port_id, mode)
501
502 return port_id # If we're successful
503
504 # Complex call, updating both DB and switch state
505 # 1. Check validity of inputs
506 # 2. Update the port config on the switch
507 # 3. If all went OK, save config on the switch
508 # 4. Change details of the port in the DB
509 #
510 # If things fail, we attempt to roll back by rebooting the switch
511 def set_current_vlan(self, state, port_id, vlan_id):
512
513 print 'set_current_vlan'
514 db = state.db
515 config = state.config
516
517 # 1. Sanity checks!
518 port = db.get_port_by_id(port_id)
519 if port is None:
520 raise InputError("Port ID %d does not exist" % port_id)
521 if port.is_trunk:
522 raise InputError("Port ID %d is not an access port" % port_id)
523 if port.is_locked:
524 raise InputError("Port ID %d is locked" % port_id)
525
526 vlan = db.get_vlan_by_id(vlan_id)
527 if vlan is None:
528 raise InputError("VLAN ID %d does not exist" % vlan_id)
529
530 # Get the right driver
531 switch_name = db.get_switch_name_by_id(port.switch_id)
532 s = self.get_switch_driver(switch_name, config)
533
534 # 2. Now start configuring the switch
535 try:
536 s.switch_connect(config.switches[switch_name].username,
537 config.switches[switch_name].password,
538 config.switches[switch_name].enablepassword)
539 except:
540 print 'Failed to talk to switch %s!' % switch_name
541 raise
542
543 try:
544 s.port_set_access_vlan(port.name, vlan.tag)
545 except IOError:
546 # Bugger. Looks like one of the switch calls above
547 # failed. To undo the changes safely, we'll need to reset
548 # all the config on this switch
549 s.switch_restart() # Will implicitly also close the connection
550 del s
551 raise
552
553 # 3. All seems to have worked so far!
554 s.switch_save_running_config()
555 s.switch_disconnect()
556 del s
557
558 # 4. And update the DB
559 db.set_current_vlan(port_id, vlan_id)
560
561 return port_id # If we're successful
562
563 # Complex call, updating both DB and switch state
564 # 1. Check validity of input
565 # 2. Update the port config on the switch
566 # 3. If all went OK, save config on the switch
567 # 4. Change details of the port in the DB
568 #
569 # If things fail, we attempt to roll back by rebooting the switch
570 def restore_base_vlan(self, state, port_id):
571
572 print 'restore_base_vlan'
573 db = state.db
574 config = state.config
575
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000576 # 1. Sanity checks!
577 port = db.get_port_by_id(port_id)
578 if port is None:
579 raise InputError("Port ID %d does not exist" % port_id)
580 if port.is_trunk:
581 raise InputError("Port ID %d is not an access port" % port_id)
582 if port.is_locked:
583 raise InputError("Port ID %d is locked" % port_id)
584
585 # Bail out early if we're *already* on the base VLAN. This is
586 # not an error
587 if port.current_vlan_id == port.base_vlan_id:
588 return port_id
589
590 vlan = db.get_vlan_by_id(port.base_vlan_id)
591
592 # Get the right driver
593 switch_name = db.get_switch_name_by_id(port.switch_id)
594 s = self.get_switch_driver(switch_name, config)
595
596 # 2. Now start configuring the switch
597 try:
598 s.switch_connect(config.switches[switch_name].username,
599 config.switches[switch_name].password,
600 config.switches[switch_name].enablepassword)
601 except:
602 print 'Failed to talk to switch %s!' % switch_name
603 raise
604
605 try:
606 s.port_set_access_vlan(port.name, vlan.tag)
607 except IOError:
608 # Bugger. Looks like one of the switch calls above
609 # failed. To undo the changes safely, we'll need to reset
610 # all the config on this switch
611 s.switch_restart() # Will implicitly also close the connection
612 del s
613 raise
614
615 # 3. All seems to have worked so far!
616 s.switch_save_running_config()
617 s.switch_disconnect()
618 del s
619
620 # 4. And update the DB
621 db.set_current_vlan(port_id, port.base_vlan_id)
622
623 return port_id # If we're successful
624
625 # Complex call, updating both DB and switch state
626 # * Check validity of input
627 # * Read all the config from the switch (switch, ports, VLANs)
628 # * Create initial DB entries to match each of those
629 # * Merge VLANs across all switches
630 # * Set up ports appropriately
631 #
632 def auto_import_switch(self, state, switch_name):
633
634 print 'auto_import_switch'
635 db = state.db
636 config = state.config
637
638 # 1. Sanity checks!
639 switch_id = db.get_switch_id_by_name(switch_name)
640 if switch_id is not None:
641 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
642
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000643 if not switch_name in config.switches:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000644 raise InputError("Switch name %s not defined in config" % switch_name)
645
646 # 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,
651 config.switches[switch_name].enablepassword)
652 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
657 # to create VLANs on *other* switches, and it's easier to do that before we've added our new switch
658
659 new_vlan_tags = []
660
661 # Grab the VLANs defined on this switch
662 vlan_tags = s.vlan_get_list()
663 for vlan_tag in vlan_tags:
664 vlan_name = s.vlan_get_name(vlan_tag)
665
666 # If a VLAN is already in the database, then that's easy -
667 # we can just ignore it. However, we have to check that
668 # there is not a different name for the existing VLAN tag
669 # - bail out if so...
670 #
671 # If this VLAN tag is not already in the DB, we'll need to
672 # add it there and to all the other switches (and their
673 # trunk ports!) too.
674 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
675 if vlan_id is not None:
676 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
677 if vlan_name != vlan_db_name:
678 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))
679
680 else:
681 # OK, we'll need to set up the new VLAN now. It can't
682 # be a base VLAN - switches don't have such a concept!
683 # Rather than create individually here, add to a
684 # list. *Only* once we've worked through all the
685 # switch's VLANs successfully (checking for existing
686 # records and possible clashes!) should we start
687 # committing changes
688 new_vlan_tags.append(vlan_tag)
689
690 # Now create the VLAN DB entries
691 for vlan_tag in new_vlan_tags:
692 vlan_name = s.vlan_get_name(vlan_tag)
693 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
694
695 # *Now* add this switch itself to the database, after we've
696 # worked on all the other switches
697 switch_id = db.create_switch(switch_name)
698
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000699 # And now the ports
700 trunk_ports = []
701 ports = s.switch_get_port_names()
702 for port_name in ports:
703 port_id = None
704 port_mode = s.port_get_mode(port_name)
705 if port_mode == 'access':
706 # Access ports are easy - just create the port, and
707 # set both the current and base VLANs to the current
708 # VLAN on the switch. We'll end up changing this after
709 # import if needed.
710 port_vlan = s.port_get_access_vlan(port_name)
711 port_id = db.create_port(switch_id, port_name, port_vlan, port_vlan)
712 # Nothing further needed
713 elif port_mode == 'trunk':
714 # Trunk ports are a little more involved. First,
715 # create the port in the DB, setting the VLANs to the
716 # first VLAN found on the trunk port. This will *also*
717 # be in access mode by default, and unlocked.
718 port_vlans = s.port_get_trunk_vlan_list(port_name)
719 if port_vlans == 'ALL':
720 port_vlans = (state.default_vlan_id,) # easy for our purposes
721 port_id = db.create_port(switch_id, port_name,
722 port_vlans[0], port_vlans[0])
723 # Append to a list of trunk ports that we will need to
724 # modify once we're done
725 trunk_ports.append(port_id)
726
727 # Done with this switch in the main loop here
728 s.switch_disconnect()
729 del s
730
731 # Now on each trunk port we need to add all the VLANs across
732 # our system, so it's ready to go. Each time we call this, it
733 # will connect, disconnect, save config etc.
734 for port_id in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000735 self.set_port_mode(state, port_id, 'trunk')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000736
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000737 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000738 ret['switch_id'] = switch_id
739 ret['num_ports_added'] = len(ports)
740 ret['num_vlans_added'] = len(new_vlan_tags)
741 return ret # If we're successful