blob: 1e246fc74cab650b0423a1a4ffd2c0b28046a509 [file] [log] [blame]
Steve McIntyref1c04f92014-12-16 18:23:15 +00001import logging
Steve McIntyre53c7ad92014-12-16 19:21:13 +00002import time
Steve McIntyref1c04f92014-12-16 18:23:15 +00003from errors import CriticalError, InputError, ConfigError, SocketError
4
5class VlanUtil:
6 """VLANd utility functions"""
7
Steve McIntyre5f6f85e2014-12-22 16:42:28 +00008 def get_switch_driver(self, switch_name, config):
9 logging.debug("Trying to find a driver for %s" % switch_name)
10 driver = config.switches[switch_name].driver
Steve McIntyref1c04f92014-12-16 18:23:15 +000011 logging.debug("Driver: %s" % driver)
12 module = __import__("drivers.%s" % driver, fromlist=[driver])
13 class_ = getattr(module, driver)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000014 return class_(switch_name)
Steve McIntyref1c04f92014-12-16 18:23:15 +000015
Steve McIntyre519158e2014-12-23 13:44:44 +000016 def probe_switches(self, state):
17 config = state.config
Steve McIntyree8d80582014-12-23 16:53:39 +000018 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000019 for switch_name in sorted(config.switches):
20 print "Found switch %s:" % (switch_name)
Steve McIntyree8d80582014-12-23 16:53:39 +000021 print " Probing..."
Steve McIntyre519158e2014-12-23 13:44:44 +000022
Steve McIntyre4b4ab652014-12-22 17:19:09 +000023 s = self.get_switch_driver(switch_name, config)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000024 s.switch_connect(config.switches[switch_name].username,
25 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +000026 config.switches[switch_name].enable_password)
Steve McIntyre27d4b582014-12-23 22:51:00 +000027 ret[switch_name] = 'Found %d ports: ' % len(s.switch_get_port_names())
28 for name in s.switch_get_port_names():
29 ret[switch_name] += '%s ' % name
Steve McIntyrea2a8f792014-12-17 17:34:32 +000030 s.switch_disconnect()
31 del(s)
Steve McIntyrea2020cb2014-12-23 16:56:40 +000032 return ret
Steve McIntyrec68a18e2014-12-17 16:29:28 +000033
Steve McIntyre091e2ac2014-12-16 19:20:07 +000034 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000035 def perform_db_query(self, state, command, data):
Steve McIntyre091e2ac2014-12-16 19:20:07 +000036 print 'perform_db_query'
Steve McIntyref1c04f92014-12-16 18:23:15 +000037 print command
38 print data
39 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000040 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000041 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000042 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000043 ret = db.all_switches()
44 elif command == 'db.all_ports':
45 ret = db.all_ports()
46 elif command == 'db.all_vlans':
47 ret = db.all_vlans()
48 elif command == 'db.get_switch_by_id':
49 ret = db.get_switch_by_id(data['switch_id'])
50 elif command == 'db.get_switch_id_by_name':
51 ret = db.get_switch_id_by_name(data['name'])
52 elif command == 'db.get_switch_name_by_id':
53 ret = db.get_switch_name_by_id(data['switch_id'])
54 elif command == 'db.get_port_by_id':
55 ret = db.get_port_by_id(data['port_id'])
56 elif command == 'db.get_ports_by_switch':
57 ret = db.get_ports_by_switch(data['switch_id'])
58 elif command == 'db.get_port_by_switch_and_name':
59 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
60 elif command == 'db.get_current_vlan_id_by_port':
61 ret = db.get_current_vlan_id_by_port(data['port_id'])
62 elif command == 'db.get_base_vlan_id_by_port':
63 ret = db.get_base_vlan_id_by_port(data['port_id'])
64 elif command == 'db.get_ports_by_current_vlan':
65 ret = db.get_ports_by_current_vlan(data['vlan_id'])
66 elif command == 'db.get_ports_by_base_vlan':
67 ret = db.get_ports_by_base_vlan(data['vlan_id'])
68 elif command == 'db.get_vlan_by_id':
69 ret = db.get_vlan_by_id(data['vlan_id'])
70 elif command == 'db.get_vlan_id_by_name':
71 ret = db.get_vlan_id_by_name(data['name'])
72 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +000073 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +000074 elif command == 'db.get_vlan_name_by_id':
75 ret = db.get_vlan_name_by_id(data['vlan_id'])
76 else:
Steve McIntyree749fef2014-12-17 16:35:45 +000077 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +000078
Steve McIntyre5da37fa2014-12-17 13:14:44 +000079 except InputError:
80 raise
81
Steve McIntyre798af842014-12-23 22:29:46 +000082# except:
83# raise InputError("Invalid input in query")
Steve McIntyref1c04f92014-12-16 18:23:15 +000084
85 return ret
86
Steve McIntyre53c7ad92014-12-16 19:21:13 +000087 # Simple helper wrapper for all the read-only daemon state queries
88 def perform_daemon_query(self, state, command, data):
89 print 'perform_daemon_query'
90 print command
91 print data
92 ret = {}
93 try:
94 if command == 'daemon.status':
95 # data ignored
96 ret['running'] = 'ok'
97 elif command == 'daemon.version':
98 # data ignored
99 ret['version'] = state.version
100 elif command == 'daemon.statistics':
101 ret['uptime'] = time.time() - state.starttime
Steve McIntyre88b79df2014-12-23 13:45:08 +0000102 elif command == 'daemon.probe_switches':
103 ret = self.probe_switches(state)
Steve McIntyre06fe6422015-01-23 17:55:43 +0000104 elif command == 'daemon.shutdown':
105 # data ignored
106 ret['shutdown'] = 'Shutting down'
107 state.running = False
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000108 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000109 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000110
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000111 except InputError:
112 raise
113
Steve McIntyre798af842014-12-23 22:29:46 +0000114# except:
115# raise InputError("Invalid input in query")
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000116
117 return ret
118
Steve McIntyree749fef2014-12-17 16:35:45 +0000119 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000120 def perform_db_update(self, state, command, data):
121 print 'perform_db_update'
122 print command
123 print data
124 ret = {}
125 db = state.db
126 try:
127 if command == 'db.create_switch':
128 ret = db.create_switch(data['name'])
129 elif command == 'db.create_port':
130 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000131 state.default_vlan_id,
132 state.default_vlan_id)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000133 elif command == 'db.delete_switch':
134 ret = db.delete_switch(data['switch_id'])
135 elif command == 'db.delete_port':
136 ret = db.delete_port(data['port_id'])
137 elif command == 'db.set_port_is_locked':
138 ret = db.set_port_is_locked(data['port_id'], data['is_locked'])
139 elif command == 'db.set_base_vlan':
140 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
141 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000142 raise InputError("Unknown db_update command \"%s\"" % command)
143
144 except InputError:
145 raise
146
Steve McIntyre798af842014-12-23 22:29:46 +0000147# except:
148# raise InputError("Invalid input in query")
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000149
150 return ret
151
152 # Helper wrapper for API functions that modify both database state
153 # and on-switch VLAN state
154 def perform_vlan_update(self, state, command, data):
155 print 'perform_vlan_update'
156 print command
157 print data
158 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000159
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000160 try:
161 # All of these are complex commands, so call helpers
162 # rather than inline the code here
163 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000164 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000165 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000166 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000167 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000168 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000169 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000170 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000171 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000172 ret = self.restore_base_vlan(state, int(data['port_id']))
173 elif command == 'api.auto_import_switch':
174 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000175 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000176 raise InputError("Unknown query command \"%s\"" % command)
177
Steve McIntyre3256b182014-12-19 15:38:15 +0000178 except InputError as e:
179 print 'got error %s' % e
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000180 raise
181
Steve McIntyre798af842014-12-23 22:29:46 +0000182# except:
183# raise InputError("Invalid input in query")
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000184
185 return ret
186
187
Steve McIntyre3256b182014-12-19 15:38:15 +0000188 # Complex call
189 # 1. create the VLAN in the DB
190 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000191 # a. Create the VLAN
192 # b. Add the VLAN to all trunk ports (if needed)
193 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000194 #
195 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000196 # fine. If things fail, we attempt to roll back by rebooting
197 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000198 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000199
200 print 'create_vlan'
201 db = state.db
202 config = state.config
203
Steve McIntyre3256b182014-12-19 15:38:15 +0000204 # 1. Database record first
205 try:
206 print 'Adding DB record first: name %s, tag %d, is_base_vlan %d' % (name, tag, is_base_vlan)
207 vlan_id = db.create_vlan(name, tag, is_base_vlan)
208 print 'Added VLAN tag %d, name %s to the database, created VLAN ID %d' % (tag, name, vlan_id)
209 except InputError:
210 print 'DB creation failed'
211 raise
212
Steve McIntyre153157d2014-12-19 18:05:20 +0000213 # Keep track of which switches we've configured, for later use
214 switches_done = []
215
Steve McIntyre3256b182014-12-19 15:38:15 +0000216 # 2. Now the switches
217 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000218 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000219 trunk_ports = []
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000220 switch_name = switch.name
Steve McIntyre3256b182014-12-19 15:38:15 +0000221 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000222 print 'Adding new VLAN to switch %s' % switch_name
Steve McIntyre3256b182014-12-19 15:38:15 +0000223 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000224 s = self.get_switch_driver(switch_name, config)
225 s.switch_connect(config.switches[switch_name].username,
226 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000227 config.switches[switch_name].enable_password)
Steve McIntyre3256b182014-12-19 15:38:15 +0000228
Steve McIntyre153157d2014-12-19 18:05:20 +0000229 # Mark this switch as one we've touched, for
230 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000231 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000232
Steve McIntyre3256b182014-12-19 15:38:15 +0000233 # 2a. Create the VLAN on the switch
234 s.vlan_create(tag)
235 s.vlan_set_name(tag, name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000236 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000237
238 # 2b. Do we need to worry about trunk ports on this switch?
239 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
240 print 'This switch does not need special trunk port handling'
241 else:
242 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000243 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyre3256b182014-12-19 15:38:15 +0000244 if trunk_ports is None:
245 print "But it has no trunk ports defined"
246 trunk_ports = []
247 else:
248 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
249
250 # Modify any trunk ports as needed
251 for port in trunk_ports:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000252 print 'Added VLAN tag %d, name %s to switch %s' % (tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000253 s.port_add_trunk_to_vlan(port, tag)
254
Steve McIntyre3256b182014-12-19 15:38:15 +0000255 # And now we're done with this switch
256 s.switch_disconnect()
257 del s
258
259 except IOError:
260 raise
261
262 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000263 # Bugger. Looks like one of the switch calls above
264 # failed. To undo the changes safely, we'll need to reset
265 # all the switches we managed to configure. This could
266 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000267 for switch_name in switches_done:
268 s = self.get_switch_driver(switch_name, config)
269 s.switch_connect(config.switches[switch_name].username,
270 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000271 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000272 s.switch_restart() # Will implicitly also close the connection
273 del s
274
Steve McIntyre3256b182014-12-19 15:38:15 +0000275 # Undo the database change
276 print 'Switch access failed. Deleting the new VLAN entry in the database'
277 db.delete_vlan(vlan_id)
278 raise
279
Steve McIntyre153157d2014-12-19 18:05:20 +0000280 # If we've got this far, things were successful. Save config
281 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000282 for switch_name in switches_done:
283 s = self.get_switch_driver(switch_name, config)
284 s.switch_connect(config.switches[switch_name].username,
285 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000286 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000287 s.switch_save_running_config()
288 s.switch_disconnect()
289 del s
290
Steve McIntyre3256b182014-12-19 15:38:15 +0000291 return vlan_id # If we're successful
292
Steve McIntyrefeb64522014-12-19 18:53:02 +0000293 # Complex call
294 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
295 # 2. Iterate through all switches:
296 # a. Remove the VLAN from all trunk ports (if needed)
297 # b. Remove the VLAN
298 # 3. If all went OK, save config on the switches
299 # 4. Remove the VLAN in the DB
300 #
301 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000302 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000303
304 print 'delete_vlan'
305 db = state.db
306 config = state.config
307
Steve McIntyrefeb64522014-12-19 18:53:02 +0000308 # 1. Check for database records first
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000309 print 'Checking for ports using VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000310 vlan = db.get_vlan_by_id(vlan_id)
311 if vlan is None:
312 raise InputError("VLAN ID %d does not exist" % vlan_id)
313 vlan_tag = vlan.tag
314 ports = db.get_ports_by_current_vlan(vlan_id)
315 if ports is not None:
316 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
317 (vlan_id, len(ports)))
318 ports = db.get_ports_by_base_vlan(vlan_id)
319 if ports is not None:
320 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
321 (vlan_id, len(ports)))
322
323 # Keep track of which switches we've configured, for later use
324 switches_done = []
325
326 # 2. Now the switches
327 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000328 for switch in db.all_switches():
329 switch_name = switch.name
Steve McIntyrefeb64522014-12-19 18:53:02 +0000330 trunk_ports = []
331 try:
332 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000333 s = self.get_switch_driver(switch_name, config)
334 s.switch_connect(config.switches[switch_name].username,
335 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000336 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000337
338 # Mark this switch as one we've touched, for
339 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000340 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000341
342 # 2a. Do we need to worry about trunk ports on this switch?
343 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
344 print 'This switch does not need special trunk port handling'
345 else:
346 print 'This switch needs special trunk port handling'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000347 trunk_ports = db.get_trunk_port_names_by_switch(switch.switch_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000348 if trunk_ports is None:
349 print "But it has no trunk ports defined"
350 trunk_ports = []
351 else:
352 print 'Found %d trunk_ports that need adjusting' % len(trunk_ports)
353
354 # Modify any trunk ports as needed
355 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000356 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000357 print 'Removed VLAN tag %d from switch %s port %s' % (vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000358
359 # 2b. Remove the VLAN from the switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000360 print 'Removing VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000361 s.vlan_destroy(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000362 print 'Removed VLAN tag %d from switch %s' % (vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000363
364 # And now we're done with this switch
365 s.switch_disconnect()
366 del s
367
368 except IOError:
369 raise
370
371 except IOError:
372 # Bugger. Looks like one of the switch calls above
373 # failed. To undo the changes safely, we'll need to reset
374 # all the switches we managed to configure. This could
375 # take some time!
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000376 for switch_name in switches_done:
377 s = self.get_switch_driver(switch_name, config)
378 s.switch_connect(config.switches[switch_name].username,
379 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000380 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000381 s.switch_restart() # Will implicitly also close the connection
382 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000383 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000384
385 # 3. If we've got this far, things were successful. Save
386 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000387 for switch_name in switches_done:
388 s = self.get_switch_driver(switch_name, config)
389 s.switch_connect(config.switches[switch_name].username,
390 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000391 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000392 s.switch_save_running_config()
393 s.switch_disconnect()
394 del s
395
396 # 4. Finally, remove the VLAN in the DB
397 try:
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000398 print 'Removing DB record: VLAN ID %d' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000399 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre4a43ab02014-12-22 01:47:03 +0000400 print 'Removed VLAN ID %d from the database OK' % vlan_id
Steve McIntyrefeb64522014-12-19 18:53:02 +0000401 except InputError:
402 print 'DB deletion failed'
403 raise
404
405 return vlan_id # If we're successful
406
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000407 # Complex call, depends on existing state a lot
408 # 1. Check validity of inputs
409 # 2. Switch mode and other config on the port.
410 # a. If switching trunk->access, remove all trunk VLANs from it
411 # (if needed) and switch back to the base VLAN for the
412 # port. Next, switch to access mode.
413 # b. If switching access->trunk, switch back to the base VLAN
414 # for the port. Next, switch mode. Then add all trunk VLANs
415 # to it (if needed)
416 # 3. If all went OK, save config on the switch
417 # 4. Change details of the port in the DB
418 #
419 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000420 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000421
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000422 print 'set_port_mode'
423 db = state.db
424 config = state.config
425
426 # 1. Sanity-check inputs
427 if mode is not 'access' and mode is not 'trunk':
428 raise InputError("Port mode %s is not a valid option: try 'access' or 'trunk" % mode)
429 port = db.get_port_by_id(port_id)
430 if port is None:
431 raise InputError("Port ID %d does not exist" % port_id)
432 if mode == 'trunk' and port.is_trunk:
433 raise InputError("Port ID %d is already in trunk mode")
434 if mode == 'access' and not port.is_trunk:
435 raise InputError("Port ID %d is already in access mode")
436 base_vlan_tag = db.get_vlan_tag_by_id(port.base_vlan_id)
437
438 # Get the right driver
439 switch_name = db.get_switch_name_by_id(port.switch_id)
440 s = self.get_switch_driver(switch_name, config)
441
442 # 2. Now start configuring the switch
443 try:
444 s.switch_connect(config.switches[switch_name].username,
445 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000446 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000447 except:
448 print 'Failed to talk to switch %s!' % switch_name
449 raise
450
451 try:
452 if port.is_trunk:
453 # 2a. We're going from a trunk port to an access port
454 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
455 print 'This switch does not need special trunk port handling'
456 else:
457 print 'This switch needs special trunk port handling'
458 vlans = s.port_get_trunk_vlan_list(port.name)
459 if vlans is None:
460 print "But it has no VLANs defined on port %s" % port.name
461 vlans = []
462 else:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000463 print 'Found %d vlans that need dropping on port %s' % (len(vlans), port.name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000464
465 for vlan in vlans:
466 s.port_remove_trunk_from_vlan(port.name, vlan)
467 s.port_add_trunk_to_vlan(port.name, base_vlan_tag)
468 s.port_set_mode(port.name, "access")
469
470 else:
471 # 2b. We're going from an access port to a trunk port
472 s.port_set_access_vlan(port.name, base_vlan_tag)
473 s.port_set_mode(port.name, "trunk")
474 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
475 print 'This switch does not need special trunk port handling'
476 else:
477 vlans = db.all_vlans()
478 for vlan in vlans:
479 s.port_add_trunk_to_vlan(port.name, vlan.tag)
480
481 except IOError:
482 # Bugger. Looks like one of the switch calls above
483 # failed. To undo the changes safely, we'll need to reset
484 # all the config on this switch
485 s.switch_restart() # Will implicitly also close the connection
486 del s
487 raise
488
489 # 3. All seems to have worked so far!
490 s.switch_save_running_config()
491 s.switch_disconnect()
492 del s
493
494 # 4. And update the DB
495 db.set_port_mode(port_id, mode)
496
497 return port_id # If we're successful
498
499 # Complex call, updating both DB and switch state
500 # 1. Check validity of inputs
501 # 2. Update the port config on the switch
502 # 3. If all went OK, save config on the switch
503 # 4. Change details of the port in the DB
504 #
505 # If things fail, we attempt to roll back by rebooting the switch
506 def set_current_vlan(self, state, port_id, vlan_id):
507
508 print 'set_current_vlan'
509 db = state.db
510 config = state.config
511
512 # 1. Sanity checks!
513 port = db.get_port_by_id(port_id)
514 if port is None:
515 raise InputError("Port ID %d does not exist" % port_id)
516 if port.is_trunk:
517 raise InputError("Port ID %d is not an access port" % port_id)
518 if port.is_locked:
519 raise InputError("Port ID %d is locked" % port_id)
520
521 vlan = db.get_vlan_by_id(vlan_id)
522 if vlan is None:
523 raise InputError("VLAN ID %d does not exist" % vlan_id)
524
525 # Get the right driver
526 switch_name = db.get_switch_name_by_id(port.switch_id)
527 s = self.get_switch_driver(switch_name, config)
528
529 # 2. Now start configuring the switch
530 try:
531 s.switch_connect(config.switches[switch_name].username,
532 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000533 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000534 except:
535 print 'Failed to talk to switch %s!' % switch_name
536 raise
537
538 try:
539 s.port_set_access_vlan(port.name, vlan.tag)
540 except IOError:
541 # Bugger. Looks like one of the switch calls above
542 # failed. To undo the changes safely, we'll need to reset
543 # all the config on this switch
544 s.switch_restart() # Will implicitly also close the connection
545 del s
546 raise
547
548 # 3. All seems to have worked so far!
549 s.switch_save_running_config()
550 s.switch_disconnect()
551 del s
552
553 # 4. And update the DB
554 db.set_current_vlan(port_id, vlan_id)
555
556 return port_id # If we're successful
557
558 # Complex call, updating both DB and switch state
559 # 1. Check validity of input
560 # 2. Update the port config on the switch
561 # 3. If all went OK, save config on the switch
562 # 4. Change details of the port in the DB
563 #
564 # If things fail, we attempt to roll back by rebooting the switch
565 def restore_base_vlan(self, state, port_id):
566
567 print 'restore_base_vlan'
568 db = state.db
569 config = state.config
570
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000571 # 1. Sanity checks!
572 port = db.get_port_by_id(port_id)
573 if port is None:
574 raise InputError("Port ID %d does not exist" % port_id)
575 if port.is_trunk:
576 raise InputError("Port ID %d is not an access port" % port_id)
577 if port.is_locked:
578 raise InputError("Port ID %d is locked" % port_id)
579
580 # Bail out early if we're *already* on the base VLAN. This is
581 # not an error
582 if port.current_vlan_id == port.base_vlan_id:
583 return port_id
584
585 vlan = db.get_vlan_by_id(port.base_vlan_id)
586
587 # Get the right driver
588 switch_name = db.get_switch_name_by_id(port.switch_id)
589 s = self.get_switch_driver(switch_name, config)
590
591 # 2. Now start configuring the switch
592 try:
593 s.switch_connect(config.switches[switch_name].username,
594 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000595 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000596 except:
597 print 'Failed to talk to switch %s!' % switch_name
598 raise
599
600 try:
601 s.port_set_access_vlan(port.name, vlan.tag)
602 except IOError:
603 # Bugger. Looks like one of the switch calls above
604 # failed. To undo the changes safely, we'll need to reset
605 # all the config on this switch
606 s.switch_restart() # Will implicitly also close the connection
607 del s
608 raise
609
610 # 3. All seems to have worked so far!
611 s.switch_save_running_config()
612 s.switch_disconnect()
613 del s
614
615 # 4. And update the DB
616 db.set_current_vlan(port_id, port.base_vlan_id)
617
618 return port_id # If we're successful
619
620 # Complex call, updating both DB and switch state
621 # * Check validity of input
622 # * Read all the config from the switch (switch, ports, VLANs)
623 # * Create initial DB entries to match each of those
624 # * Merge VLANs across all switches
625 # * Set up ports appropriately
626 #
627 def auto_import_switch(self, state, switch_name):
628
629 print 'auto_import_switch'
630 db = state.db
631 config = state.config
632
633 # 1. Sanity checks!
634 switch_id = db.get_switch_id_by_name(switch_name)
635 if switch_id is not None:
636 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
637
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000638 if not switch_name in config.switches:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000639 raise InputError("Switch name %s not defined in config" % switch_name)
640
Steve McIntyrefc511242014-12-23 22:28:30 +0000641 print 'args look ok'
642
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000643 # 2. Now start reading config from the switch
644 try:
645 s = self.get_switch_driver(switch_name, config)
646 s.switch_connect(config.switches[switch_name].username,
647 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000648 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000649 except:
650 print 'Failed to talk to switch %s!' % switch_name
651 raise
652
653 # DON'T create the switch record in the DB first - we'll want
Steve McIntyrefc511242014-12-23 22:28:30 +0000654 # to create VLANs on *other* switches, and it's easier to do
655 # that before we've added our new switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000656
657 new_vlan_tags = []
658
659 # Grab the VLANs defined on this switch
660 vlan_tags = s.vlan_get_list()
Steve McIntyrefc511242014-12-23 22:28:30 +0000661
662 print ' found %d vlans on the switch' % len(vlan_tags)
663
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000664 for vlan_tag in vlan_tags:
665 vlan_name = s.vlan_get_name(vlan_tag)
666
667 # If a VLAN is already in the database, then that's easy -
668 # we can just ignore it. However, we have to check that
669 # there is not a different name for the existing VLAN tag
Steve McIntyreb1529072014-12-23 17:17:22 +0000670 # - bail out if so... UNLESS we're looking at the default
671 # VLAN
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000672 #
673 # If this VLAN tag is not already in the DB, we'll need to
674 # add it there and to all the other switches (and their
675 # trunk ports!) too.
Steve McIntyrefc511242014-12-23 22:28:30 +0000676 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
Steve McIntyreb1529072014-12-23 17:17:22 +0000677 if vlan_id is not state.default_vlan_id:
Steve McIntyreb1529072014-12-23 17:17:22 +0000678 if vlan_id is not None:
679 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
680 if vlan_name != vlan_db_name:
681 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 +0000682
Steve McIntyreb1529072014-12-23 17:17:22 +0000683 else:
684 # OK, we'll need to set up the new VLAN now. It can't
685 # be a base VLAN - switches don't have such a concept!
686 # Rather than create individually here, add to a
687 # list. *Only* once we've worked through all the
688 # switch's VLANs successfully (checking for existing
689 # records and possible clashes!) should we start
690 # committing changes
691 new_vlan_tags.append(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000692
693 # Now create the VLAN DB entries
694 for vlan_tag in new_vlan_tags:
695 vlan_name = s.vlan_get_name(vlan_tag)
696 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
697
698 # *Now* add this switch itself to the database, after we've
699 # worked on all the other switches
700 switch_id = db.create_switch(switch_name)
701
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000702 # And now the ports
703 trunk_ports = []
704 ports = s.switch_get_port_names()
Steve McIntyrefc511242014-12-23 22:28:30 +0000705 print ' found %d ports on the switch' % len(ports)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000706 for port_name in ports:
Steve McIntyrefc511242014-12-23 22:28:30 +0000707 print ' trying to import port %s' % port_name
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000708 port_id = None
709 port_mode = s.port_get_mode(port_name)
710 if port_mode == 'access':
711 # Access ports are easy - just create the port, and
712 # set both the current and base VLANs to the current
713 # VLAN on the switch. We'll end up changing this after
714 # import if needed.
715 port_vlan = s.port_get_access_vlan(port_name)
Steve McIntyre6f17b102014-12-24 02:18:08 +0000716 port_vlan_id = db.get_vlan_id_by_tag(port_vlan)
717 port_id = db.create_port(switch_id, port_name,
718 port_vlan_id, port_vlan_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000719 print ' access port, VLAN %d' % int(port_vlan)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000720 # Nothing further needed
721 elif port_mode == 'trunk':
Steve McIntyrefc511242014-12-23 22:28:30 +0000722 print ' trunk port'
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000723 # Trunk ports are a little more involved. First,
724 # create the port in the DB, setting the VLANs to the
725 # first VLAN found on the trunk port. This will *also*
726 # be in access mode by default, and unlocked.
727 port_vlans = s.port_get_trunk_vlan_list(port_name)
Steve McIntyrefc511242014-12-23 22:28:30 +0000728 print ' trunk port has VLANs:'
729 print port_vlans
Steve McIntyre6f17b102014-12-24 02:18:08 +0000730 if port_vlans == [] or port_vlans is None or 'ALL' in port_vlans:
731 port_vlans = (state.config.vland.default_vlan_tag,) # easy for our purposes
732 port_vlan_id = db.get_vlan_id_by_tag(port_vlans[0])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000733 port_id = db.create_port(switch_id, port_name,
Steve McIntyre6f17b102014-12-24 02:18:08 +0000734 port_vlan_id, port_vlan_id)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000735 # Append to a list of trunk ports that we will need to
736 # modify once we're done
737 trunk_ports.append(port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000738 else:
739 raise CriticalError("Unrecognised port port mode %s???" % port_mode)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000740
Steve McIntyrefc511242014-12-23 22:28:30 +0000741 print " Added port %s, got port ID %d" % (port_name, port_id)
742
743 # Now, on each trunk port on the switch, we need to add all
744 # the VLANs already configured across our system
745 if not 'TrunkWildCardVlans' in s.switch_get_capabilities():
746 for port_id in trunk_ports:
747 port = db.get_port_by_id(port_id)
748 db.set_port_mode(port_id, "trunk")
749
750 for vlan in db.all_vlans():
751 if vlan.vlan_id is not state.default_vlan_id:
752 print "Adding allowed VLAN tag %d to trunk port %s" % (vlan.tag, port.name)
753 s.port_add_trunk_to_vlan(port.name, vlan.tag)
754
755
756 # Done with this switch \o/
757 s.switch_save_running_config()
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000758 s.switch_disconnect()
759 del s
760
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000761 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000762 ret['switch_id'] = switch_id
763 ret['num_ports_added'] = len(ports)
764 ret['num_vlans_added'] = len(new_vlan_tags)
765 return ret # If we're successful