blob: ec8e1f4bf1491b8fb6a2a7cd46470e5ee6a3de0a [file] [log] [blame]
Dave Pigott281203e2014-09-17 23:45:02 +01001#! /usr/bin/python
2
Steve McIntyre8fed4912018-01-31 17:21:31 +00003# Copyright 2014-2018 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 McIntyre8fed4912018-01-31 17:21:31 +000029# The schema version that this code expects. If it finds an older version (or
30# no version!) at startup, it will auto-migrate to the latest version
31#
32# Version 0: Base, no version found
33# Version 1: No changes, except adding the version and coping with upgrade
34DATABASE_SCHEMA_VERSION = 1
35
Steve McIntyre6a618162014-12-10 16:47:07 +000036if __name__ == '__main__':
37 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
38 sys.path.insert(0, vlandpath)
39 sys.path.insert(0, "%s/.." % vlandpath)
40
Steve McIntyreb01959f2016-03-22 17:02:39 +000041from errors import CriticalError, InputError, NotFoundError
Dave Pigott281203e2014-09-17 23:45:02 +010042
43class VlanDB:
Steve McIntyreea343aa2015-10-23 17:46:17 +010044 def __init__(self, db_name="vland", username="vland", readonly=True):
Dave Pigott281203e2014-09-17 23:45:02 +010045 try:
Steve McIntyree38f6222014-11-27 15:09:49 +000046 self.connection = psycopg2.connect(database=db_name, user=username)
Steve McIntyreb09ed282014-12-02 17:59:35 +000047 self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
Steve McIntyreea343aa2015-10-23 17:46:17 +010048 if not readonly:
49 self._init_state()
Dave Pigott281203e2014-09-17 23:45:02 +010050 except Exception as e:
Steve McIntyre5fa22652015-04-01 18:01:45 +010051 logging.error("Failed to access database: %s", e)
Steve McIntyre7cf80982015-02-12 07:03:40 +000052 raise
Dave Pigott281203e2014-09-17 23:45:02 +010053
54 def __del__(self):
55 self.cursor.close()
56 self.connection.close()
57
Steve McIntyreea343aa2015-10-23 17:46:17 +010058 # Create the state table (if needed) and add its only record
Steve McIntyre8fed4912018-01-31 17:21:31 +000059 #
60 # Use the stored record of the expected database schema to track what
61 # version the on-disk database is, and upgrade it to match the current code
62 # if necessary.
Steve McIntyreea343aa2015-10-23 17:46:17 +010063 def _init_state(self):
Steve McIntyre8fed4912018-01-31 17:21:31 +000064 found_db = False
65 current_db_version = 0
Steve McIntyreea343aa2015-10-23 17:46:17 +010066 try:
67 sql = "SELECT * FROM state"
68 self.cursor.execute(sql)
Steve McIntyre8fed4912018-01-31 17:21:31 +000069 found_db = True
Steve McIntyreea343aa2015-10-23 17:46:17 +010070 except psycopg2.ProgrammingError:
71 self.connection.commit() # state doesn't exist; clear error
Steve McIntyre8fed4912018-01-31 17:21:31 +000072 sql = "CREATE TABLE state (last_modified TIMESTAMP, schema_version INTEGER)"
Steve McIntyreea343aa2015-10-23 17:46:17 +010073 self.cursor.execute(sql)
Steve McIntyre8fed4912018-01-31 17:21:31 +000074 # We've just created a version 1 database
75 current_db_version = 1
76
77 if found_db:
78 # Grab the version of the database we have
79 try:
80 sql = "SELECT schema_version FROM state"
81 self.cursor.execute(sql)
82 current_db_version = self.cursor.fetchone()[0]
83 # No version found ==> we have "version 0"
84 except psycopg2.ProgrammingError:
85 self.connection.commit() # state doesn't exist; clear error
86 current_db_version = 0
87
88 # Now delete the existing state record, we'll write a new one in a
89 # moment
90 self.cursor.execute('DELETE FROM state')
91 logging.info("Found a database, version %d", current_db_version)
92
93 # Apply upgrades here!
94 if current_db_version < 1:
95 logging.info("Upgrading database to match schema version 1")
96 sql = "ALTER TABLE state ADD schema_version INTEGER"
97 self.cursor.execute(sql)
98 logging.info("Sschema version 1 upgrade successful")
99
100 sql = "INSERT INTO state (last_modified, schema_version) VALUES (%s, %s)"
101 data = (datetime.datetime.now(), DATABASE_SCHEMA_VERSION)
Steve McIntyreea343aa2015-10-23 17:46:17 +0100102 self.cursor.execute(sql, data)
103 self.connection.commit()
104
Steve McIntyre31d6dfa2014-12-02 12:35:56 +0000105 # Create a new switch in the database. Switches are really simple
106 # devices - they're just containers for ports.
107 #
108 # Constraints:
109 # Switches must be uniquely named
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000110 def create_switch(self, name):
Steve McIntyre31d6dfa2014-12-02 12:35:56 +0000111
Steve McIntyre549435f2014-12-05 15:42:46 +0000112 switch_id = self.get_switch_id_by_name(name)
Steve McIntyre31d6dfa2014-12-02 12:35:56 +0000113 if switch_id is not None:
114 raise InputError("Switch name %s already exists" % name)
115
Dave Pigott2649a1a2014-09-18 00:04:49 +0100116 try:
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000117 sql = "INSERT INTO switch (name) VALUES (%s) RETURNING switch_id"
Steve McIntyre31d6dfa2014-12-02 12:35:56 +0000118 data = (name, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000119 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100120 switch_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100121 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100122 self.connection.commit()
123 except:
124 self.connection.rollback()
125 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000126
Dave Pigott281203e2014-09-17 23:45:02 +0100127 return switch_id
128
Steve McIntyrec4890132015-08-07 15:19:11 +0100129 # Create a new port in the database. Three of the fields are
130 # created with default values (is_locked, is_trunk, trunk_id)
131 # here, and should be updated separately if desired. For the
132 # current_vlan_id and base_vlan_id fields, *BE CAREFUL* that you
133 # have already looked up the correct VLAN_ID for each. This is
134 # *NOT* the same as the VLAN tag (likely to be 1). You Have Been
135 # Warned!
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000136 #
137 # Constraints:
138 # 1. The switch referred to must already exist
139 # 2. The VLANs mentioned here must already exist
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000140 # 3. (Switch/name) must be unique
Steve McIntyreea753972015-08-05 13:52:48 +0100141 # 4. (Switch/number) must be unique
142 def create_port(self, switch_id, name, number, current_vlan_id, base_vlan_id):
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000143
Steve McIntyre549435f2014-12-05 15:42:46 +0000144 switch = self.get_switch_by_id(switch_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000145 if switch is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000146 raise NotFoundError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000147
148 for vlan_id in (current_vlan_id, base_vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000149 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +0000150 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000151 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000152
153 port_id = self.get_port_by_switch_and_name(switch_id, name)
154 if port_id is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000155 raise InputError("Already have a port %s on switch ID %d" % (name, int(switch_id)))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +0000156
Steve McIntyreea753972015-08-05 13:52:48 +0100157 port_id = self.get_port_by_switch_and_number(switch_id, int(number))
158 if port_id is not None:
159 raise InputError("Already have a port %d on switch ID %d" % (int(number), int(switch_id)))
160
Dave Pigott2649a1a2014-09-18 00:04:49 +0100161 try:
Steve McIntyrec4890132015-08-07 15:19:11 +0100162 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 +0100163 data = (name, number, switch_id,
Steve McIntyre90a4a972014-11-28 16:50:56 +0000164 False, False,
Steve McIntyrec4890132015-08-07 15:19:11 +0100165 current_vlan_id, base_vlan_id, TRUNK_ID_NONE)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000166 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100167 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100168 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100169 self.connection.commit()
170 except:
171 self.connection.rollback()
172 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000173
Dave Pigott281203e2014-09-17 23:45:02 +0100174 return port_id
175
Steve McIntyreb005a2f2014-11-28 18:23:05 +0000176 # Create a new vlan in the database. We locally add a creation
177 # timestamp, for debug purposes. If vlans seems to be sticking
178 # around, we'll be able to see when they were created.
Steve McIntyre31b2df52014-12-02 12:37:54 +0000179 #
180 # Constraints:
181 # Names and tags must be unique
Steve McIntyre57f45912014-12-08 14:43:00 +0000182 # Tags must be in the range 1-4095 (802.1q spec)
Steve McIntyre49777e72014-12-08 16:00:46 +0000183 # Names can be any free-form text, length 1-32 characters
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000184 def create_vlan(self, name, tag, is_base_vlan):
Steve McIntyre31b2df52014-12-02 12:37:54 +0000185
Steve McIntyre57f45912014-12-08 14:43:00 +0000186 if int(tag) < 1 or int(tag) > 4095:
Steve McIntyre49777e72014-12-08 16:00:46 +0000187 raise InputError("VLAN tag %d is outside of the valid range (1-4095)" % int(tag))
188
189 if (len(name) < 1) or (len(name) > 32):
190 raise InputError("VLAN name %s is invalid (must be 1-32 chars)" % name)
Steve McIntyre57f45912014-12-08 14:43:00 +0000191
Steve McIntyrea34c1812014-12-05 15:27:55 +0000192 vlan_id = self.get_vlan_id_by_name(name)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000193 if vlan_id is not None:
194 raise InputError("VLAN name %s is already in use" % name)
195
Steve McIntyre50eb0602014-12-05 15:29:04 +0000196 vlan_id = self.get_vlan_id_by_tag(tag)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000197 if vlan_id is not None:
198 raise InputError("VLAN tag %d is already in use" % int(tag))
199
Dave Pigott2649a1a2014-09-18 00:04:49 +0100200 try:
Steve McIntyred74d97c2014-11-28 14:44:39 +0000201 dt = datetime.datetime.now()
Steve McIntyre4b918132014-12-05 17:04:46 +0000202 sql = "INSERT INTO vlan (name, tag, is_base_vlan, creation_time) VALUES (%s, %s, %s, %s) RETURNING vlan_id"
203 data = (name, tag, is_base_vlan, dt)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000204 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100205 vlan_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100206 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100207 self.connection.commit()
208 except:
209 self.connection.rollback()
210 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000211
Dave Pigott281203e2014-09-17 23:45:02 +0100212 return vlan_id
213
Steve McIntyrec4890132015-08-07 15:19:11 +0100214 # Create a new trunk in the database, linking two ports. Trunks
215 # are really simple objects for our use - they're just containers
216 # for 2 ports.
217 #
218 # Constraints:
219 # 1. Both ports listed must already exist.
220 # 2. Both ports must be in trunk mode.
221 # 3. Both must not be locked.
222 # 4. Both must not already be in a trunk.
223 def create_trunk(self, port_id1, port_id2):
224
225 for port_id in (port_id1, port_id2):
226 port = self.get_port_by_id(int(port_id))
227 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000228 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100229 if not port.is_trunk:
230 raise InputError("Port ID %d is not in trunk mode" % int(port_id))
231 if port.is_locked:
232 raise InputError("Port ID %d is locked" % int(port_id))
233 if port.trunk_id != TRUNK_ID_NONE:
234 raise InputError("Port ID %d is already on trunk ID %d" % (int(port_id), int(port.trunk_id)))
235
236 try:
237 # Add the trunk itself
238 dt = datetime.datetime.now()
239 sql = "INSERT INTO trunk (creation_time) VALUES (%s) RETURNING trunk_id"
240 data = (dt, )
241 self.cursor.execute(sql, data)
242 trunk_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100243 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyrec4890132015-08-07 15:19:11 +0100244 self.connection.commit()
245 # And update the ports
246 for port_id in (port_id1, port_id2):
247 self._set_port_trunk(port_id, trunk_id)
248 except:
249 self.delete_trunk(trunk_id)
250 raise
251
252 return trunk_id
253
Steve McIntyre2d685c72014-12-08 15:24:12 +0000254 # Internal helper function
Dave Pigott281203e2014-09-17 23:45:02 +0100255 def _delete_row(self, table, field, value):
Dave Pigott2649a1a2014-09-18 00:04:49 +0100256 try:
Steve McIntyree03de002014-12-02 17:14:14 +0000257 sql = "DELETE FROM %s WHERE %s = %s" % (table, field, '%s')
258 data = (value,)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000259 self.cursor.execute(sql, data)
Steve McIntyreea343aa2015-10-23 17:46:17 +0100260 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Dave Pigott2649a1a2014-09-18 00:04:49 +0100261 self.connection.commit()
262 except:
263 self.connection.rollback()
264 raise
Dave Pigott281203e2014-09-17 23:45:02 +0100265
Steve McIntyre388f0e22014-12-02 17:19:04 +0000266 # Delete the specified switch
267 #
268 # Constraints:
269 # 1. The switch must exist
270 # 2. The switch may not be referenced by any ports -
271 # delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100272 def delete_switch(self, switch_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000273 switch = self.get_switch_by_id(switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000274 if switch is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000275 raise NotFoundError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyre388f0e22014-12-02 17:19:04 +0000276 ports = self.get_ports_by_switch(switch_id)
277 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000278 raise InputError("Cannot delete switch ID %d when it still has %d ports" %
279 (int(switch_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100280 self._delete_row("switch", "switch_id", switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000281 return switch_id
Dave Pigott281203e2014-09-17 23:45:02 +0100282
Steve McIntyre6a968622014-12-02 18:01:41 +0000283 # Delete the specified port
284 #
285 # Constraints:
286 # 1. The port must exist
287 # 2. The port must not be locked
Dave Pigott281203e2014-09-17 23:45:02 +0100288 def delete_port(self, port_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000289 port = self.get_port_by_id(port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000290 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000291 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre6a968622014-12-02 18:01:41 +0000292 if port.is_locked:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000293 raise InputError("Cannot delete port ID %d as it is locked" % int(port_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100294 self._delete_row("port", "port_id", port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000295 return port_id
Dave Pigott281203e2014-09-17 23:45:02 +0100296
Steve McIntyre14552ac2014-12-05 15:23:57 +0000297 # Delete the specified VLAN
298 #
299 # Constraints:
Steve McIntyre2a5df972015-08-07 15:19:40 +0100300 # 1. The VLAN must exist
Steve McIntyre14552ac2014-12-05 15:23:57 +0000301 # 2. The VLAN may not contain any ports - move or delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100302 def delete_vlan(self, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000303 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000304 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000305 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000306 ports = self.get_ports_by_current_vlan(vlan_id)
307 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000308 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
309 (int(vlan_id), len(ports)))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000310 ports = self.get_ports_by_base_vlan(vlan_id)
311 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000312 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
313 (int(vlan_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100314 self._delete_row("vlan", "vlan_id", vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000315 return vlan_id
Dave Pigott281203e2014-09-17 23:45:02 +0100316
Steve McIntyrec4890132015-08-07 15:19:11 +0100317 # Delete the specified trunk
318 #
319 # Constraints:
320 # 1. The trunk must exist
321 #
322 # Any ports attached will be detached (i.e. moved to trunk TRUNK_ID_NONE)
323 def delete_trunk(self, trunk_id):
324 trunk = self.get_trunk_by_id(trunk_id)
325 if trunk is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000326 raise NotFoundError("Trunk ID %d does not exist" % int(trunk_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100327 ports = self.get_ports_by_trunk(trunk_id)
328 for port_id in ports:
329 self._set_port_trunk(port_id, TRUNK_ID_NONE)
330 self._delete_row("trunk", "trunk_id", trunk_id)
331 return trunk_id
332
Steve McIntyre6c4f33f2015-08-03 19:25:07 +0100333 # Find the lowest unused VLAN tag and return it
334 #
335 # Constraints:
336 # None
337 def find_lowest_unused_vlan_tag(self):
338 sql = "SELECT tag FROM vlan ORDER BY tag ASC"
339 self.cursor.execute(sql,)
340
341 # Walk through the list, looking for gaps
342 last = 1
343 result = None
344
345 for record in self.cursor:
346 if (record[0] - last) > 1:
347 result = last + 1
348 break
349 last = record[0]
350
351 if result is None:
352 result = last + 1
353
354 if result > 4093:
355 raise CriticalError("Can't find any VLAN tags remaining for allocation!")
356
357 return result
358
Steve McIntyre2d685c72014-12-08 15:24:12 +0000359 # Grab one column from one row of a query on one column; useful as
360 # a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100361 def _get_element(self, select_field, table, compare_field, value):
Steve McIntyre95614c22014-11-28 17:02:44 +0000362
Steve McIntyredc173072016-08-25 18:29:10 +0100363 if value is None:
364 raise ValueError("Asked to look up using None as a key in %s" % compare_field)
365
Steve McIntyre95614c22014-11-28 17:02:44 +0000366 # We really want to use psycopg's type handling deal with the
367 # (potentially) user-supplied data in the value field, so we
368 # have to pass (sql,data) through to cursor.execute. However,
369 # we can't have psycopg do all the argument substitution here
370 # as it will quote all the params like the table name. That
371 # doesn't work. So, we substitute a "%s" for "%s" here so we
372 # keep it after python's own string substitution.
373 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
374
375 # Now, the next icky thing: we need to make sure that we're
376 # passing a dict so that psycopg2 can pick it apart properly
377 # for its own substitution code. We force this with the
378 # trailing comma here
379 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000380 self.cursor.execute(sql, data)
Steve McIntyre95614c22014-11-28 17:02:44 +0000381
Steve McIntyre58b57a42014-12-02 13:09:21 +0000382 if self.cursor.rowcount > 0:
383 return self.cursor.fetchone()[0]
384 else:
Steve McIntyrec831f9c2014-12-02 12:38:54 +0000385 return None
Dave Pigott281203e2014-09-17 23:45:02 +0100386
Steve McIntyre2d685c72014-12-08 15:24:12 +0000387 # Grab one column from one row of a query on 2 columns; useful as
388 # a quick wrapper
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000389 def _get_element2(self, select_field, table, compare_field1, value1, compare_field2, value2):
390
Steve McIntyredc173072016-08-25 18:29:10 +0100391 if value1 is None:
392 raise ValueError("Asked to look up using None as a key in %s" % compare_field1)
393 if value2 is None:
394 raise ValueError("Asked to look up using None as a key in %s" % compare_field2)
395
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000396 # We really want to use psycopg's type handling deal with the
397 # (potentially) user-supplied data in the value field, so we
398 # have to pass (sql,data) through to cursor.execute. However,
399 # we can't have psycopg do all the argument substitution here
400 # as it will quote all the params like the table name. That
401 # doesn't work. So, we substitute a "%s" for "%s" here so we
402 # keep it after python's own string substitution.
403 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s" % (select_field, table, compare_field1, "%s", compare_field2, "%s")
404
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000405 data = (value1, value2)
406 self.cursor.execute(sql, data)
407
408 if self.cursor.rowcount > 0:
409 return self.cursor.fetchone()[0]
410 else:
411 return None
412
Steve McIntyre2d685c72014-12-08 15:24:12 +0000413 # Grab one column from multiple rows of a query; useful as a quick
414 # wrapper
Steve McIntyre05e3e622015-09-25 01:29:18 +0100415 def _get_multi_elements(self, select_field, table, compare_field, value, sort_field):
Steve McIntyree9da15e2014-12-05 15:22:41 +0000416
Steve McIntyredc173072016-08-25 18:29:10 +0100417 if value is None:
418 raise ValueError("Asked to look up using None as a key in %s" % compare_field)
419
Steve McIntyree9da15e2014-12-05 15:22:41 +0000420 # We really want to use psycopg's type handling deal with the
421 # (potentially) user-supplied data in the value field, so we
422 # have to pass (sql,data) through to cursor.execute. However,
423 # we can't have psycopg do all the argument substitution here
424 # as it will quote all the params like the table name. That
425 # doesn't work. So, we substitute a "%s" for "%s" here so we
426 # keep it after python's own string substitution.
Steve McIntyre05e3e622015-09-25 01:29:18 +0100427 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 +0000428
429 # Now, the next icky thing: we need to make sure that we're
430 # passing a dict so that psycopg2 can pick it apart properly
431 # for its own substitution code. We force this with the
432 # trailing comma here
433 data = (value, )
434 self.cursor.execute(sql, data)
435
436 if self.cursor.rowcount > 0:
437 results = []
438 for record in self.cursor:
Steve McIntyre52509622014-12-02 17:13:15 +0000439 results.append(record[0])
Steve McIntyree9da15e2014-12-05 15:22:41 +0000440 return results
Steve McIntyre52509622014-12-02 17:13:15 +0000441 else:
442 return None
443
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000444 # Grab one column from multiple rows of a 2-part query; useful as
445 # a wrapper
Steve McIntyre05e3e622015-09-25 01:29:18 +0100446 def _get_multi_elements2(self, select_field, table, compare_field1, value1, compare_field2, value2, sort_field):
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000447
Steve McIntyredc173072016-08-25 18:29:10 +0100448 if value1 is None:
449 raise ValueError("Asked to look up using None as a key in %s" % compare_field1)
450 if value2 is None:
451 raise ValueError("Asked to look up using None as a key in %s" % compare_field2)
452
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000453 # We really want to use psycopg's type handling deal with the
454 # (potentially) user-supplied data in the value field, so we
455 # have to pass (sql,data) through to cursor.execute. However,
456 # we can't have psycopg do all the argument substitution here
457 # as it will quote all the params like the table name. That
458 # doesn't work. So, we substitute a "%s" for "%s" here so we
459 # keep it after python's own string substitution.
Steve McIntyre05e3e622015-09-25 01:29:18 +0100460 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 +0000461
462 data = (value1, value2)
463 self.cursor.execute(sql, data)
464
465 if self.cursor.rowcount > 0:
466 results = []
467 for record in self.cursor:
468 results.append(record[0])
469 return results
470 else:
471 return None
472
Steve McIntyre2d685c72014-12-08 15:24:12 +0000473 # Simple lookup: look up a switch by ID, and return all the
474 # details of that switch.
475 #
476 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000477 def get_switch_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000478 return self._get_row("switch", "switch_id", int(switch_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000479
Steve McIntyre2d685c72014-12-08 15:24:12 +0000480 # Simple lookup: look up a switch by name, and return the ID of
481 # that switch.
482 #
483 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000484 def get_switch_id_by_name(self, name):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100485 return self._get_element("switch_id", "switch", "name", name)
Dave Pigott281203e2014-09-17 23:45:02 +0100486
Steve McIntyre2d685c72014-12-08 15:24:12 +0000487 # Simple lookup: look up a switch by ID, and return the name of
488 # that switch.
489 #
490 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000491 def get_switch_name_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000492 return self._get_element("name", "switch", "switch_id", int(switch_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100493
Steve McIntyre2d685c72014-12-08 15:24:12 +0000494 # Simple lookup: look up a port by ID, and return all the details
495 # of that port.
496 #
497 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000498 def get_port_by_id(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000499 return self._get_row("port", "port_id", int(port_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000500
Steve McIntyre2d685c72014-12-08 15:24:12 +0000501 # Simple lookup: look up a switch by ID, and return the IDs of all
502 # the ports on that switch.
503 #
504 # Returns None on failure.
Steve McIntyreb67f3912014-12-02 17:14:36 +0000505 def get_ports_by_switch(self, switch_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100506 return self._get_multi_elements("port_id", "port", "switch_id", int(switch_id), "port_id")
Steve McIntyreb67f3912014-12-02 17:14:36 +0000507
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000508 # More complex lookup: look up all the trunk ports on a switch by
509 # ID
510 #
511 # Returns None on failure.
512 def get_trunk_port_names_by_switch(self, switch_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100513 return self._get_multi_elements2("name", "port", "switch_id", int(switch_id), "is_trunk", True, "port_id")
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000514
Steve McIntyre2d685c72014-12-08 15:24:12 +0000515 # Simple lookup: look up a port by its name and its parent switch
516 # by ID, and return the ID of the port.
517 #
518 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000519 def get_port_by_switch_and_name(self, switch_id, name):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000520 return self._get_element2("port_id", "port", "switch_id", int(switch_id), "name", name)
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000521
Steve McIntyre45f55012015-08-05 13:55:15 +0100522 # Simple lookup: look up a port by its external name and its
523 # parent switch by ID, and return the ID of the port.
524 #
525 # Returns None on failure.
526 def get_port_by_switch_and_number(self, switch_id, number):
527 return self._get_element2("port_id", "port", "switch_id", int(switch_id), "number", int(number))
528
Steve McIntyre2d685c72014-12-08 15:24:12 +0000529 # Simple lookup: look up a port by ID, and return the current VLAN
530 # id of that port.
531 #
532 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000533 def get_current_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000534 return self._get_element("current_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000535
Steve McIntyre2d685c72014-12-08 15:24:12 +0000536 # Simple lookup: look up a port by ID, and return the base VLAN
537 # id of that port.
538 #
539 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000540 def get_base_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000541 return self._get_element("base_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000542
Steve McIntyre2d685c72014-12-08 15:24:12 +0000543 # Simple lookup: look up a current VLAN by ID, and return the IDs
544 # of all the ports on that VLAN.
545 #
546 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000547 def get_ports_by_current_vlan(self, vlan_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100548 return self._get_multi_elements("port_id", "port", "current_vlan_id", int(vlan_id), "port_id")
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000549
Steve McIntyre2d685c72014-12-08 15:24:12 +0000550 # Simple lookup: look up a base VLAN by ID, and return the IDs
551 # of all the ports on that VLAN.
552 #
553 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000554 def get_ports_by_base_vlan(self, vlan_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100555 return self._get_multi_elements("port_id", "port", "base_vlan_id", int(vlan_id), "port_id")
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000556
Steve McIntyrec4890132015-08-07 15:19:11 +0100557 # Simple lookup: look up a trunk by ID, and return the IDs of the
558 # ports on both ends of that trunk.
559 #
560 # Returns None on failure.
561 def get_ports_by_trunk(self, trunk_id):
Steve McIntyre05e3e622015-09-25 01:29:18 +0100562 return self._get_multi_elements("port_id", "port", "trunk_id", int(trunk_id), "port_id")
Steve McIntyrec4890132015-08-07 15:19:11 +0100563
Steve McIntyre2d685c72014-12-08 15:24:12 +0000564 # Simple lookup: look up a VLAN by ID, and return all the details
565 # of that VLAN.
566 #
567 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000568 def get_vlan_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000569 return self._get_row("vlan", "vlan_id", int(vlan_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000570
Steve McIntyre2d685c72014-12-08 15:24:12 +0000571 # Simple lookup: look up a VLAN by name, and return the ID of that
572 # VLAN.
573 #
574 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000575 def get_vlan_id_by_name(self, name):
576 return self._get_element("vlan_id", "vlan", "name", name)
577
Steve McIntyre2d685c72014-12-08 15:24:12 +0000578 # Simple lookup: look up a VLAN by tag, and return the ID of that
579 # VLAN.
580 #
581 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000582 def get_vlan_id_by_tag(self, tag):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000583 return self._get_element("vlan_id", "vlan", "tag", int(tag))
Steve McIntyref3655062014-12-05 15:34:39 +0000584
Steve McIntyre2d685c72014-12-08 15:24:12 +0000585 # Simple lookup: look up a VLAN by ID, and return the name of that
586 # VLAN.
587 #
588 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000589 def get_vlan_name_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000590 return self._get_element("name", "vlan", "vlan_id", int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100591
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000592 # Simple lookup: look up a VLAN by ID, and return the tag of that
593 # VLAN.
594 #
595 # Returns None on failure.
596 def get_vlan_tag_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000597 return self._get_element("tag", "vlan", "vlan_id", int(vlan_id))
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000598
Steve McIntyrec4890132015-08-07 15:19:11 +0100599 # Simple lookup: look up a trunk by ID, and return all the details
600 # of that trunk.
601 #
602 # Returns None on failure.
603 def get_trunk_by_id(self, trunk_id):
604 return self._get_row("trunk", "trunk_id", int(trunk_id))
605
Steve McIntyreea343aa2015-10-23 17:46:17 +0100606 # Get the last-modified time for the database
607 def get_last_modified_time(self):
608 sql = "SELECT last_modified FROM state"
609 self.cursor.execute(sql)
Steve McIntyreaf24aaa2015-10-23 17:59:04 +0100610 return self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100611
Steve McIntyre2d685c72014-12-08 15:24:12 +0000612 # Grab one row of a query on one column; useful as a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100613 def _get_row(self, table, field, value):
Steve McIntyree0b842a2014-11-28 18:23:47 +0000614
615 # We really want to use psycopg's type handling deal with the
616 # (potentially) user-supplied data in the value field, so we
617 # have to pass (sql,data) through to cursor.execute. However,
618 # we can't have psycopg do all the argument substitution here
619 # as it will quote all the params like the table name. That
620 # doesn't work. So, we substitute a "%s" for "%s" here so we
621 # keep it after python's own string substitution.
622 sql = "SELECT * FROM %s WHERE %s = %s" % (table, field, "%s")
623
624 # Now, the next icky thing: we need to make sure that we're
625 # passing a dict so that psycopg2 can pick it apart properly
626 # for its own substitution code. We force this with the
627 # trailing comma here
628 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000629 self.cursor.execute(sql, data)
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100630 return self.cursor.fetchone()
631
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000632 # (Un)Lock a port in the database. This can only be done through
633 # the admin interface, and will stop API users from modifying
634 # settings on the port. Use this to lock down ports that are used
635 # for PDUs and other core infrastructure
636 def set_port_is_locked(self, port_id, is_locked):
Steve McIntyre8c64d952014-12-05 16:22:44 +0000637 port = self.get_port_by_id(port_id)
638 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000639 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000640 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000641 sql = "UPDATE port SET is_locked=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000642 data = (is_locked, port_id)
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000643 self.cursor.execute(sql, data)
644 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100645 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000646 self.connection.commit()
647 except:
648 self.connection.rollback()
649 raise
Steve McIntyre1c8a3212015-07-14 17:07:31 +0100650 return port_id
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000651
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000652 # Set the mode of a port in the database. Valid values for mode
653 # are "trunk" and "access"
654 def set_port_mode(self, port_id, mode):
655 port = self.get_port_by_id(port_id)
656 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000657 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000658 if mode == "access":
659 is_trunk = False
660 elif mode == "trunk":
661 is_trunk = True
662 else:
663 raise InputError("Port mode %s is not valid" % mode)
664 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000665 sql = "UPDATE port SET is_trunk=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000666 data = (is_trunk, port_id)
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000667 self.cursor.execute(sql, data)
668 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100669 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000670 self.connection.commit()
671 except:
672 self.connection.rollback()
673 raise
674 return port_id
675
Steve McIntyre2d685c72014-12-08 15:24:12 +0000676 # Set the current vlan of a port in the database. The VLAN is
677 # passed by ID.
678 #
679 # Constraints:
680 # 1. The port must already exist
681 # 2. The port must not be a trunk port
682 # 3. The port must not be locked
683 # 1. The VLAN must already exist in the database
Steve McIntyre9eb78652014-12-05 17:51:53 +0000684 def set_current_vlan(self, port_id, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000685 port = self.get_port_by_id(port_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000686 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000687 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100688
Steve McIntyre6dd00be2014-12-05 17:29:35 +0000689 if port.is_trunk or port.is_locked:
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100690 raise CriticalError("The port is locked")
691
Steve McIntyre549435f2014-12-05 15:42:46 +0000692 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000693 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000694 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100695
696 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000697 sql = "UPDATE port SET current_vlan_id=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000698 data = (vlan_id, port_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000699 self.cursor.execute(sql, data)
Steve McIntyree1371102014-12-05 17:17:09 +0000700 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100701 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyree1371102014-12-05 17:17:09 +0000702 self.connection.commit()
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100703 except:
704 self.connection.rollback()
705 raise
Steve McIntyree1371102014-12-05 17:17:09 +0000706 return port_id
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100707
Steve McIntyre2d685c72014-12-08 15:24:12 +0000708 # Set the base vlan of a port in the database. The VLAN is
709 # passed by ID.
710 #
711 # Constraints:
712 # 1. The port must already exist
713 # 2. The port must not be a trunk port
714 # 3. The port must not be locked
Steve McIntyree653d172015-08-06 16:51:18 +0100715 # 4. The VLAN must already exist in the database
Steve McIntyredaae5502014-12-05 17:55:18 +0000716 def set_base_vlan(self, port_id, vlan_id):
717 port = self.get_port_by_id(port_id)
718 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000719 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000720
721 if port.is_trunk or port.is_locked:
722 raise CriticalError("The port is locked")
723
724 vlan = self.get_vlan_by_id(vlan_id)
725 if vlan is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000726 raise NotFoundError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000727 if not vlan.is_base_vlan:
728 raise InputError("VLAN ID %d is not a base VLAN" % int(vlan_id))
729
730 try:
731 sql = "UPDATE port SET base_vlan_id=%s WHERE port_id=%s RETURNING port_id"
732 data = (vlan_id, port_id)
733 self.cursor.execute(sql, data)
734 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100735 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyredaae5502014-12-05 17:55:18 +0000736 self.connection.commit()
737 except:
738 self.connection.rollback()
739 raise
740 return port_id
741
Steve McIntyrec4890132015-08-07 15:19:11 +0100742 # Internal function: Attach a port to a trunk in the database.
743 #
744 # Constraints:
745 # 1. The port must already exist
746 # 2. The port must not be locked
747 def _set_port_trunk(self, port_id, trunk_id):
748 port = self.get_port_by_id(port_id)
749 if port is None:
Steve McIntyreb01959f2016-03-22 17:02:39 +0000750 raise NotFoundError("Port ID %d does not exist" % int(port_id))
Steve McIntyrec4890132015-08-07 15:19:11 +0100751 if port.is_locked:
752 raise CriticalError("The port is locked")
753 try:
754 sql = "UPDATE port SET trunk_id=%s WHERE port_id=%s RETURNING port_id"
755 data = (int(trunk_id), int(port_id))
756 self.cursor.execute(sql, data)
757 port_id = self.cursor.fetchone()[0]
Steve McIntyreea343aa2015-10-23 17:46:17 +0100758 self.cursor.execute("UPDATE state SET last_modified=%s", (datetime.datetime.now(),))
Steve McIntyrec4890132015-08-07 15:19:11 +0100759 self.connection.commit()
760 except:
761 self.connection.rollback()
762 raise
763 return port_id
764
Steve McIntyre2d685c72014-12-08 15:24:12 +0000765 # Trivial helper function to return all the rows in a given table
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100766 def _dump_table(self, table, order):
Dave Pigott281203e2014-09-17 23:45:02 +0100767 result = []
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100768 self.cursor.execute("SELECT * FROM %s ORDER by %s ASC" % (table, order))
Dave Pigott281203e2014-09-17 23:45:02 +0100769 record = self.cursor.fetchone()
770 while record != None:
Steve McIntyree73eb122014-11-27 15:18:47 +0000771 result.append(record)
Dave Pigott281203e2014-09-17 23:45:02 +0100772 record = self.cursor.fetchone()
773 return result
774
775 def all_switches(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100776 return self._dump_table("switch", "switch_id")
Dave Pigott281203e2014-09-17 23:45:02 +0100777
778 def all_ports(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100779 return self._dump_table("port", "port_id")
Dave Pigott281203e2014-09-17 23:45:02 +0100780
781 def all_vlans(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100782 return self._dump_table("vlan", "vlan_id")
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100783
Steve McIntyrec4890132015-08-07 15:19:11 +0100784 def all_trunks(self):
Steve McIntyree3fb49a2015-09-23 00:04:12 +0100785 return self._dump_table("trunk", "trunk_id")
Steve McIntyrec4890132015-08-07 15:19:11 +0100786
Steve McIntyre6a618162014-12-10 16:47:07 +0000787if __name__ == '__main__':
788 db = VlanDB()
Steve McIntyre6d84ec12014-12-18 16:56:56 +0000789 s = db.all_switches()
790 print 'The DB knows about %d switch(es)' % len(s)
791 print s
792 p = db.all_ports()
793 print 'The DB knows about %d port(s)' % len(p)
794 print p
795 v = db.all_vlans()
796 print 'The DB knows about %d vlan(s)' % len(v)
797 print v
Steve McIntyrec4890132015-08-07 15:19:11 +0100798 t = db.all_trunks()
799 print 'The DB knows about %d trunks(s)' % len(t)
800 print t
Steve McIntyre6a618162014-12-10 16:47:07 +0000801
Steve McIntyre6c4f33f2015-08-03 19:25:07 +0100802 print 'First free VLAN tag is %d' % db.find_lowest_unused_vlan_tag()