blob: d944f0e3a48cee3aa29f00e63a841e659d74753f [file] [log] [blame]
Dave Pigott281203e2014-09-17 23:45:02 +01001#! /usr/bin/python
2
3# Copyright 2014 Linaro Limited
Steve McIntyre663dc062014-10-20 11:11:47 +01004# Author: Dave Pigott <dave.pigott@linaro.org>
Dave Pigott281203e2014-09-17 23:45:02 +01005#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19# MA 02110-1301, USA.
20
21import psycopg2
22import psycopg2.extras
Steve McIntyre6a618162014-12-10 16:47:07 +000023import datetime, os, sys
24
25if __name__ == '__main__':
26 vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0])))
27 sys.path.insert(0, vlandpath)
28 sys.path.insert(0, "%s/.." % vlandpath)
29
Steve McIntyre6b013652014-12-02 12:35:18 +000030from errors import CriticalError, InputError
Dave Pigott281203e2014-09-17 23:45:02 +010031
32class VlanDB:
33 def __init__(self, db_name="vland", username="vland"):
34 try:
Steve McIntyree38f6222014-11-27 15:09:49 +000035 self.connection = psycopg2.connect(database=db_name, user=username)
Steve McIntyreb09ed282014-12-02 17:59:35 +000036 self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
Dave Pigott281203e2014-09-17 23:45:02 +010037 except Exception as e:
38 print "Failed to access database: %s" % e
39
40 def __del__(self):
41 self.cursor.close()
42 self.connection.close()
43
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000044 # Create a new switch in the database. Switches are really simple
45 # devices - they're just containers for ports.
46 #
47 # Constraints:
48 # Switches must be uniquely named
Steve McIntyredbd7fe52014-11-27 16:54:29 +000049 def create_switch(self, name):
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000050
Steve McIntyre549435f2014-12-05 15:42:46 +000051 switch_id = self.get_switch_id_by_name(name)
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000052 if switch_id is not None:
53 raise InputError("Switch name %s already exists" % name)
54
Dave Pigott2649a1a2014-09-18 00:04:49 +010055 try:
Steve McIntyredbd7fe52014-11-27 16:54:29 +000056 sql = "INSERT INTO switch (name) VALUES (%s) RETURNING switch_id"
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000057 data = (name, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +000058 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +010059 switch_id = self.cursor.fetchone()[0]
60 self.connection.commit()
61 except:
62 self.connection.rollback()
63 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +000064
Dave Pigott281203e2014-09-17 23:45:02 +010065 return switch_id
66
Steve McIntyre90a4a972014-11-28 16:50:56 +000067 # Create a new port in the database. Two of the fields are created
68 # with default values (is_locked, is_trunk) here, and should be
69 # updated separately if desired. For the current_vlan_id and
70 # base_vlan_id fields, *BE CAREFUL* that you have already looked
71 # up the correct VLAN_ID for each. This is *NOT* the same as the
72 # VLAN tag (likely to be 1).
73 # You Have Been Warned!
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000074 #
75 # Constraints:
76 # 1. The switch referred to must already exist
77 # 2. The VLANs mentioned here must already exist
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000078 # 3. (Switch/name) must be unique
Steve McIntyre1d10dbe2014-12-02 18:23:36 +000079 def create_port(self, switch_id, name, current_vlan_id, base_vlan_id):
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000080
Steve McIntyre549435f2014-12-05 15:42:46 +000081 switch = self.get_switch_by_id(switch_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000082 if switch is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000083 raise InputError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000084
85 for vlan_id in (current_vlan_id, base_vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +000086 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000087 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000088 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000089
90 port_id = self.get_port_by_switch_and_name(switch_id, name)
91 if port_id is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000092 raise InputError("Already have a port %s on switch ID %d" % (name, int(switch_id)))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000093
Dave Pigott2649a1a2014-09-18 00:04:49 +010094 try:
Steve McIntyre4b918132014-12-05 17:04:46 +000095 sql = "INSERT INTO port (name, switch_id, is_locked, is_trunk, current_vlan_id, base_vlan_id) VALUES (%s, %s, %s, %s, %s, %s) RETURNING port_id"
96 data = (name, switch_id,
Steve McIntyre90a4a972014-11-28 16:50:56 +000097 False, False,
Steve McIntyre4b918132014-12-05 17:04:46 +000098 current_vlan_id, base_vlan_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +000099 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100100 port_id = self.cursor.fetchone()[0]
101 self.connection.commit()
102 except:
103 self.connection.rollback()
104 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000105
Dave Pigott281203e2014-09-17 23:45:02 +0100106 return port_id
107
Steve McIntyreb005a2f2014-11-28 18:23:05 +0000108 # Create a new vlan in the database. We locally add a creation
109 # timestamp, for debug purposes. If vlans seems to be sticking
110 # around, we'll be able to see when they were created.
Steve McIntyre31b2df52014-12-02 12:37:54 +0000111 #
112 # Constraints:
113 # Names and tags must be unique
Steve McIntyre57f45912014-12-08 14:43:00 +0000114 # Tags must be in the range 1-4095 (802.1q spec)
Steve McIntyre49777e72014-12-08 16:00:46 +0000115 # Names can be any free-form text, length 1-32 characters
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000116 def create_vlan(self, name, tag, is_base_vlan):
Steve McIntyre31b2df52014-12-02 12:37:54 +0000117
Steve McIntyre57f45912014-12-08 14:43:00 +0000118 if int(tag) < 1 or int(tag) > 4095:
Steve McIntyre49777e72014-12-08 16:00:46 +0000119 raise InputError("VLAN tag %d is outside of the valid range (1-4095)" % int(tag))
120
121 if (len(name) < 1) or (len(name) > 32):
122 raise InputError("VLAN name %s is invalid (must be 1-32 chars)" % name)
Steve McIntyre57f45912014-12-08 14:43:00 +0000123
Steve McIntyrea34c1812014-12-05 15:27:55 +0000124 vlan_id = self.get_vlan_id_by_name(name)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000125 if vlan_id is not None:
126 raise InputError("VLAN name %s is already in use" % name)
127
Steve McIntyre50eb0602014-12-05 15:29:04 +0000128 vlan_id = self.get_vlan_id_by_tag(tag)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000129 if vlan_id is not None:
130 raise InputError("VLAN tag %d is already in use" % int(tag))
131
Dave Pigott2649a1a2014-09-18 00:04:49 +0100132 try:
Steve McIntyred74d97c2014-11-28 14:44:39 +0000133 dt = datetime.datetime.now()
Steve McIntyre4b918132014-12-05 17:04:46 +0000134 sql = "INSERT INTO vlan (name, tag, is_base_vlan, creation_time) VALUES (%s, %s, %s, %s) RETURNING vlan_id"
135 data = (name, tag, is_base_vlan, dt)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000136 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100137 vlan_id = self.cursor.fetchone()[0]
138 self.connection.commit()
139 except:
140 self.connection.rollback()
141 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000142
Dave Pigott281203e2014-09-17 23:45:02 +0100143 return vlan_id
144
Steve McIntyre2d685c72014-12-08 15:24:12 +0000145 # Internal helper function
Dave Pigott281203e2014-09-17 23:45:02 +0100146 def _delete_row(self, table, field, value):
Dave Pigott2649a1a2014-09-18 00:04:49 +0100147 try:
Steve McIntyree03de002014-12-02 17:14:14 +0000148 sql = "DELETE FROM %s WHERE %s = %s" % (table, field, '%s')
149 data = (value,)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000150 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100151 self.connection.commit()
152 except:
153 self.connection.rollback()
154 raise
Dave Pigott281203e2014-09-17 23:45:02 +0100155
Steve McIntyre388f0e22014-12-02 17:19:04 +0000156 # Delete the specified switch
157 #
158 # Constraints:
159 # 1. The switch must exist
160 # 2. The switch may not be referenced by any ports -
161 # delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100162 def delete_switch(self, switch_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000163 switch = self.get_switch_by_id(switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000164 if switch is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000165 raise InputError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyre388f0e22014-12-02 17:19:04 +0000166 ports = self.get_ports_by_switch(switch_id)
167 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000168 raise InputError("Cannot delete switch ID %d when it still has %d ports" %
169 (int(switch_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100170 self._delete_row("switch", "switch_id", switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000171 return switch_id
Dave Pigott281203e2014-09-17 23:45:02 +0100172
Steve McIntyre6a968622014-12-02 18:01:41 +0000173 # Delete the specified port
174 #
175 # Constraints:
176 # 1. The port must exist
177 # 2. The port must not be locked
Dave Pigott281203e2014-09-17 23:45:02 +0100178 def delete_port(self, port_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000179 port = self.get_port_by_id(port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000180 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000181 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre6a968622014-12-02 18:01:41 +0000182 if port.is_locked:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000183 raise InputError("Cannot delete port ID %d as it is locked" % int(port_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100184 self._delete_row("port", "port_id", port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000185 return port_id
Dave Pigott281203e2014-09-17 23:45:02 +0100186
Steve McIntyre14552ac2014-12-05 15:23:57 +0000187 # Delete the specified VLAN
188 #
189 # Constraints:
190 # 1. The VLAN
191 # 2. The VLAN may not contain any ports - move or delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100192 def delete_vlan(self, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000193 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000194 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000195 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000196 ports = self.get_ports_by_current_vlan(vlan_id)
197 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000198 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
199 (int(vlan_id), len(ports)))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000200 ports = self.get_ports_by_base_vlan(vlan_id)
201 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000202 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
203 (int(vlan_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100204 self._delete_row("vlan", "vlan_id", vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000205 return vlan_id
Dave Pigott281203e2014-09-17 23:45:02 +0100206
Steve McIntyre2d685c72014-12-08 15:24:12 +0000207 # Grab one column from one row of a query on one column; useful as
208 # a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100209 def _get_element(self, select_field, table, compare_field, value):
Steve McIntyre95614c22014-11-28 17:02:44 +0000210
211 # We really want to use psycopg's type handling deal with the
212 # (potentially) user-supplied data in the value field, so we
213 # have to pass (sql,data) through to cursor.execute. However,
214 # we can't have psycopg do all the argument substitution here
215 # as it will quote all the params like the table name. That
216 # doesn't work. So, we substitute a "%s" for "%s" here so we
217 # keep it after python's own string substitution.
218 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
219
220 # Now, the next icky thing: we need to make sure that we're
221 # passing a dict so that psycopg2 can pick it apart properly
222 # for its own substitution code. We force this with the
223 # trailing comma here
224 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000225 self.cursor.execute(sql, data)
Steve McIntyre95614c22014-11-28 17:02:44 +0000226
Steve McIntyre58b57a42014-12-02 13:09:21 +0000227 if self.cursor.rowcount > 0:
228 return self.cursor.fetchone()[0]
229 else:
Steve McIntyrec831f9c2014-12-02 12:38:54 +0000230 return None
Dave Pigott281203e2014-09-17 23:45:02 +0100231
Steve McIntyre2d685c72014-12-08 15:24:12 +0000232 # Grab one column from one row of a query on 2 columns; useful as
233 # a quick wrapper
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000234 def _get_element2(self, select_field, table, compare_field1, value1, compare_field2, value2):
235
236 # We really want to use psycopg's type handling deal with the
237 # (potentially) user-supplied data in the value field, so we
238 # have to pass (sql,data) through to cursor.execute. However,
239 # we can't have psycopg do all the argument substitution here
240 # as it will quote all the params like the table name. That
241 # doesn't work. So, we substitute a "%s" for "%s" here so we
242 # keep it after python's own string substitution.
243 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s" % (select_field, table, compare_field1, "%s", compare_field2, "%s")
244
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000245 data = (value1, value2)
246 self.cursor.execute(sql, data)
247
248 if self.cursor.rowcount > 0:
249 return self.cursor.fetchone()[0]
250 else:
251 return None
252
Steve McIntyre2d685c72014-12-08 15:24:12 +0000253 # Grab one column from multiple rows of a query; useful as a quick
254 # wrapper
Steve McIntyree9da15e2014-12-05 15:22:41 +0000255 def _get_multi_elements(self, select_field, table, compare_field, value):
256
257 # We really want to use psycopg's type handling deal with the
258 # (potentially) user-supplied data in the value field, so we
259 # have to pass (sql,data) through to cursor.execute. However,
260 # we can't have psycopg do all the argument substitution here
261 # as it will quote all the params like the table name. That
262 # doesn't work. So, we substitute a "%s" for "%s" here so we
263 # keep it after python's own string substitution.
264 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
265
266 # Now, the next icky thing: we need to make sure that we're
267 # passing a dict so that psycopg2 can pick it apart properly
268 # for its own substitution code. We force this with the
269 # trailing comma here
270 data = (value, )
271 self.cursor.execute(sql, data)
272
273 if self.cursor.rowcount > 0:
274 results = []
275 for record in self.cursor:
Steve McIntyre52509622014-12-02 17:13:15 +0000276 results.append(record[0])
Steve McIntyree9da15e2014-12-05 15:22:41 +0000277 return results
Steve McIntyre52509622014-12-02 17:13:15 +0000278 else:
279 return None
280
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000281 # Grab one column from multiple rows of a 2-part query; useful as
282 # a wrapper
283 def _get_multi_elements2(self, select_field, table, compare_field1, value1, compare_field2, value2):
284
285 # We really want to use psycopg's type handling deal with the
286 # (potentially) user-supplied data in the value field, so we
287 # have to pass (sql,data) through to cursor.execute. However,
288 # we can't have psycopg do all the argument substitution here
289 # as it will quote all the params like the table name. That
290 # doesn't work. So, we substitute a "%s" for "%s" here so we
291 # keep it after python's own string substitution.
292 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s" % (select_field, table, compare_field1, "%s", compare_field2, "%s")
293
294 data = (value1, value2)
295 self.cursor.execute(sql, data)
296
297 if self.cursor.rowcount > 0:
298 results = []
299 for record in self.cursor:
300 results.append(record[0])
301 return results
302 else:
303 return None
304
Steve McIntyre2d685c72014-12-08 15:24:12 +0000305 # Simple lookup: look up a switch by ID, and return all the
306 # details of that switch.
307 #
308 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000309 def get_switch_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000310 return self._get_row("switch", "switch_id", int(switch_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000311
Steve McIntyre2d685c72014-12-08 15:24:12 +0000312 # Simple lookup: look up a switch by name, and return the ID of
313 # that switch.
314 #
315 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000316 def get_switch_id_by_name(self, name):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100317 return self._get_element("switch_id", "switch", "name", name)
Dave Pigott281203e2014-09-17 23:45:02 +0100318
Steve McIntyre2d685c72014-12-08 15:24:12 +0000319 # Simple lookup: look up a switch by ID, and return the name of
320 # that switch.
321 #
322 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000323 def get_switch_name_by_id(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000324 return self._get_element("name", "switch", "switch_id", int(switch_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100325
Steve McIntyre2d685c72014-12-08 15:24:12 +0000326 # Simple lookup: look up a port by ID, and return all the details
327 # of that port.
328 #
329 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000330 def get_port_by_id(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000331 return self._get_row("port", "port_id", int(port_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000332
Steve McIntyre2d685c72014-12-08 15:24:12 +0000333 # Simple lookup: look up a switch by ID, and return the IDs of all
334 # the ports on that switch.
335 #
336 # Returns None on failure.
Steve McIntyreb67f3912014-12-02 17:14:36 +0000337 def get_ports_by_switch(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000338 return self._get_multi_elements("port_id", "port", "switch_id", int(switch_id))
Steve McIntyreb67f3912014-12-02 17:14:36 +0000339
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000340 # More complex lookup: look up all the trunk ports on a switch by
341 # ID
342 #
343 # Returns None on failure.
344 def get_trunk_port_names_by_switch(self, switch_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000345 return self._get_multi_elements2("name", "port", "switch_id", int(switch_id), "is_trunk", True)
Steve McIntyre7201c9b2014-12-17 17:33:51 +0000346
Steve McIntyre2d685c72014-12-08 15:24:12 +0000347 # Simple lookup: look up a port by its name and its parent switch
348 # by ID, and return the ID of the port.
349 #
350 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000351 def get_port_by_switch_and_name(self, switch_id, name):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000352 print "Looking for switch_id %s and name %s" % (switch_id, name)
353 return self._get_element2("port_id", "port", "switch_id", int(switch_id), "name", name)
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000354
Steve McIntyre2d685c72014-12-08 15:24:12 +0000355 # Simple lookup: look up a port by ID, and return the current VLAN
356 # id of that port.
357 #
358 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000359 def get_current_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000360 return self._get_element("current_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000361
Steve McIntyre2d685c72014-12-08 15:24:12 +0000362 # Simple lookup: look up a port by ID, and return the base VLAN
363 # id of that port.
364 #
365 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000366 def get_base_vlan_id_by_port(self, port_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000367 return self._get_element("base_vlan_id", "port", "port_id", int(port_id))
Steve McIntyredaae5502014-12-05 17:55:18 +0000368
Steve McIntyre2d685c72014-12-08 15:24:12 +0000369 # Simple lookup: look up a current VLAN by ID, and return the IDs
370 # of all the ports on that VLAN.
371 #
372 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000373 def get_ports_by_current_vlan(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000374 return self._get_multi_elements("port_id", "port", "current_vlan_id", int(vlan_id))
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000375
Steve McIntyre2d685c72014-12-08 15:24:12 +0000376 # Simple lookup: look up a base VLAN by ID, and return the IDs
377 # of all the ports on that VLAN.
378 #
379 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000380 def get_ports_by_base_vlan(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000381 return self._get_multi_elements("port_id", "port", "base_vlan_id", int(vlan_id))
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000382
Steve McIntyre2d685c72014-12-08 15:24:12 +0000383 # Simple lookup: look up a VLAN by ID, and return all the details
384 # of that VLAN.
385 #
386 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000387 def get_vlan_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000388 return self._get_row("vlan", "vlan_id", int(vlan_id))
Steve McIntyref3655062014-12-05 15:34:39 +0000389
Steve McIntyre2d685c72014-12-08 15:24:12 +0000390 # Simple lookup: look up a VLAN by name, and return the ID of that
391 # VLAN.
392 #
393 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000394 def get_vlan_id_by_name(self, name):
395 return self._get_element("vlan_id", "vlan", "name", name)
396
Steve McIntyre2d685c72014-12-08 15:24:12 +0000397 # Simple lookup: look up a VLAN by tag, and return the ID of that
398 # VLAN.
399 #
400 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000401 def get_vlan_id_by_tag(self, tag):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000402 return self._get_element("vlan_id", "vlan", "tag", int(tag))
Steve McIntyref3655062014-12-05 15:34:39 +0000403
Steve McIntyre2d685c72014-12-08 15:24:12 +0000404 # Simple lookup: look up a VLAN by ID, and return the name of that
405 # VLAN.
406 #
407 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000408 def get_vlan_name_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000409 return self._get_element("name", "vlan", "vlan_id", int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100410
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000411 # Simple lookup: look up a VLAN by ID, and return the tag of that
412 # VLAN.
413 #
414 # Returns None on failure.
415 def get_vlan_tag_by_id(self, vlan_id):
Steve McIntyre32e3a892015-01-23 17:47:46 +0000416 return self._get_element("tag", "vlan", "vlan_id", int(vlan_id))
Steve McIntyreb9b0aa52014-12-21 23:31:12 +0000417
Steve McIntyre2d685c72014-12-08 15:24:12 +0000418 # Grab one row of a query on one column; useful as a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100419 def _get_row(self, table, field, value):
Steve McIntyree0b842a2014-11-28 18:23:47 +0000420
421 # We really want to use psycopg's type handling deal with the
422 # (potentially) user-supplied data in the value field, so we
423 # have to pass (sql,data) through to cursor.execute. However,
424 # we can't have psycopg do all the argument substitution here
425 # as it will quote all the params like the table name. That
426 # doesn't work. So, we substitute a "%s" for "%s" here so we
427 # keep it after python's own string substitution.
428 sql = "SELECT * FROM %s WHERE %s = %s" % (table, field, "%s")
429
430 # Now, the next icky thing: we need to make sure that we're
431 # passing a dict so that psycopg2 can pick it apart properly
432 # for its own substitution code. We force this with the
433 # trailing comma here
434 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000435 self.cursor.execute(sql, data)
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100436 return self.cursor.fetchone()
437
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000438 # (Un)Lock a port in the database. This can only be done through
439 # the admin interface, and will stop API users from modifying
440 # settings on the port. Use this to lock down ports that are used
441 # for PDUs and other core infrastructure
442 def set_port_is_locked(self, port_id, is_locked):
Steve McIntyre8c64d952014-12-05 16:22:44 +0000443 port = self.get_port_by_id(port_id)
444 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000445 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000446 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000447 sql = "UPDATE port SET is_locked=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000448 data = (is_locked, port_id)
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000449 self.cursor.execute(sql, data)
450 port_id = self.cursor.fetchone()[0]
451 self.connection.commit()
452 except:
453 self.connection.rollback()
454 raise
455 return port_id
456
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000457 # Set the mode of a port in the database. Valid values for mode
458 # are "trunk" and "access"
459 def set_port_mode(self, port_id, mode):
460 port = self.get_port_by_id(port_id)
461 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000462 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000463 if mode == "access":
464 is_trunk = False
465 elif mode == "trunk":
466 is_trunk = True
467 else:
468 raise InputError("Port mode %s is not valid" % mode)
469 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000470 sql = "UPDATE port SET is_trunk=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000471 data = (is_trunk, port_id)
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000472 self.cursor.execute(sql, data)
473 port_id = self.cursor.fetchone()[0]
474 self.connection.commit()
475 except:
476 self.connection.rollback()
477 raise
478 return port_id
479
Steve McIntyre2d685c72014-12-08 15:24:12 +0000480 # Set the current vlan of a port in the database. The VLAN is
481 # passed by ID.
482 #
483 # Constraints:
484 # 1. The port must already exist
485 # 2. The port must not be a trunk port
486 # 3. The port must not be locked
487 # 1. The VLAN must already exist in the database
Steve McIntyre9eb78652014-12-05 17:51:53 +0000488 def set_current_vlan(self, port_id, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000489 port = self.get_port_by_id(port_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000490 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000491 raise InputError("Port ID %d does not exist" % int(port_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100492
Steve McIntyre6dd00be2014-12-05 17:29:35 +0000493 if port.is_trunk or port.is_locked:
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100494 raise CriticalError("The port is locked")
495
Steve McIntyre549435f2014-12-05 15:42:46 +0000496 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000497 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000498 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100499
500 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000501 sql = "UPDATE port SET current_vlan_id=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000502 data = (vlan_id, port_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000503 self.cursor.execute(sql, data)
Steve McIntyree1371102014-12-05 17:17:09 +0000504 port_id = self.cursor.fetchone()[0]
505 self.connection.commit()
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100506 except:
507 self.connection.rollback()
508 raise
Steve McIntyree1371102014-12-05 17:17:09 +0000509 return port_id
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100510
Steve McIntyre2d685c72014-12-08 15:24:12 +0000511 # Set the base vlan of a port in the database. The VLAN is
512 # passed by ID.
513 #
514 # Constraints:
515 # 1. The port must already exist
516 # 2. The port must not be a trunk port
517 # 3. The port must not be locked
518 # 1. The VLAN must already exist in the database
Steve McIntyredaae5502014-12-05 17:55:18 +0000519 def set_base_vlan(self, port_id, vlan_id):
520 port = self.get_port_by_id(port_id)
521 if port is None:
522 raise InputError("Port ID %d does not exist" % int(port_id))
523
524 if port.is_trunk or port.is_locked:
525 raise CriticalError("The port is locked")
526
527 vlan = self.get_vlan_by_id(vlan_id)
528 if vlan is None:
529 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
530 if not vlan.is_base_vlan:
531 raise InputError("VLAN ID %d is not a base VLAN" % int(vlan_id))
532
533 try:
534 sql = "UPDATE port SET base_vlan_id=%s WHERE port_id=%s RETURNING port_id"
535 data = (vlan_id, port_id)
536 self.cursor.execute(sql, data)
537 port_id = self.cursor.fetchone()[0]
538 self.connection.commit()
539 except:
540 self.connection.rollback()
541 raise
542 return port_id
543
Steve McIntyre2d685c72014-12-08 15:24:12 +0000544 # Trivial helper function to return all the rows in a given table
Dave Pigott281203e2014-09-17 23:45:02 +0100545 def _dump_table(self, table):
546 result = []
547 self.cursor.execute("SELECT * FROM %s" % table)
Dave Pigott281203e2014-09-17 23:45:02 +0100548 record = self.cursor.fetchone()
549 while record != None:
Steve McIntyree73eb122014-11-27 15:18:47 +0000550 result.append(record)
Dave Pigott281203e2014-09-17 23:45:02 +0100551 record = self.cursor.fetchone()
552 return result
553
554 def all_switches(self):
555 return self._dump_table("switch")
556
557 def all_ports(self):
558 return self._dump_table("port")
559
560 def all_vlans(self):
561 return self._dump_table("vlan")
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100562
Steve McIntyre6a618162014-12-10 16:47:07 +0000563if __name__ == '__main__':
564 db = VlanDB()
Steve McIntyre6d84ec12014-12-18 16:56:56 +0000565 s = db.all_switches()
566 print 'The DB knows about %d switch(es)' % len(s)
567 print s
568 p = db.all_ports()
569 print 'The DB knows about %d port(s)' % len(p)
570 print p
571 v = db.all_vlans()
572 print 'The DB knows about %d vlan(s)' % len(v)
573 print v
Steve McIntyre6a618162014-12-10 16:47:07 +0000574
575