blob: 527e5ec08e5af01da728b66039d2b414031ed7ba [file] [log] [blame]
Dave Pigott281203e2014-09-17 23:45:02 +01001#! /usr/bin/python
2
Steve McIntyrec03d68d2016-03-24 17:38:34 +00003# Copyright 2014-2016 Linaro Limited
Steve McIntyrec4890132015-08-07 15:19:11 +01004# Authors: Dave Pigott <dave.pigott@linaro.org>,
5# Steve McIntyre <steve.mcintyre@linaro.org>
Dave Pigott281203e2014-09-17 23:45:02 +01006#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20# MA 02110-1301, USA.
21
22import psycopg2
23import psycopg2.extras
Steve McIntyre6a618162014-12-10 16:47:07 +000024import datetime, os, sys
Steve McIntyre7cf80982015-02-12 07:03:40 +000025import logging
Steve McIntyre6a618162014-12-10 16:47:07 +000026
Steve McIntyrec4890132015-08-07 15:19:11 +010027TRUNK_ID_NONE = -1
28
Steve McIntyre6a618162014-12-10 16:47:07 +000029if __name__ == '__main__':
30 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
31 sys.path.insert(0, vlandpath)
32 sys.path.insert(0, "%s/.." % vlandpath)
33
Steve McIntyreb01959f2016-03-22 17:02:39 +000034from errors import CriticalError, InputError, NotFoundError
Dave Pigott281203e2014-09-17 23:45:02 +010035
36class VlanDB:
Steve McIntyreea343aa2015-10-23 17:46:17 +010037 def __init__(self, db_name="vland", username="vland", readonly=True):
Dave Pigott281203e2014-09-17 23:45:02 +010038 try:
Steve McIntyree38f6222014-11-27 15:09:49 +000039 self.connection = psycopg2.connect(database=db_name, user=username)
Steve McIntyreb09ed282014-12-02 17:59:35 +000040 self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
Steve McIntyreea343aa2015-10-23 17:46:17 +010041 if not readonly:
42 self._init_state()
Dave Pigott281203e2014-09-17 23:45:02 +010043 except Exception as e:
Steve McIntyre5fa22652015-04-01 18:01:45 +010044 logging.error("Failed to access database: %s", e)
Steve McIntyre7cf80982015-02-12 07:03:40 +000045 raise
Dave Pigott281203e2014-09-17 23:45:02 +010046
47 def __del__(self):
48 self.cursor.close()
49 self.connection.close()
50
Steve McIntyreea343aa2015-10-23 17:46:17 +010051 # Create the state table (if needed) and add its only record
52 def _init_state(self):
53 try:
54 sql = "SELECT * FROM state"
55 self.cursor.execute(sql)
56 self.cursor.execute('DELETE FROM state')
57 except psycopg2.ProgrammingError:
58 self.connection.commit() # state doesn't exist; clear error
59 sql = "CREATE TABLE state (last_modified TIMESTAMP)"
60 self.cursor.execute(sql)
61 sql = "INSERT INTO state (last_modified) VALUES (%s)"
62 data = (datetime.datetime.now(), )
63 self.cursor.execute(sql, data)
64 self.connection.commit()
65
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000066 # Create a new switch in the database. Switches are really simple
67 # devices - they're just containers for ports.
68 #
69 # Constraints:
70 # Switches must be uniquely named
Steve McIntyredbd7fe52014-11-27 16:54:29 +000071 def create_switch(self, name):
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000072
Steve McIntyre549435f2014-12-05 15:42:46 +000073 switch_id = self.get_switch_id_by_name(name)
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000074 if switch_id is not None:
75 raise InputError("Switch name %s already exists" % name)
76
Dave Pigott2649a1a2014-09-18 00:04:49 +010077 try:
Steve McIntyredbd7fe52014-11-27 16:54:29 +000078 sql = "INSERT INTO switch (name) VALUES (%s) RETURNING switch_id"
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000079 data = (name, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +000080 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +010081 switch_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +010082 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +010083 self.connection.commit()
84 except:
85 self.connection.rollback()
86 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +000087
Dave Pigott281203e2014-09-17 23:45:02 +010088 return switch_id
89
Steve McIntyrec4890132015-08-07 15:19:11 +010090 # Create a new port in the database. Three of the fields are
91 # created with default values (is_locked, is_trunk, trunk_id)
92 # here, and should be updated separately if desired. For the
93 # current_vlan_id and base_vlan_id fields, *BE CAREFUL* that you
94 # have already looked up the correct VLAN_ID for each. This is
95 # *NOT* the same as the VLAN tag (likely to be 1). You Have Been
96 # Warned!
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000097 #
98 # Constraints:
99 # 1. The switch referred to must already exist
100 # 2. The VLANs mentioned here must already exist
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000101 # 3. (Switch/name) must be unique
Steve McIntyreea753972015-08-05 13:52:48 +0100102 # 4. (Switch/number) must be unique
103 def create_port(self, switch_id, name, number, current_vlan_id, base_vlan_id):
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000104
Steve McIntyre549435f2014-12-05 15:42:46 +0000105 switch = self.get_switch_by_id(switch_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000106 if switch is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000107 raise NotFoundError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000108
109 for vlan_id in (current_vlan_id, base_vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000110 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000111 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000112 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000113
114 port_id = self.get_port_by_switch_and_name(switch_id, name)
115 if port_id is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000116 raise InputError("Already have a port %s on switch ID %d" % (name, int(switch_id)))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000117
Steve McIntyreea753972015-08-05 13:52:48 +0100118 port_id = self.get_port_by_switch_and_number(switch_id, int(number))
119 if port_id is not None:
120 raise InputError("Already have a port %d on switch ID %d" % (int(number), int(switch_id)))
121
Dave Pigott2649a1a2014-09-18 00:04:49 +0100122 try:
Steve McIntyrec4890132015-08-07 15:19:11 +0100123 sql = "INSERT INTO port (name, number, switch_id, is_locked, is_trunk, current_vlan_id, base_vlan_id, trunk_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING port_id"
Steve McIntyreea753972015-08-05 13:52:48 +0100124 data = (name, number, switch_id,
Steve McIntyre90a4a972014-11-28 16:50:56 +0000125 False, False,
Steve McIntyrec4890132015-08-07 15:19:11 +0100126 current_vlan_id, base_vlan_id, TRUNK_ID_NONE)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000127 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100128 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100129 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100130 self.connection.commit()
131 except:
132 self.connection.rollback()
133 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000134
Dave Pigott281203e2014-09-17 23:45:02 +0100135 return port_id
136
Steve McIntyreb005a2f2014-11-28 18:23:05 +0000137 # Create a new vlan in the database. We locally add a creation
138 # timestamp, for debug purposes. If vlans seems to be sticking
139 # around, we'll be able to see when they were created.
Steve McIntyre31b2df52014-12-02 12:37:54 +0000140 #
141 # Constraints:
142 # Names and tags must be unique
Steve McIntyre57f45912014-12-08 14:43:00 +0000143 # Tags must be in the range 1-4095 (802.1q spec)
Steve McIntyre49777e72014-12-08 16:00:46 +0000144 # Names can be any free-form text, length 1-32 characters
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000145 def create_vlan(self, name, tag, is_base_vlan):
Steve McIntyre31b2df52014-12-02 12:37:54 +0000146
Steve McIntyre57f45912014-12-08 14:43:00 +0000147 if int(tag) < 1 or int(tag) > 4095:
Steve McIntyre49777e72014-12-08 16:00:46 +0000148 raise InputError("VLAN tag %d is outside of the valid range (1-4095)" % int(tag))
149
150 if (len(name) < 1) or (len(name) > 32):
151 raise InputError("VLAN name %s is invalid (must be 1-32 chars)" % name)
Steve McIntyre57f45912014-12-08 14:43:00 +0000152
Steve McIntyrea34c1812014-12-05 15:27:55 +0000153 vlan_id = self.get_vlan_id_by_name(name)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000154 if vlan_id is not None:
155 raise InputError("VLAN name %s is already in use" % name)
156
Steve McIntyre50eb0602014-12-05 15:29:04 +0000157 vlan_id = self.get_vlan_id_by_tag(tag)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000158 if vlan_id is not None:
159 raise InputError("VLAN tag %d is already in use" % int(tag))
160
Dave Pigott2649a1a2014-09-18 00:04:49 +0100161 try:
Steve McIntyred74d97c2014-11-28 14:44:39 +0000162 dt = datetime.datetime.now()
Steve McIntyre4b918132014-12-05 17:04:46 +0000163 sql = "INSERT INTO vlan (name, tag, is_base_vlan, creation_time) VALUES (%s, %s, %s, %s) RETURNING vlan_id"
164 data = (name, tag, is_base_vlan, dt)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000165 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100166 vlan_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100167 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100168 self.connection.commit()
169 except:
170 self.connection.rollback()
171 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000172
Dave Pigott281203e2014-09-17 23:45:02 +0100173 return vlan_id
174
Steve McIntyrec4890132015-08-07 15:19:11 +0100175 # Create a new trunk in the database, linking two ports. Trunks
176 # are really simple objects for our use - they're just containers
177 # for 2 ports.
178 #
179 # Constraints:
180 # 1. Both ports listed must already exist.
181 # 2. Both ports must be in trunk mode.
182 # 3. Both must not be locked.
183 # 4. Both must not already be in a trunk.
184 def create_trunk(self, port_id1, port_id2):
185
186 for port_id in (port_id1, port_id2):
187 port = self.get_port_by_id(int(port_id))
188 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000189 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100190 if not port.is_trunk:
191 raise InputError("Port ID %d is not in trunk mode" % int(port_id))
192 if port.is_locked:
193 raise InputError("Port ID %d is locked" % int(port_id))
194 if port.trunk_id != TRUNK_ID_NONE:
195 raise InputError("Port ID %d is already on trunk ID %d" % (int(port_id), int(port.trunk_id)))
196
197 try:
198 # Add the trunk itself
199 dt = datetime.datetime.now()
200 sql = "INSERT INTO trunk (creation_time) VALUES (%s) RETURNING trunk_id"
201 data = (dt, )
202 self.cursor.execute(sql, data)
203 trunk_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100204 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyrec4890132015-08-07 15:19:11 +0100205 self.connection.commit()
206 # And update the ports
207 for port_id in (port_id1, port_id2):
208 self._set_port_trunk(port_id, trunk_id)
209 except:
210 self.delete_trunk(trunk_id)
211 raise
212
213 return trunk_id
214
Steve McIntyre2d685c72014-12-08 15:24:12 +0000215 # Internal helper function
Dave Pigott281203e2014-09-17 23:45:02 +0100216 def _delete_row(self, table, field, value):
Dave Pigott2649a1a2014-09-18 00:04:49 +0100217 try:
Steve McIntyree03de002014-12-02 17:14:14 +0000218 sql = "DELETE FROM %s WHERE %s = %s" % (table, field, '%s')
219 data = (value,)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000220 self.cursor.execute(sql, data)
Steve McIntyreea343aa2015-10-23 17:46:17 +0100221 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100222 self.connection.commit()
223 except:
224 self.connection.rollback()
225 raise
Dave Pigott281203e2014-09-17 23:45:02 +0100226
Steve McIntyre388f0e22014-12-02 17:19:04 +0000227 # Delete the specified switch
228 #
229 # Constraints:
230 # 1. The switch must exist
231 # 2. The switch may not be referenced by any ports -
232 # delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100233 def delete_switch(self, switch_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000234 switch = self.get_switch_by_id(switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000235 if switch is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000236 raise NotFoundError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyre388f0e22014-12-02 17:19:04 +0000237 ports = self.get_ports_by_switch(switch_id)
238 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000239 raise InputError("Cannot delete switch ID %d when it still has %d ports" %
240 (int(switch_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100241 self._delete_row("switch", "switch_id", switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000242 return switch_id
Dave Pigott281203e2014-09-17 23:45:02 +0100243
Steve McIntyre6a968622014-12-02 18:01:41 +0000244 # Delete the specified port
245 #
246 # Constraints:
247 # 1. The port must exist
248 # 2. The port must not be locked
Dave Pigott281203e2014-09-17 23:45:02 +0100249 def delete_port(self, port_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000250 port = self.get_port_by_id(port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000251 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000252 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre6a968622014-12-02 18:01:41 +0000253 if port.is_locked:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000254 raise InputError("Cannot delete port ID %d as it is locked" % int(port_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100255 self._delete_row("port", "port_id", port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000256 return port_id
Dave Pigott281203e2014-09-17 23:45:02 +0100257
Steve McIntyre14552ac2014-12-05 15:23:57 +0000258 # Delete the specified VLAN
259 #
260 # Constraints:
Steve McIntyre2a5df972015-08-07 15:19:40 +0100261 # 1. The VLAN must exist
Steve McIntyre14552ac2014-12-05 15:23:57 +0000262 # 2. The VLAN may not contain any ports - move or delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100263 def delete_vlan(self, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000264 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000265 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000266 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000267 ports = self.get_ports_by_current_vlan(vlan_id)
268 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000269 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
270 (int(vlan_id), len(ports)))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000271 ports = self.get_ports_by_base_vlan(vlan_id)
272 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000273 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
274 (int(vlan_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100275 self._delete_row("vlan", "vlan_id", vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000276 return vlan_id
Dave Pigott281203e2014-09-17 23:45:02 +0100277
Steve McIntyrec4890132015-08-07 15:19:11 +0100278 # Delete the specified trunk
279 #
280 # Constraints:
281 # 1. The trunk must exist
282 #
283 # Any ports attached will be detached (i.e. moved to trunk TRUNK_ID_NONE)
284 def delete_trunk(self, trunk_id):
285 trunk = self.get_trunk_by_id(trunk_id)
286 if trunk is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000287 raise NotFoundError("Trunk ID %d does not exist" % int(trunk_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100288 ports = self.get_ports_by_trunk(trunk_id)
289 for port_id in ports:
290 self._set_port_trunk(port_id, TRUNK_ID_NONE)
291 self._delete_row("trunk", "trunk_id", trunk_id)
292 return trunk_id
293
Steve McIntyre6c4f33f2015-08-03 19:25:07 +0100294 # Find the lowest unused VLAN tag and return it
295 #
296 # Constraints:
297 # None
298 def find_lowest_unused_vlan_tag(self):
299 sql = "SELECT tag FROM vlan ORDER BY tag ASC"
300 self.cursor.execute(sql,)
301
302 # Walk through the list, looking for gaps
303 last = 1
304 result = None
305
306 for record in self.cursor:
307 if (record[0] - last) > 1:
308 result = last + 1
309 break
310 last = record[0]
311
312 if result is None:
313 result = last + 1
314
315 if result > 4093:
316 raise CriticalError("Can't find any VLAN tags remaining for allocation!")
317
318 return result
319
Steve McIntyre2d685c72014-12-08 15:24:12 +0000320 # Grab one column from one row of a query on one column; useful as
321 # a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100322 def _get_element(self, select_field, table, compare_field, value):
Steve McIntyre95614c22014-11-28 17:02:44 +0000323
Steve McIntyredc173072016-08-25 18:29:10 +0100324 if value is None:
325 raise ValueError("Asked to look up using None as a key in %s" % compare_field)
326
Steve McIntyre95614c22014-11-28 17:02:44 +0000327 # We really want to use psycopg's type handling deal with the
328 # (potentially) user-supplied data in the value field, so we
329 # have to pass (sql,data) through to cursor.execute. However,
330 # we can't have psycopg do all the argument substitution here
331 # as it will quote all the params like the table name. That
332 # doesn't work. So, we substitute a "%s" for "%s" here so we
333 # keep it after python's own string substitution.
334 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
335
336 # Now, the next icky thing: we need to make sure that we're
337 # passing a dict so that psycopg2 can pick it apart properly
338 # for its own substitution code. We force this with the
339 # trailing comma here
340 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000341 self.cursor.execute(sql, data)
Steve McIntyre95614c22014-11-28 17:02:44 +0000342
Steve McIntyre58b57a42014-12-02 13:09:21 +0000343 if self.cursor.rowcount > 0:
344 return self.cursor.fetchone()[0]
345 else:
Steve McIntyrec831f9c2014-12-02 12:38:54 +0000346 return None
Dave Pigott281203e2014-09-17 23:45:02 +0100347
Steve McIntyre2d685c72014-12-08 15:24:12 +0000348 # Grab one column from one row of a query on 2 columns; useful as
349 # a quick wrapper
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000350 def _get_element2(self, select_field, table, compare_field1, value1, compare_field2, value2):
351
Steve McIntyredc173072016-08-25 18:29:10 +0100352 if value1 is None:
353 raise ValueError("Asked to look up using None as a key in %s" % compare_field1)
354 if value2 is None:
355 raise ValueError("Asked to look up using None as a key in %s" % compare_field2)
356
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000357 # We really want to use psycopg's type handling deal with the
358 # (potentially) user-supplied data in the value field, so we
359 # have to pass (sql,data) through to cursor.execute. However,
360 # we can't have psycopg do all the argument substitution here
361 # as it will quote all the params like the table name. That
362 # doesn't work. So, we substitute a "%s" for "%s" here so we
363 # keep it after python's own string substitution.
364 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s" % (select_field, table, compare_field1, "%s", compare_field2, "%s")
365
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000366 data = (value1, value2)
367 self.cursor.execute(sql, data)
368
369 if self.cursor.rowcount > 0:
370 return self.cursor.fetchone()[0]
371 else:
372 return None
373
Steve McIntyre2d685c72014-12-08 15:24:12 +0000374 # Grab one column from multiple rows of a query; useful as a quick
375 # wrapper
Steve McIntyre05e3e622015-09-25 01:29:18 +0100376 def _get_multi_elements(self, select_field, table, compare_field, value, sort_field):
Steve McIntyree9da15e2014-12-05 15:22:41 +0000377
Steve McIntyredc173072016-08-25 18:29:10 +0100378 if value is None:
379 raise ValueError("Asked to look up using None as a key in %s" % compare_field)
380
Steve McIntyree9da15e2014-12-05 15:22:41 +0000381 # We really want to use psycopg's type handling deal with the
382 # (potentially) user-supplied data in the value field, so we
383 # have to pass (sql,data) through to cursor.execute. However,
384 # we can't have psycopg do all the argument substitution here
385 # as it will quote all the params like the table name. That
386 # doesn't work. So, we substitute a "%s" for "%s" here so we
387 # keep it after python's own string substitution.
Steve McIntyre05e3e622015-09-25 01:29:18 +0100388 sql = "SELECT %s FROM %s WHERE %s = %s ORDER BY %s ASC" % (select_field, table, compare_field, "%s", sort_field)
Steve McIntyree9da15e2014-12-05 15:22:41 +0000389
390 # Now, the next icky thing: we need to make sure that we're
391 # passing a dict so that psycopg2 can pick it apart properly
392 # for its own substitution code. We force this with the
393 # trailing comma here
394 data = (value, )
395 self.cursor.execute(sql, data)
396
397 if self.cursor.rowcount > 0:
398 results = []
399 for record in self.cursor:
Steve McIntyre52509622014-12-02 17:13:15 +0000400 results.append(record[0])
Steve McIntyree9da15e2014-12-05 15:22:41 +0000401 return results
Steve McIntyre52509622014-12-02 17:13:15 +0000402 else:
403 return None
404
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000405 # Grab one column from multiple rows of a 2-part query; useful as
406 # a wrapper
Steve McIntyre05e3e622015-09-25 01:29:18 +0100407 def _get_multi_elements2(self, select_field, table, compare_field1, value1, compare_field2, value2, sort_field):
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000408
Steve McIntyredc173072016-08-25 18:29:10 +0100409 if value1 is None:
410 raise ValueError("Asked to look up using None as a key in %s" % compare_field1)
411 if value2 is None:
412 raise ValueError("Asked to look up using None as a key in %s" % compare_field2)
413
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000414 # We really want to use psycopg's type handling deal with the
415 # (potentially) user-supplied data in the value field, so we
416 # have to pass (sql,data) through to cursor.execute. However,
417 # we can't have psycopg do all the argument substitution here
418 # as it will quote all the params like the table name. That
419 # doesn't work. So, we substitute a "%s" for "%s" here so we
420 # keep it after python's own string substitution.
Steve McIntyre05e3e622015-09-25 01:29:18 +0100421 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s ORDER by %s ASC" % (select_field, table, compare_field1, "%s", compare_field2, "%s", sort_field)
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000422
423 data = (value1, value2)
424 self.cursor.execute(sql, data)
425
426 if self.cursor.rowcount > 0:
427 results = []
428 for record in self.cursor:
429 results.append(record[0])
430 return results
431 else:
432 return None
433
Steve McIntyre2d685c72014-12-08 15:24:12 +0000434 # Simple lookup: look up a switch by ID, and return all the
435 # details of that switch.
436 #
437 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000438 def get_switch_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000439 return self._get_row("switch", "switch_id", int(switch_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000440
Steve McIntyre2d685c72014-12-08 15:24:12 +0000441 # Simple lookup: look up a switch by name, and return the ID of
442 # that switch.
443 #
444 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000445 def get_switch_id_by_name(self, name):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100446 return self._get_element("switch_id", "switch", "name", name)
Dave Pigott281203e2014-09-17 23:45:02 +0100447
Steve McIntyre2d685c72014-12-08 15:24:12 +0000448 # Simple lookup: look up a switch by ID, and return the name of
449 # that switch.
450 #
451 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000452 def get_switch_name_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000453 return self._get_element("name", "switch", "switch_id", int(switch_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100454
Steve McIntyre2d685c72014-12-08 15:24:12 +0000455 # Simple lookup: look up a port by ID, and return all the details
456 # of that port.
457 #
458 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000459 def get_port_by_id(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000460 return self._get_row("port", "port_id", int(port_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000461
Steve McIntyre2d685c72014-12-08 15:24:12 +0000462 # Simple lookup: look up a switch by ID, and return the IDs of all
463 # the ports on that switch.
464 #
465 # Returns None on failure.
Steve McIntyreb67f3912014-12-02 17:14:36 +0000466 def get_ports_by_switch(self, switch_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100467 return self._get_multi_elements("port_id", "port", "switch_id", int(switch_id), "port_id")
Steve McIntyreb67f3912014-12-02 17:14:36 +0000468
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000469 # More complex lookup: look up all the trunk ports on a switch by
470 # ID
471 #
472 # Returns None on failure.
473 def get_trunk_port_names_by_switch(self, switch_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100474 return self._get_multi_elements2("name", "port", "switch_id", int(switch_id), "is_trunk", True, "port_id")
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000475
Steve McIntyre2d685c72014-12-08 15:24:12 +0000476 # Simple lookup: look up a port by its name and its parent switch
477 # by ID, and return the ID of the port.
478 #
479 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000480 def get_port_by_switch_and_name(self, switch_id, name):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000481 return self._get_element2("port_id", "port", "switch_id", int(switch_id), "name", name)
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000482
Steve McIntyre45f55012015-08-05 13:55:15 +0100483 # Simple lookup: look up a port by its external name and its
484 # parent switch by ID, and return the ID of the port.
485 #
486 # Returns None on failure.
487 def get_port_by_switch_and_number(self, switch_id, number):
488 return self._get_element2("port_id", "port", "switch_id", int(switch_id), "number", int(number))
489
Steve McIntyre2d685c72014-12-08 15:24:12 +0000490 # Simple lookup: look up a port by ID, and return the current VLAN
491 # id of that port.
492 #
493 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000494 def get_current_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000495 return self._get_element("current_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000496
Steve McIntyre2d685c72014-12-08 15:24:12 +0000497 # Simple lookup: look up a port by ID, and return the base VLAN
498 # id of that port.
499 #
500 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000501 def get_base_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000502 return self._get_element("base_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000503
Steve McIntyre2d685c72014-12-08 15:24:12 +0000504 # Simple lookup: look up a current VLAN by ID, and return the IDs
505 # of all the ports on that VLAN.
506 #
507 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000508 def get_ports_by_current_vlan(self, vlan_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100509 return self._get_multi_elements("port_id", "port", "current_vlan_id", int(vlan_id), "port_id")
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000510
Steve McIntyre2d685c72014-12-08 15:24:12 +0000511 # Simple lookup: look up a base VLAN by ID, and return the IDs
512 # of all the ports on that VLAN.
513 #
514 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000515 def get_ports_by_base_vlan(self, vlan_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100516 return self._get_multi_elements("port_id", "port", "base_vlan_id", int(vlan_id), "port_id")
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000517
Steve McIntyrec4890132015-08-07 15:19:11 +0100518 # Simple lookup: look up a trunk by ID, and return the IDs of the
519 # ports on both ends of that trunk.
520 #
521 # Returns None on failure.
522 def get_ports_by_trunk(self, trunk_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100523 return self._get_multi_elements("port_id", "port", "trunk_id", int(trunk_id), "port_id")
Steve McIntyrec4890132015-08-07 15:19:11 +0100524
Steve McIntyre2d685c72014-12-08 15:24:12 +0000525 # Simple lookup: look up a VLAN by ID, and return all the details
526 # of that VLAN.
527 #
528 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000529 def get_vlan_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000530 return self._get_row("vlan", "vlan_id", int(vlan_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000531
Steve McIntyre2d685c72014-12-08 15:24:12 +0000532 # Simple lookup: look up a VLAN by name, and return the ID of that
533 # VLAN.
534 #
535 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000536 def get_vlan_id_by_name(self, name):
537 return self._get_element("vlan_id", "vlan", "name", name)
538
Steve McIntyre2d685c72014-12-08 15:24:12 +0000539 # Simple lookup: look up a VLAN by tag, and return the ID of that
540 # VLAN.
541 #
542 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000543 def get_vlan_id_by_tag(self, tag):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000544 return self._get_element("vlan_id", "vlan", "tag", int(tag))
Steve McIntyref3655062014-12-05 15:34:39 +0000545
Steve McIntyre2d685c72014-12-08 15:24:12 +0000546 # Simple lookup: look up a VLAN by ID, and return the name of that
547 # VLAN.
548 #
549 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000550 def get_vlan_name_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000551 return self._get_element("name", "vlan", "vlan_id", int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100552
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000553 # Simple lookup: look up a VLAN by ID, and return the tag of that
554 # VLAN.
555 #
556 # Returns None on failure.
557 def get_vlan_tag_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000558 return self._get_element("tag", "vlan", "vlan_id", int(vlan_id))
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000559
Steve McIntyrec4890132015-08-07 15:19:11 +0100560 # Simple lookup: look up a trunk by ID, and return all the details
561 # of that trunk.
562 #
563 # Returns None on failure.
564 def get_trunk_by_id(self, trunk_id):
565 return self._get_row("trunk", "trunk_id", int(trunk_id))
566
Steve McIntyreea343aa2015-10-23 17:46:17 +0100567 # Get the last-modified time for the database
568 def get_last_modified_time(self):
569 sql = "SELECT last_modified FROM state"
570 self.cursor.execute(sql)
Steve McIntyreaf24aaa2015-10-23 17:59:04 +0100571 return self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100572
Steve McIntyre2d685c72014-12-08 15:24:12 +0000573 # Grab one row of a query on one column; useful as a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100574 def _get_row(self, table, field, value):
Steve McIntyree0b842a2014-11-28 18:23:47 +0000575
576 # We really want to use psycopg's type handling deal with the
577 # (potentially) user-supplied data in the value field, so we
578 # have to pass (sql,data) through to cursor.execute. However,
579 # we can't have psycopg do all the argument substitution here
580 # as it will quote all the params like the table name. That
581 # doesn't work. So, we substitute a "%s" for "%s" here so we
582 # keep it after python's own string substitution.
583 sql = "SELECT * FROM %s WHERE %s = %s" % (table, field, "%s")
584
585 # Now, the next icky thing: we need to make sure that we're
586 # passing a dict so that psycopg2 can pick it apart properly
587 # for its own substitution code. We force this with the
588 # trailing comma here
589 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000590 self.cursor.execute(sql, data)
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100591 return self.cursor.fetchone()
592
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000593 # (Un)Lock a port in the database. This can only be done through
594 # the admin interface, and will stop API users from modifying
595 # settings on the port. Use this to lock down ports that are used
596 # for PDUs and other core infrastructure
597 def set_port_is_locked(self, port_id, is_locked):
Steve McIntyre8c64d952014-12-05 16:22:44 +0000598 port = self.get_port_by_id(port_id)
599 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000600 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000601 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000602 sql = "UPDATE port SET is_locked=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000603 data = (is_locked, port_id)
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000604 self.cursor.execute(sql, data)
605 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100606 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000607 self.connection.commit()
608 except:
609 self.connection.rollback()
610 raise
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100611 return port_id
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000612
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000613 # Set the mode of a port in the database. Valid values for mode
614 # are "trunk" and "access"
615 def set_port_mode(self, port_id, mode):
616 port = self.get_port_by_id(port_id)
617 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000618 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000619 if mode == "access":
620 is_trunk = False
621 elif mode == "trunk":
622 is_trunk = True
623 else:
624 raise InputError("Port mode %s is not valid" % mode)
625 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000626 sql = "UPDATE port SET is_trunk=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000627 data = (is_trunk, port_id)
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000628 self.cursor.execute(sql, data)
629 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100630 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000631 self.connection.commit()
632 except:
633 self.connection.rollback()
634 raise
635 return port_id
636
Steve McIntyre2d685c72014-12-08 15:24:12 +0000637 # Set the current vlan of a port in the database. The VLAN is
638 # passed by ID.
639 #
640 # Constraints:
641 # 1. The port must already exist
642 # 2. The port must not be a trunk port
643 # 3. The port must not be locked
644 # 1. The VLAN must already exist in the database
Steve McIntyre9eb78652014-12-05 17:51:53 +0000645 def set_current_vlan(self, port_id, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000646 port = self.get_port_by_id(port_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000647 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000648 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100649
Steve McIntyre6dd00be2014-12-05 17:29:35 +0000650 if port.is_trunk or port.is_locked:
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100651 raise CriticalError("The port is locked")
652
Steve McIntyre549435f2014-12-05 15:42:46 +0000653 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000654 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000655 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100656
657 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000658 sql = "UPDATE port SET current_vlan_id=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000659 data = (vlan_id, port_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000660 self.cursor.execute(sql, data)
Steve McIntyree1371102014-12-05 17:17:09 +0000661 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100662 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyree1371102014-12-05 17:17:09 +0000663 self.connection.commit()
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100664 except:
665 self.connection.rollback()
666 raise
Steve McIntyree1371102014-12-05 17:17:09 +0000667 return port_id
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100668
Steve McIntyre2d685c72014-12-08 15:24:12 +0000669 # Set the base vlan of a port in the database. The VLAN is
670 # passed by ID.
671 #
672 # Constraints:
673 # 1. The port must already exist
674 # 2. The port must not be a trunk port
675 # 3. The port must not be locked
Steve McIntyree653d172015-08-06 16:51:18 +0100676 # 4. The VLAN must already exist in the database
Steve McIntyredaae5502014-12-05 17:55:18 +0000677 def set_base_vlan(self, port_id, vlan_id):
678 port = self.get_port_by_id(port_id)
679 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000680 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000681
682 if port.is_trunk or port.is_locked:
683 raise CriticalError("The port is locked")
684
685 vlan = self.get_vlan_by_id(vlan_id)
686 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000687 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000688 if not vlan.is_base_vlan:
689 raise InputError("VLAN ID %d is not a base VLAN" % int(vlan_id))
690
691 try:
692 sql = "UPDATE port SET base_vlan_id=%s WHERE port_id=%s RETURNING port_id"
693 data = (vlan_id, port_id)
694 self.cursor.execute(sql, data)
695 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100696 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyredaae5502014-12-05 17:55:18 +0000697 self.connection.commit()
698 except:
699 self.connection.rollback()
700 raise
701 return port_id
702
Steve McIntyrec4890132015-08-07 15:19:11 +0100703 # Internal function: Attach a port to a trunk in the database.
704 #
705 # Constraints:
706 # 1. The port must already exist
707 # 2. The port must not be locked
708 def _set_port_trunk(self, port_id, trunk_id):
709 port = self.get_port_by_id(port_id)
710 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000711 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100712 if port.is_locked:
713 raise CriticalError("The port is locked")
714 try:
715 sql = "UPDATE port SET trunk_id=%s WHERE port_id=%s RETURNING port_id"
716 data = (int(trunk_id), int(port_id))
717 self.cursor.execute(sql, data)
718 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100719 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyrec4890132015-08-07 15:19:11 +0100720 self.connection.commit()
721 except:
722 self.connection.rollback()
723 raise
724 return port_id
725
Steve McIntyre2d685c72014-12-08 15:24:12 +0000726 # Trivial helper function to return all the rows in a given table
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100727 def _dump_table(self, table, order):
Dave Pigott281203e2014-09-17 23:45:02 +0100728 result = []
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100729 self.cursor.execute("SELECT * FROM %s ORDER by %s ASC" % (table, order))
Dave Pigott281203e2014-09-17 23:45:02 +0100730 record = self.cursor.fetchone()
731 while record != None:
Steve McIntyree73eb122014-11-27 15:18:47 +0000732 result.append(record)
Dave Pigott281203e2014-09-17 23:45:02 +0100733 record = self.cursor.fetchone()
734 return result
735
736 def all_switches(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100737 return self._dump_table("switch", "switch_id")
Dave Pigott281203e2014-09-17 23:45:02 +0100738
739 def all_ports(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100740 return self._dump_table("port", "port_id")
Dave Pigott281203e2014-09-17 23:45:02 +0100741
742 def all_vlans(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100743 return self._dump_table("vlan", "vlan_id")
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100744
Steve McIntyrec4890132015-08-07 15:19:11 +0100745 def all_trunks(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100746 return self._dump_table("trunk", "trunk_id")
Steve McIntyrec4890132015-08-07 15:19:11 +0100747
Steve McIntyre6a618162014-12-10 16:47:07 +0000748if __name__ == '__main__':
749 db = VlanDB()
Steve McIntyre6d84ec12014-12-18 16:56:56 +0000750 s = db.all_switches()
751 print 'The DB knows about %d switch(es)' % len(s)
752 print s
753 p = db.all_ports()
754 print 'The DB knows about %d port(s)' % len(p)
755 print p
756 v = db.all_vlans()
757 print 'The DB knows about %d vlan(s)' % len(v)
758 print v
Steve McIntyrec4890132015-08-07 15:19:11 +0100759 t = db.all_trunks()
760 print 'The DB knows about %d trunks(s)' % len(t)
761 print t
Steve McIntyre6a618162014-12-10 16:47:07 +0000762
Steve McIntyre6c4f33f2015-08-03 19:25:07 +0100763 print 'First free VLAN tag is %d' % db.find_lowest_unused_vlan_tag()