blob: 738eb00313bc6c5433dd22de543f00eedbb0cbdd [file] [log] [blame]
Steve McIntyre8e99c5f2018-02-02 15:23:46 +00001# Copyright 2014-2018 Linaro Limited
Steve McIntyred2313b22016-03-12 11:50:10 +00002#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16# MA 02110-1301, USA.
17#
18# Utility routines, including handling of API functions
19#
20
Steve McIntyref1c04f92014-12-16 18:23:15 +000021import logging
Steve McIntyre53c7ad92014-12-16 19:21:13 +000022import time
Steve McIntyreb01959f2016-03-22 17:02:39 +000023from errors import CriticalError, NotFoundError, InputError, ConfigError, SocketError
Steve McIntyref1c04f92014-12-16 18:23:15 +000024
25class VlanUtil:
26 """VLANd utility functions"""
27
Steve McIntyre26de4d92015-09-23 00:04:55 +010028 def set_logging_level(self, level):
Steve McIntyre26de4d92015-09-23 00:04:55 +010029 loglevel = logging.CRITICAL
30 if level == "ERROR":
31 loglevel = logging.ERROR
32 elif level == "WARNING":
33 loglevel = logging.WARNING
34 elif level == "INFO":
35 loglevel = logging.INFO
36 elif level == "DEBUG":
37 loglevel = logging.DEBUG
38 return loglevel
39
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000040 def get_switch_driver(self, switch_name, config):
Steve McIntyre5fa22652015-04-01 18:01:45 +010041 logging.debug("Trying to find a driver for %s", switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000042 driver = config.switches[switch_name].driver
Steve McIntyre5fa22652015-04-01 18:01:45 +010043 logging.debug("Driver: %s", driver)
Steve McIntyref1c04f92014-12-16 18:23:15 +000044 module = __import__("drivers.%s" % driver, fromlist=[driver])
45 class_ = getattr(module, driver)
Steve McIntyre0f38f0e2015-07-14 15:26:05 +010046 return class_(switch_name, debug = config.switches[switch_name].debug)
Steve McIntyref1c04f92014-12-16 18:23:15 +000047
Steve McIntyre519158e2014-12-23 13:44:44 +000048 def probe_switches(self, state):
49 config = state.config
Steve McIntyree8d80582014-12-23 16:53:39 +000050 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000051 for switch_name in sorted(config.switches):
Steve McIntyre5fa22652015-04-01 18:01:45 +010052 logging.debug("Found switch %s:", switch_name)
Steve McIntyre7cf80982015-02-12 07:03:40 +000053 logging.debug(" Probing...")
Steve McIntyre519158e2014-12-23 13:44:44 +000054
Steve McIntyre4b4ab652014-12-22 17:19:09 +000055 s = self.get_switch_driver(switch_name, config)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +000056 s.switch_connect(config.switches[switch_name].username,
57 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +000058 config.switches[switch_name].enable_password)
Steve McIntyre27d4b582014-12-23 22:51:00 +000059 ret[switch_name] = 'Found %d ports: ' % len(s.switch_get_port_names())
60 for name in s.switch_get_port_names():
61 ret[switch_name] += '%s ' % name
Steve McIntyrea2a8f792014-12-17 17:34:32 +000062 s.switch_disconnect()
Steve McIntyre2ed90f52015-07-21 17:59:52 +010063 del s
Steve McIntyrea2020cb2014-12-23 16:56:40 +000064 return ret
Steve McIntyrec68a18e2014-12-17 16:29:28 +000065
Steve McIntyre091e2ac2014-12-16 19:20:07 +000066 # Simple helper wrapper for all the read-only database queries
Steve McIntyre2150bc22014-12-17 13:13:56 +000067 def perform_db_query(self, state, command, data):
Steve McIntyre7cf80982015-02-12 07:03:40 +000068 logging.debug('perform_db_query')
69 logging.debug(command)
70 logging.debug(data)
Steve McIntyref1c04f92014-12-16 18:23:15 +000071 ret = {}
Steve McIntyre2150bc22014-12-17 13:13:56 +000072 db = state.db
Steve McIntyref1c04f92014-12-16 18:23:15 +000073 try:
Steve McIntyre091e2ac2014-12-16 19:20:07 +000074 if command == 'db.all_switches':
Steve McIntyref1c04f92014-12-16 18:23:15 +000075 ret = db.all_switches()
76 elif command == 'db.all_ports':
77 ret = db.all_ports()
78 elif command == 'db.all_vlans':
79 ret = db.all_vlans()
Steve McIntyrec4890132015-08-07 15:19:11 +010080 elif command == 'db.all_trunks':
81 ret = db.all_trunks()
Steve McIntyref1c04f92014-12-16 18:23:15 +000082 elif command == 'db.get_switch_by_id':
83 ret = db.get_switch_by_id(data['switch_id'])
84 elif command == 'db.get_switch_id_by_name':
85 ret = db.get_switch_id_by_name(data['name'])
86 elif command == 'db.get_switch_name_by_id':
87 ret = db.get_switch_name_by_id(data['switch_id'])
88 elif command == 'db.get_port_by_id':
89 ret = db.get_port_by_id(data['port_id'])
90 elif command == 'db.get_ports_by_switch':
91 ret = db.get_ports_by_switch(data['switch_id'])
92 elif command == 'db.get_port_by_switch_and_name':
93 ret = db.get_port_by_switch_and_name(data['switch_id'], data['name'])
Steve McIntyre45f55012015-08-05 13:55:15 +010094 elif command == 'db.get_port_by_switch_and_number':
95 ret = db.get_port_by_switch_and_number(data['switch_id'], int(data['number']))
Steve McIntyref1c04f92014-12-16 18:23:15 +000096 elif command == 'db.get_current_vlan_id_by_port':
97 ret = db.get_current_vlan_id_by_port(data['port_id'])
98 elif command == 'db.get_base_vlan_id_by_port':
99 ret = db.get_base_vlan_id_by_port(data['port_id'])
100 elif command == 'db.get_ports_by_current_vlan':
101 ret = db.get_ports_by_current_vlan(data['vlan_id'])
102 elif command == 'db.get_ports_by_base_vlan':
103 ret = db.get_ports_by_base_vlan(data['vlan_id'])
Steve McIntyredfeac092018-02-07 00:57:49 +0000104 elif command == 'db.get_port_mode':
105 ret = db.get_port_mode(data['port_id'])
Steve McIntyrec4890132015-08-07 15:19:11 +0100106 elif command == 'db.get_ports_by_trunk':
107 ret = db.get_ports_by_trunk(data['trunk_id'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000108 elif command == 'db.get_vlan_by_id':
109 ret = db.get_vlan_by_id(data['vlan_id'])
Steve McIntyre65533d72015-01-23 18:01:17 +0000110 elif command == 'db.get_vlan_tag_by_id':
111 ret = db.get_vlan_tag_by_id(data['vlan_id'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000112 elif command == 'db.get_vlan_id_by_name':
113 ret = db.get_vlan_id_by_name(data['name'])
114 elif command == 'db.get_vlan_id_by_tag':
Steve McIntyre07946c22014-12-17 13:14:15 +0000115 ret = db.get_vlan_id_by_tag(data['tag'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000116 elif command == 'db.get_vlan_name_by_id':
117 ret = db.get_vlan_name_by_id(data['vlan_id'])
Steve McIntyrec4890132015-08-07 15:19:11 +0100118 elif command == 'db.get_trunk_by_id':
119 ret = db.get_trunk_by_id(data['trunk_id'])
Steve McIntyref1c04f92014-12-16 18:23:15 +0000120 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000121 raise InputError("Unknown db_query command \"%s\"" % command)
Steve McIntyref1c04f92014-12-16 18:23:15 +0000122
Steve McIntyreb01959f2016-03-22 17:02:39 +0000123 except (InputError, NotFoundError) as e:
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100124 logging.error('perform_db_query(%s) got error %s', command, e)
Steve McIntyre5da37fa2014-12-17 13:14:44 +0000125 raise
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100126 except ValueError as e:
127 logging.error('perform_db_query(%s) got error %s', command, e)
128 raise InputError("Invalid value in API call argument: %s" % e)
Steve McIntyredc173072016-08-25 18:29:10 +0100129 except TypeError as e:
130 logging.error('perform_db_query(%s) got error %s', command, e)
131 raise InputError("Invalid type in API call argument: %s" % e)
Steve McIntyref1c04f92014-12-16 18:23:15 +0000132
133 return ret
134
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000135 # Simple helper wrapper for all the read-only daemon state queries
136 def perform_daemon_query(self, state, command, data):
Steve McIntyre7cf80982015-02-12 07:03:40 +0000137 logging.debug('perform_daemon_query')
138 logging.debug(command)
139 logging.debug(data)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000140 ret = {}
141 try:
142 if command == 'daemon.status':
143 # data ignored
144 ret['running'] = 'ok'
Steve McIntyreea343aa2015-10-23 17:46:17 +0100145 ret['last_modified'] = state.db.get_last_modified_time()
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000146 elif command == 'daemon.version':
147 # data ignored
148 ret['version'] = state.version
149 elif command == 'daemon.statistics':
150 ret['uptime'] = time.time() - state.starttime
Steve McIntyre88b79df2014-12-23 13:45:08 +0000151 elif command == 'daemon.probe_switches':
152 ret = self.probe_switches(state)
Steve McIntyre06fe6422015-01-23 17:55:43 +0000153 elif command == 'daemon.shutdown':
154 # data ignored
155 ret['shutdown'] = 'Shutting down'
156 state.running = False
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000157 else:
Steve McIntyree749fef2014-12-17 16:35:45 +0000158 raise InputError("Unknown daemon_query command \"%s\"" % command)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000159
Steve McIntyreb01959f2016-03-22 17:02:39 +0000160 except (InputError, NotFoundError) as e:
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100161 logging.error('perform_daemon_query(%s) got error %s', command, e)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000162 raise
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100163 except ValueError as e:
164 logging.error('perform_daemon_query(%s) got error %s', command, e)
Steve McIntyredc173072016-08-25 18:29:10 +0100165 raise InputError("Invalid value in API call argument: %s" % e)
166 except TypeError as e:
167 logging.error('perform_daemon_query(%s) got error %s', command, e)
168 raise InputError("Invalid type in API call argument: %s" % e)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000169
170 return ret
171
Steve McIntyree749fef2014-12-17 16:35:45 +0000172 # Helper wrapper for API functions modifying database state only
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000173 def perform_db_update(self, state, command, data):
Steve McIntyre7cf80982015-02-12 07:03:40 +0000174 logging.debug('perform_db_update')
175 logging.debug(command)
176 logging.debug(data)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000177 ret = {}
178 db = state.db
179 try:
180 if command == 'db.create_switch':
181 ret = db.create_switch(data['name'])
182 elif command == 'db.create_port':
Steve McIntyreca6adfc2015-08-06 15:08:58 +0100183 try:
184 number = int(data['number'])
185 except ValueError:
186 raise InputError("Invalid value for port number (%s) - must be numeric only!" % data['number'])
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000187 ret = db.create_port(data['switch_id'], data['name'],
Steve McIntyreca6adfc2015-08-06 15:08:58 +0100188 number,
Steve McIntyrefefdbb42014-12-22 16:14:28 +0000189 state.default_vlan_id,
190 state.default_vlan_id)
Steve McIntyre0850e9b2016-03-12 11:51:47 +0000191 elif command == 'db.create_trunk':
Steve McIntyrec4890132015-08-07 15:19:11 +0100192 ret = db.create_trunk(data['port_id1'], data['port_id2'])
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000193 elif command == 'db.delete_switch':
194 ret = db.delete_switch(data['switch_id'])
195 elif command == 'db.delete_port':
196 ret = db.delete_port(data['port_id'])
197 elif command == 'db.set_port_is_locked':
Steve McIntyre7d219202018-02-01 16:53:25 +0000198 ret = db.set_port_is_locked(data['port_id'],
199 data['is_locked'],
200 data['lock_reason'])
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000201 elif command == 'db.set_base_vlan':
202 ret = db.set_base_vlan(data['port_id'], data['base_vlan_id'])
Steve McIntyrec4890132015-08-07 15:19:11 +0100203 elif command == 'db.delete_trunk':
204 ret = db.delete_trunk(data['trunk_id'])
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000205 else:
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000206 raise InputError("Unknown db_update command \"%s\"" % command)
207
Steve McIntyreb01959f2016-03-22 17:02:39 +0000208 except (InputError, NotFoundError) as e:
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100209 logging.error('perform_db_update(%s) got error %s', command, e)
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000210 raise
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100211 except ValueError as e:
212 logging.error('perform_db_update(%s) got error %s', command, e)
Steve McIntyredc173072016-08-25 18:29:10 +0100213 raise InputError("Invalid value in API call argument: %s" % e)
214 except TypeError as e:
215 logging.error('perform_db_update(%s) got error %s', command, e)
216 raise InputError("Invalid type in API call argument: %s" % e)
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000217
218 return ret
219
220 # Helper wrapper for API functions that modify both database state
221 # and on-switch VLAN state
222 def perform_vlan_update(self, state, command, data):
Steve McIntyre7cf80982015-02-12 07:03:40 +0000223 logging.debug('perform_vlan_update')
224 logging.debug(command)
225 logging.debug(data)
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000226 ret = {}
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000227
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000228 try:
229 # All of these are complex commands, so call helpers
230 # rather than inline the code here
231 if command == 'api.create_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000232 ret = self.create_vlan(state, data['name'], int(data['tag']), data['is_base_vlan'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000233 elif command == 'api.delete_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000234 ret = self.delete_vlan(state, int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000235 elif command == 'api.set_port_mode':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000236 ret = self.set_port_mode(state, int(data['port_id']), data['mode'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000237 elif command == 'api.set_current_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000238 ret = self.set_current_vlan(state, int(data['port_id']), int(data['vlan_id']))
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000239 elif command == 'api.restore_base_vlan':
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000240 ret = self.restore_base_vlan(state, int(data['port_id']))
241 elif command == 'api.auto_import_switch':
242 ret = self.auto_import_switch(state, data['switch'])
Steve McIntyre0f9dea62014-12-17 16:37:01 +0000243 else:
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000244 raise InputError("Unknown query command \"%s\"" % command)
245
Steve McIntyreb01959f2016-03-22 17:02:39 +0000246 except (InputError, NotFoundError) as e:
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100247 logging.error('perform_vlan_update(%s) got error %s', command, e)
Steve McIntyrea590b5b2014-12-17 13:15:14 +0000248 raise
Steve McIntyre978cb9f2015-10-09 16:51:28 +0100249 except ValueError as e:
250 logging.error('perform_vlan_update(%s) got error %s', command, e)
Steve McIntyredc173072016-08-25 18:29:10 +0100251 raise InputError("Invalid value in API call argument: %s" % e)
252 except TypeError as e:
253 logging.error('perform_vlan_update(%s) got error %s', command, e)
254 raise InputError("Invalid type in API call argument: %s" % e)
Steve McIntyre53c7ad92014-12-16 19:21:13 +0000255
256 return ret
257
258
Steve McIntyre3256b182014-12-19 15:38:15 +0000259 # Complex call
260 # 1. create the VLAN in the DB
261 # 2. Iterate through all switches:
Steve McIntyre1ab8b872014-12-19 18:37:00 +0000262 # a. Create the VLAN
263 # b. Add the VLAN to all trunk ports (if needed)
264 # 3. If all went OK, save config on all the switches
Steve McIntyre3256b182014-12-19 15:38:15 +0000265 #
266 # The VLAN may already exist on some of the switches, that's
Steve McIntyre153157d2014-12-19 18:05:20 +0000267 # fine. If things fail, we attempt to roll back by rebooting
268 # switches then removing the VLAN in the DB.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000269 def create_vlan(self, state, name, tag, is_base_vlan):
Steve McIntyre3256b182014-12-19 15:38:15 +0000270
Steve McIntyre7cf80982015-02-12 07:03:40 +0000271 logging.debug('create_vlan')
Steve McIntyre3256b182014-12-19 15:38:15 +0000272 db = state.db
273 config = state.config
274
Steve McIntyre6b8d3862015-08-03 19:28:25 +0100275 # Check for tag == -1, i.e. use the next available tag
276 if tag == -1:
277 tag = db.find_lowest_unused_vlan_tag()
278 logging.debug('create_vlan called with a tag of -1, found first unused tag %d', tag)
279
Steve McIntyre3256b182014-12-19 15:38:15 +0000280 # 1. Database record first
281 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100282 logging.debug('Adding DB record first: name %s, tag %d, is_base_vlan %d', name, tag, is_base_vlan)
Steve McIntyre3256b182014-12-19 15:38:15 +0000283 vlan_id = db.create_vlan(name, tag, is_base_vlan)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100284 logging.debug('Added VLAN tag %d, name %s to the database, created VLAN ID %d', tag, name, vlan_id)
Steve McIntyreb01959f2016-03-22 17:02:39 +0000285 except (InputError, NotFoundError):
Steve McIntyre7cf80982015-02-12 07:03:40 +0000286 logging.debug('DB creation failed')
Steve McIntyre3256b182014-12-19 15:38:15 +0000287 raise
288
Steve McIntyre153157d2014-12-19 18:05:20 +0000289 # Keep track of which switches we've configured, for later use
290 switches_done = []
291
Steve McIntyre3256b182014-12-19 15:38:15 +0000292 # 2. Now the switches
293 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000294 for switch in db.all_switches():
Steve McIntyre3256b182014-12-19 15:38:15 +0000295 trunk_ports = []
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000296 switch_name = switch['name']
Steve McIntyre3256b182014-12-19 15:38:15 +0000297 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100298 logging.debug('Adding new VLAN to switch %s', switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000299 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000300 s = self.get_switch_driver(switch_name, config)
301 s.switch_connect(config.switches[switch_name].username,
302 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000303 config.switches[switch_name].enable_password)
Steve McIntyre3256b182014-12-19 15:38:15 +0000304
Steve McIntyre153157d2014-12-19 18:05:20 +0000305 # Mark this switch as one we've touched, for
306 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000307 switches_done.append(switch_name)
Steve McIntyre153157d2014-12-19 18:05:20 +0000308
Steve McIntyre3256b182014-12-19 15:38:15 +0000309 # 2a. Create the VLAN on the switch
310 s.vlan_create(tag)
311 s.vlan_set_name(tag, name)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100312 logging.debug('Added VLAN tag %d, name %s to switch %s', tag, name, switch_name)
Steve McIntyre3256b182014-12-19 15:38:15 +0000313
314 # 2b. Do we need to worry about trunk ports on this switch?
315 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
Steve McIntyre7cf80982015-02-12 07:03:40 +0000316 logging.debug('This switch does not need special trunk port handling')
Steve McIntyre3256b182014-12-19 15:38:15 +0000317 else:
Steve McIntyre7cf80982015-02-12 07:03:40 +0000318 logging.debug('This switch needs special trunk port handling')
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000319 trunk_ports = db.get_trunk_port_names_by_switch(switch['switch_id'])
Steve McIntyre3256b182014-12-19 15:38:15 +0000320 if trunk_ports is None:
Steve McIntyre7cf80982015-02-12 07:03:40 +0000321 logging.debug("But it has no trunk ports defined")
Steve McIntyre3256b182014-12-19 15:38:15 +0000322 trunk_ports = []
323 else:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100324 logging.debug('Found %d trunk_ports that need adjusting', len(trunk_ports))
Steve McIntyre3256b182014-12-19 15:38:15 +0000325
326 # Modify any trunk ports as needed
327 for port in trunk_ports:
Steve McIntyre1ce09f92015-09-04 14:35:13 +0100328 logging.debug('Adding VLAN tag %d, name %s to switch %s port %s', tag, name, switch_name, port)
Steve McIntyre3256b182014-12-19 15:38:15 +0000329 s.port_add_trunk_to_vlan(port, tag)
330
Steve McIntyre3256b182014-12-19 15:38:15 +0000331 # And now we're done with this switch
332 s.switch_disconnect()
333 del s
334
Steve McIntyre8f27ae12015-09-04 14:35:38 +0100335 except IOError as e:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000336 logging.error('Failed to add VLAN %d to switch ID %d (%s): %s', tag, switch['switch_id'], switch['name'], e)
Steve McIntyre3256b182014-12-19 15:38:15 +0000337 raise
Steve McIntyreb826fc72015-07-27 17:57:40 +0100338
Steve McIntyre3256b182014-12-19 15:38:15 +0000339 except IOError:
Steve McIntyre153157d2014-12-19 18:05:20 +0000340 # Bugger. Looks like one of the switch calls above
341 # failed. To undo the changes safely, we'll need to reset
342 # all the switches we managed to configure. This could
343 # take some time!
Steve McIntyre38a24e52015-07-21 17:54:58 +0100344 logging.error('create_vlan failed, resetting all switches to recover')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000345 for switch_name in switches_done:
346 s = self.get_switch_driver(switch_name, config)
347 s.switch_connect(config.switches[switch_name].username,
348 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000349 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000350 s.switch_restart() # Will implicitly also close the connection
351 del s
352
Steve McIntyre3256b182014-12-19 15:38:15 +0000353 # Undo the database change
Steve McIntyre7cf80982015-02-12 07:03:40 +0000354 logging.debug('Switch access failed. Deleting the new VLAN entry in the database')
Steve McIntyre3256b182014-12-19 15:38:15 +0000355 db.delete_vlan(vlan_id)
356 raise
357
Steve McIntyre153157d2014-12-19 18:05:20 +0000358 # If we've got this far, things were successful. Save config
359 # on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000360 for switch_name in switches_done:
361 s = self.get_switch_driver(switch_name, config)
362 s.switch_connect(config.switches[switch_name].username,
363 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000364 config.switches[switch_name].enable_password)
Steve McIntyre153157d2014-12-19 18:05:20 +0000365 s.switch_save_running_config()
366 s.switch_disconnect()
367 del s
368
Steve McIntyre6b8d3862015-08-03 19:28:25 +0100369 return (vlan_id, tag) # If we're successful
Steve McIntyre3256b182014-12-19 15:38:15 +0000370
Steve McIntyrefeb64522014-12-19 18:53:02 +0000371 # Complex call
372 # 1. Check in the DB if there are any ports on the VLAN. Bail if so
373 # 2. Iterate through all switches:
374 # a. Remove the VLAN from all trunk ports (if needed)
375 # b. Remove the VLAN
376 # 3. If all went OK, save config on the switches
377 # 4. Remove the VLAN in the DB
378 #
379 # If things fail, we attempt to roll back by rebooting switches.
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000380 def delete_vlan(self, state, vlan_id):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000381
Steve McIntyre7cf80982015-02-12 07:03:40 +0000382 logging.debug('delete_vlan')
Steve McIntyrefeb64522014-12-19 18:53:02 +0000383 db = state.db
384 config = state.config
385
Steve McIntyrefeb64522014-12-19 18:53:02 +0000386 # 1. Check for database records first
Steve McIntyre5fa22652015-04-01 18:01:45 +0100387 logging.debug('Checking for ports using VLAN ID %d', vlan_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000388 vlan = db.get_vlan_by_id(vlan_id)
389 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000390 raise NotFoundError("VLAN ID %d does not exist" % vlan_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000391 vlan_tag = vlan['tag']
Steve McIntyrefeb64522014-12-19 18:53:02 +0000392 ports = db.get_ports_by_current_vlan(vlan_id)
393 if ports is not None:
394 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
395 (vlan_id, len(ports)))
396 ports = db.get_ports_by_base_vlan(vlan_id)
397 if ports is not None:
398 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
399 (vlan_id, len(ports)))
400
401 # Keep track of which switches we've configured, for later use
402 switches_done = []
403
404 # 2. Now the switches
405 try:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000406 for switch in db.all_switches():
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000407 switch_name = switch['name']
Steve McIntyrefeb64522014-12-19 18:53:02 +0000408 trunk_ports = []
409 try:
410 # Get the right driver
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000411 s = self.get_switch_driver(switch_name, config)
412 s.switch_connect(config.switches[switch_name].username,
413 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000414 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000415
416 # Mark this switch as one we've touched, for
417 # either config saving or rollback below
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000418 switches_done.append(switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000419
420 # 2a. Do we need to worry about trunk ports on this switch?
421 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
Steve McIntyre7cf80982015-02-12 07:03:40 +0000422 logging.debug('This switch does not need special trunk port handling')
Steve McIntyrefeb64522014-12-19 18:53:02 +0000423 else:
Steve McIntyre7cf80982015-02-12 07:03:40 +0000424 logging.debug('This switch needs special trunk port handling')
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000425 trunk_ports = db.get_trunk_port_names_by_switch(switch['switch_id'])
Steve McIntyrefeb64522014-12-19 18:53:02 +0000426 if trunk_ports is None:
Steve McIntyre7cf80982015-02-12 07:03:40 +0000427 logging.debug("But it has no trunk ports defined")
Steve McIntyrefeb64522014-12-19 18:53:02 +0000428 trunk_ports = []
429 else:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100430 logging.debug('Found %d trunk_ports that need adjusting', len(trunk_ports))
Steve McIntyrefeb64522014-12-19 18:53:02 +0000431
432 # Modify any trunk ports as needed
433 for port in trunk_ports:
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000434 s.port_remove_trunk_from_vlan(port, vlan_tag)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100435 logging.debug('Removed VLAN tag %d from switch %s port %s', vlan_tag, switch_name, port)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000436
437 # 2b. Remove the VLAN from the switch
Steve McIntyre5fa22652015-04-01 18:01:45 +0100438 logging.debug('Removing VLAN tag %d from switch %s', vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000439 s.vlan_destroy(vlan_tag)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100440 logging.debug('Removed VLAN tag %d from switch %s', vlan_tag, switch_name)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000441
442 # And now we're done with this switch
443 s.switch_disconnect()
444 del s
445
446 except IOError:
447 raise
448
449 except IOError:
450 # Bugger. Looks like one of the switch calls above
451 # failed. To undo the changes safely, we'll need to reset
452 # all the switches we managed to configure. This could
453 # take some time!
Steve McIntyre38a24e52015-07-21 17:54:58 +0100454 logging.error('delete_vlan failed, resetting all switches to recover')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000455 for switch_name in switches_done:
456 s = self.get_switch_driver(switch_name, config)
457 s.switch_connect(config.switches[switch_name].username,
458 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000459 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000460 s.switch_restart() # Will implicitly also close the connection
461 del s
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000462 raise
Steve McIntyrefeb64522014-12-19 18:53:02 +0000463
464 # 3. If we've got this far, things were successful. Save
465 # config on all the switches so it will persist across reboots
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000466 for switch_name in switches_done:
467 s = self.get_switch_driver(switch_name, config)
468 s.switch_connect(config.switches[switch_name].username,
469 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000470 config.switches[switch_name].enable_password)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000471 s.switch_save_running_config()
472 s.switch_disconnect()
473 del s
474
475 # 4. Finally, remove the VLAN in the DB
476 try:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100477 logging.debug('Removing DB record: VLAN ID %d', vlan_id)
Steve McIntyrefeb64522014-12-19 18:53:02 +0000478 vlan_id = db.delete_vlan(vlan_id)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100479 logging.debug('Removed VLAN ID %d from the database OK', vlan_id)
Steve McIntyreb01959f2016-03-22 17:02:39 +0000480 except (InputError, NotFoundError):
Steve McIntyre7cf80982015-02-12 07:03:40 +0000481 logging.debug('DB deletion failed')
Steve McIntyrefeb64522014-12-19 18:53:02 +0000482 raise
483
484 return vlan_id # If we're successful
485
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000486 # Complex call, depends on existing state a lot
487 # 1. Check validity of inputs
488 # 2. Switch mode and other config on the port.
489 # a. If switching trunk->access, remove all trunk VLANs from it
490 # (if needed) and switch back to the base VLAN for the
491 # port. Next, switch to access mode.
492 # b. If switching access->trunk, switch back to the base VLAN
493 # for the port. Next, switch mode. Then add all trunk VLANs
494 # to it (if needed)
495 # 3. If all went OK, save config on the switch
496 # 4. Change details of the port in the DB
497 #
498 # If things fail, we attempt to roll back by rebooting the switch
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000499 def set_port_mode(self, state, port_id, mode):
Steve McIntyrefeb64522014-12-19 18:53:02 +0000500
Steve McIntyre7cf80982015-02-12 07:03:40 +0000501 logging.debug('set_port_mode')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000502 db = state.db
503 config = state.config
504
505 # 1. Sanity-check inputs
Steve McIntyre5b0de002015-01-23 18:05:13 +0000506 if mode != 'access' and mode != 'trunk':
507 raise InputError("Port mode '%s' is not a valid option: try 'access' or 'trunk'" % mode)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000508 port = db.get_port_by_id(port_id)
509 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000510 raise NotFoundError("Port ID %d does not exist" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000511 if port['is_locked']:
Steve McIntyre28114092015-02-13 03:04:40 +0000512 raise InputError("Port ID %d is locked" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000513 if mode == 'trunk' and port['is_trunk']:
Steve McIntyre3aff7da2015-02-13 04:00:11 +0000514 raise InputError("Port ID %d is already in trunk mode" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000515 if mode == 'access' and not port['is_trunk']:
Steve McIntyre3aff7da2015-02-13 04:00:11 +0000516 raise InputError("Port ID %d is already in access mode" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000517 base_vlan_tag = db.get_vlan_tag_by_id(port['base_vlan_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000518
519 # Get the right driver
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000520 switch_name = db.get_switch_name_by_id(port['switch_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000521 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:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100529 logging.debug('Failed to talk to switch %s!', switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000530 raise
531
532 try:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000533 if port['is_trunk']:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000534 # 2a. We're going from a trunk port to an access port
535 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
Steve McIntyre7cf80982015-02-12 07:03:40 +0000536 logging.debug('This switch does not need special trunk port handling')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000537 else:
Steve McIntyre7cf80982015-02-12 07:03:40 +0000538 logging.debug('This switch needs special trunk port handling')
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000539 vlans = s.port_get_trunk_vlan_list(port['name'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000540 if vlans is None:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000541 logging.debug("But it has no VLANs defined on port %s", port['name'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000542 vlans = []
543 else:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000544 logging.debug('Found %d vlans that may need dropping on port %s', len(vlans), port['name'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000545
546 for vlan in vlans:
Steve McIntyre97f5e872015-01-23 18:07:05 +0000547 if vlan != state.config.vland.default_vlan_tag:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000548 s.port_remove_trunk_from_vlan(port['name'], vlan)
Steve McIntyref903b692015-07-09 18:29:05 +0100549
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000550 s.port_set_mode(port['name'], "access")
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000551
552 else:
553 # 2b. We're going from an access port to a trunk port
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000554 s.port_set_access_vlan(port['name'], base_vlan_tag)
555 s.port_set_mode(port['name'], "trunk")
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000556 if 'TrunkWildCardVlans' in s.switch_get_capabilities():
Steve McIntyre7cf80982015-02-12 07:03:40 +0000557 logging.debug('This switch does not need special trunk port handling')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000558 else:
559 vlans = db.all_vlans()
560 for vlan in vlans:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000561 if vlan['tag'] != state.config.vland.default_vlan_tag:
562 s.port_add_trunk_to_vlan(port['name'], vlan['tag'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000563
564 except IOError:
Steve McIntyre38a24e52015-07-21 17:54:58 +0100565 logging.error('set_port_mode failed, resetting switch to recover')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000566 # Bugger. Looks like one of the switch calls above
567 # failed. To undo the changes safely, we'll need to reset
568 # all the config on this switch
569 s.switch_restart() # Will implicitly also close the connection
570 del s
571 raise
572
573 # 3. All seems to have worked so far!
574 s.switch_save_running_config()
575 s.switch_disconnect()
576 del s
577
578 # 4. And update the DB
579 db.set_port_mode(port_id, mode)
580
581 return port_id # If we're successful
582
583 # Complex call, updating both DB and switch state
584 # 1. Check validity of inputs
585 # 2. Update the port config on the switch
586 # 3. If all went OK, save config on the switch
587 # 4. Change details of the port in the DB
588 #
589 # If things fail, we attempt to roll back by rebooting the switch
590 def set_current_vlan(self, state, port_id, vlan_id):
591
Steve McIntyre7cf80982015-02-12 07:03:40 +0000592 logging.debug('set_current_vlan')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000593 db = state.db
594 config = state.config
595
596 # 1. Sanity checks!
597 port = db.get_port_by_id(port_id)
598 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000599 raise NotFoundError("Port ID %d does not exist" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000600 if port['is_locked']:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000601 raise InputError("Port ID %d is locked" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000602 if port['is_trunk']:
Steve McIntyre3bb7bbd2015-02-13 03:06:01 +0000603 raise InputError("Port ID %d is not an access port" % port_id)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000604
605 vlan = db.get_vlan_by_id(vlan_id)
606 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000607 raise NotFoundError("VLAN ID %d does not exist" % vlan_id)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000608
609 # Get the right driver
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000610 switch_name = db.get_switch_name_by_id(port['switch_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000611 s = self.get_switch_driver(switch_name, config)
612
613 # 2. Now start configuring the switch
614 try:
615 s.switch_connect(config.switches[switch_name].username,
616 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000617 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000618 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100619 logging.debug('Failed to talk to switch %s!', switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000620 raise
621
622 try:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000623 s.port_set_access_vlan(port['name'], vlan['tag'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000624 except IOError:
Steve McIntyre38a24e52015-07-21 17:54:58 +0100625 logging.error('set_current_vlan failed, resetting switch to recover')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000626 # Bugger. Looks like one of the switch calls above
627 # failed. To undo the changes safely, we'll need to reset
628 # all the config on this switch
629 s.switch_restart() # Will implicitly also close the connection
630 del s
631 raise
632
633 # 3. All seems to have worked so far!
634 s.switch_save_running_config()
635 s.switch_disconnect()
636 del s
637
638 # 4. And update the DB
639 db.set_current_vlan(port_id, vlan_id)
640
641 return port_id # If we're successful
642
643 # Complex call, updating both DB and switch state
644 # 1. Check validity of input
645 # 2. Update the port config on the switch
646 # 3. If all went OK, save config on the switch
647 # 4. Change details of the port in the DB
648 #
649 # If things fail, we attempt to roll back by rebooting the switch
650 def restore_base_vlan(self, state, port_id):
651
Steve McIntyre7cf80982015-02-12 07:03:40 +0000652 logging.debug('restore_base_vlan')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000653 db = state.db
654 config = state.config
655
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000656 # 1. Sanity checks!
657 port = db.get_port_by_id(port_id)
658 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000659 raise NotFoundError("Port ID %d does not exist" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000660 if port['is_trunk']:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000661 raise InputError("Port ID %d is not an access port" % port_id)
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000662 if port['is_locked']:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000663 raise InputError("Port ID %d is locked" % port_id)
664
665 # Bail out early if we're *already* on the base VLAN. This is
666 # not an error
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000667 if port['current_vlan_id'] == port['base_vlan_id']:
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000668 return port_id
669
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000670 vlan = db.get_vlan_by_id(port['base_vlan_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000671
672 # Get the right driver
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000673 switch_name = db.get_switch_name_by_id(port['switch_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000674 s = self.get_switch_driver(switch_name, config)
675
676 # 2. Now start configuring the switch
677 try:
678 s.switch_connect(config.switches[switch_name].username,
679 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000680 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000681 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100682 logging.debug('Failed to talk to switch %s!', switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000683 raise
684
685 try:
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000686 s.port_set_access_vlan(port['name'], vlan['tag'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000687 except IOError:
Steve McIntyre38a24e52015-07-21 17:54:58 +0100688 logging.error('restore_base_vlan failed, resetting switch to recover')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000689 # Bugger. Looks like one of the switch calls above
690 # failed. To undo the changes safely, we'll need to reset
691 # all the config on this switch
692 s.switch_restart() # Will implicitly also close the connection
693 del s
694 raise
695
696 # 3. All seems to have worked so far!
697 s.switch_save_running_config()
698 s.switch_disconnect()
699 del s
700
701 # 4. And update the DB
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000702 db.set_current_vlan(port_id, port['base_vlan_id'])
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000703
704 return port_id # If we're successful
705
706 # Complex call, updating both DB and switch state
707 # * Check validity of input
708 # * Read all the config from the switch (switch, ports, VLANs)
709 # * Create initial DB entries to match each of those
710 # * Merge VLANs across all switches
711 # * Set up ports appropriately
712 #
713 def auto_import_switch(self, state, switch_name):
714
Steve McIntyre7cf80982015-02-12 07:03:40 +0000715 logging.debug('auto_import_switch')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000716 db = state.db
717 config = state.config
718
Steve McIntyrea8fe1de2015-02-11 17:13:12 +0000719 port_vlans = {}
720
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000721 # 1. Sanity checks!
722 switch_id = db.get_switch_id_by_name(switch_name)
723 if switch_id is not None:
724 raise InputError("Switch name %s already exists in the DB (ID %d)" % (switch_name, switch_id))
725
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000726 if not switch_name in config.switches:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000727 raise NotFoundError("Switch name %s not defined in config" % switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000728
729 # 2. Now start reading config from the switch
730 try:
731 s = self.get_switch_driver(switch_name, config)
732 s.switch_connect(config.switches[switch_name].username,
733 config.switches[switch_name].password,
Steve McIntyre3b655af2014-12-23 13:43:19 +0000734 config.switches[switch_name].enable_password)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000735 except:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100736 logging.debug('Failed to talk to switch %s!', switch_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000737 raise
738
739 # DON'T create the switch record in the DB first - we'll want
Steve McIntyrefc511242014-12-23 22:28:30 +0000740 # to create VLANs on *other* switches, and it's easier to do
741 # that before we've added our new switch
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000742
743 new_vlan_tags = []
744
745 # Grab the VLANs defined on this switch
746 vlan_tags = s.vlan_get_list()
Steve McIntyrefc511242014-12-23 22:28:30 +0000747
Steve McIntyre5fa22652015-04-01 18:01:45 +0100748 logging.debug(' found %d vlans on the switch', len(vlan_tags))
Steve McIntyrefc511242014-12-23 22:28:30 +0000749
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000750 for vlan_tag in vlan_tags:
751 vlan_name = s.vlan_get_name(vlan_tag)
752
753 # If a VLAN is already in the database, then that's easy -
754 # we can just ignore it. However, we have to check that
755 # there is not a different name for the existing VLAN tag
Steve McIntyreb1529072014-12-23 17:17:22 +0000756 # - bail out if so... UNLESS we're looking at the default
757 # VLAN
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000758 #
759 # If this VLAN tag is not already in the DB, we'll need to
760 # add it there and to all the other switches (and their
761 # trunk ports!) too.
Steve McIntyrefc511242014-12-23 22:28:30 +0000762 vlan_id = db.get_vlan_id_by_tag(vlan_tag)
Steve McIntyre6c09c0c2015-07-17 17:20:01 +0100763 if vlan_id != state.default_vlan_id:
Steve McIntyreb1529072014-12-23 17:17:22 +0000764 if vlan_id is not None:
765 vlan_db_name = db.get_vlan_name_by_id(vlan_id)
766 if vlan_name != vlan_db_name:
767 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 +0000768
Steve McIntyreb1529072014-12-23 17:17:22 +0000769 else:
770 # OK, we'll need to set up the new VLAN now. It can't
771 # be a base VLAN - switches don't have such a concept!
772 # Rather than create individually here, add to a
773 # list. *Only* once we've worked through all the
774 # switch's VLANs successfully (checking for existing
775 # records and possible clashes!) should we start
776 # committing changes
777 new_vlan_tags.append(vlan_tag)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000778
779 # Now create the VLAN DB entries
780 for vlan_tag in new_vlan_tags:
781 vlan_name = s.vlan_get_name(vlan_tag)
782 vlan_id = self.create_vlan(state, vlan_name, vlan_tag, False)
783
784 # *Now* add this switch itself to the database, after we've
785 # worked on all the other switches
786 switch_id = db.create_switch(switch_name)
787
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000788 # And now the ports
789 trunk_ports = []
790 ports = s.switch_get_port_names()
Steve McIntyre5fa22652015-04-01 18:01:45 +0100791 logging.debug(' found %d ports on the switch', len(ports))
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000792 for port_name in ports:
Steve McIntyre5fa22652015-04-01 18:01:45 +0100793 logging.debug(' trying to import port %s', port_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000794 port_id = None
795 port_mode = s.port_get_mode(port_name)
Steve McIntyreea753972015-08-05 13:52:48 +0100796 port_number = s.port_map_name_to_number(port_name)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000797 if port_mode == 'access':
798 # Access ports are easy - just create the port, and
799 # set both the current and base VLANs to the current
800 # VLAN on the switch. We'll end up changing this after
801 # import if needed.
Steve McIntyrea8fe1de2015-02-11 17:13:12 +0000802 port_vlans[port_name] = (s.port_get_access_vlan(port_name),)
803 port_vlan_id = db.get_vlan_id_by_tag(port_vlans[port_name][0])
Steve McIntyreea753972015-08-05 13:52:48 +0100804 port_id = db.create_port(switch_id, port_name, port_number,
Steve McIntyre6f17b102014-12-24 02:18:08 +0000805 port_vlan_id, port_vlan_id)
Steve McIntyre5fa22652015-04-01 18:01:45 +0100806 logging.debug(' access port, VLAN %d', int(port_vlans[port_name][0]))
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000807 # Nothing further needed
808 elif port_mode == 'trunk':
Steve McIntyre7cf80982015-02-12 07:03:40 +0000809 logging.debug(' trunk port, VLANs:')
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000810 # Trunk ports are a little more involved. First,
811 # create the port in the DB, setting the VLANs to the
812 # first VLAN found on the trunk port. This will *also*
813 # be in access mode by default, and unlocked.
Steve McIntyrea8fe1de2015-02-11 17:13:12 +0000814 port_vlans[port_name] = s.port_get_trunk_vlan_list(port_name)
Steve McIntyre7cf80982015-02-12 07:03:40 +0000815 logging.debug(port_vlans[port_name])
Steve McIntyrea8fe1de2015-02-11 17:13:12 +0000816 if port_vlans[port_name] == [] or port_vlans[port_name] is None or 'ALL' in port_vlans[port_name]:
817 port_vlans[port_name] = (state.config.vland.default_vlan_tag,)
818 port_vlan_id = db.get_vlan_id_by_tag(port_vlans[port_name][0])
Steve McIntyreea753972015-08-05 13:52:48 +0100819 port_id = db.create_port(switch_id, port_name, port_number,
Steve McIntyre6f17b102014-12-24 02:18:08 +0000820 port_vlan_id, port_vlan_id)
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000821 # Append to a list of trunk ports that we will need to
822 # modify once we're done
823 trunk_ports.append(port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000824 else:
Steve McIntyre857d89f2015-07-24 16:22:37 +0100825 # We've found a port mode we don't want, e.g. the
826 # "dynamic auto" on a Cisco Catalyst. Handle that here
827 # - tell the switch to set that port to access and
828 # handle accordingly.
829 s.port_set_mode(port_name, 'access')
830 port_vlans[port_name] = (s.port_get_access_vlan(port_name),)
831 port_vlan_id = db.get_vlan_id_by_tag(port_vlans[port_name][0])
Steve McIntyreea753972015-08-05 13:52:48 +0100832 port_id = db.create_port(switch_id, port_name, port_number,
Steve McIntyre857d89f2015-07-24 16:22:37 +0100833 port_vlan_id, port_vlan_id)
834 logging.debug(' Found port in %s mode', port_mode)
835 logging.debug(' Forcing to access mode, VLAN %d', int(port_vlans[port_name][0]))
836 port_mode = "access"
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000837
Steve McIntyre5fa22652015-04-01 18:01:45 +0100838 logging.debug(" Added port %s, got port ID %d", port_name, port_id)
Steve McIntyrefc511242014-12-23 22:28:30 +0000839
Steve McIntyre574e3342015-01-23 18:08:33 +0000840 db.set_port_mode(port_id, port_mode)
841
Steve McIntyre6feea572015-02-09 15:49:20 +0000842 # Make sure this switch has all the VLANs we need
843 for vlan in db.all_vlans():
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000844 if vlan['tag'] != state.config.vland.default_vlan_tag:
845 if not vlan['tag'] in vlan_tags:
846 logging.debug("Adding VLAN tag %d to this switch", vlan['tag'])
847 s.vlan_create(vlan['tag'])
848 s.vlan_set_name(vlan['tag'], vlan['name'])
Steve McIntyre6feea572015-02-09 15:49:20 +0000849
Steve McIntyrefc511242014-12-23 22:28:30 +0000850 # Now, on each trunk port on the switch, we need to add all
851 # the VLANs already configured across our system
852 if not 'TrunkWildCardVlans' in s.switch_get_capabilities():
853 for port_id in trunk_ports:
854 port = db.get_port_by_id(port_id)
Steve McIntyreb826fc72015-07-27 17:57:40 +0100855
Steve McIntyrefc511242014-12-23 22:28:30 +0000856 for vlan in db.all_vlans():
Steve McIntyree6ff7f02018-02-05 18:52:59 +0000857 if vlan['vlan_id'] != state.default_vlan_id:
858 if not vlan['tag'] in port_vlans[port['name']]:
859 logging.debug("Adding allowed VLAN tag %d to trunk port %s", vlan['tag'], port['name'])
860 s.port_add_trunk_to_vlan(port['name'], vlan['tag'])
Steve McIntyrefc511242014-12-23 22:28:30 +0000861
Steve McIntyrefc511242014-12-23 22:28:30 +0000862 # Done with this switch \o/
863 s.switch_save_running_config()
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000864 s.switch_disconnect()
865 del s
866
Steve McIntyre4b4ab652014-12-22 17:19:09 +0000867 ret = {}
Steve McIntyre5f6f85e2014-12-22 16:42:28 +0000868 ret['switch_id'] = switch_id
869 ret['num_ports_added'] = len(ports)
870 ret['num_vlans_added'] = len(new_vlan_tags)
871 return ret # If we're successful