blob: 378dc353ddce648d1ef8218bd59151f9bad12b25 [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 McIntyred74d97c2014-11-28 14:44:39 +000023import datetime
Steve McIntyre6b013652014-12-02 12:35:18 +000024from errors import CriticalError, InputError
Dave Pigott281203e2014-09-17 23:45:02 +010025
26class VlanDB:
27 def __init__(self, db_name="vland", username="vland"):
28 try:
Steve McIntyree38f6222014-11-27 15:09:49 +000029 self.connection = psycopg2.connect(database=db_name, user=username)
Steve McIntyreb09ed282014-12-02 17:59:35 +000030 self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
Dave Pigott281203e2014-09-17 23:45:02 +010031 except Exception as e:
32 print "Failed to access database: %s" % e
33
34 def __del__(self):
35 self.cursor.close()
36 self.connection.close()
37
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000038 # Create a new switch in the database. Switches are really simple
39 # devices - they're just containers for ports.
40 #
41 # Constraints:
42 # Switches must be uniquely named
Steve McIntyredbd7fe52014-11-27 16:54:29 +000043 def create_switch(self, name):
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000044
Steve McIntyre549435f2014-12-05 15:42:46 +000045 switch_id = self.get_switch_id_by_name(name)
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000046 if switch_id is not None:
47 raise InputError("Switch name %s already exists" % name)
48
Dave Pigott2649a1a2014-09-18 00:04:49 +010049 try:
Steve McIntyredbd7fe52014-11-27 16:54:29 +000050 sql = "INSERT INTO switch (name) VALUES (%s) RETURNING switch_id"
Steve McIntyre31d6dfa2014-12-02 12:35:56 +000051 data = (name, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +000052 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +010053 switch_id = self.cursor.fetchone()[0]
54 self.connection.commit()
55 except:
56 self.connection.rollback()
57 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +000058
Dave Pigott281203e2014-09-17 23:45:02 +010059 return switch_id
60
Steve McIntyre90a4a972014-11-28 16:50:56 +000061 # Create a new port in the database. Two of the fields are created
62 # with default values (is_locked, is_trunk) here, and should be
63 # updated separately if desired. For the current_vlan_id and
64 # base_vlan_id fields, *BE CAREFUL* that you have already looked
65 # up the correct VLAN_ID for each. This is *NOT* the same as the
66 # VLAN tag (likely to be 1).
67 # You Have Been Warned!
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000068 #
69 # Constraints:
70 # 1. The switch referred to must already exist
71 # 2. The VLANs mentioned here must already exist
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000072 # 3. (Switch/name) must be unique
Steve McIntyre1d10dbe2014-12-02 18:23:36 +000073 def create_port(self, switch_id, name, current_vlan_id, base_vlan_id):
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000074
Steve McIntyre549435f2014-12-05 15:42:46 +000075 switch = self.get_switch_by_id(switch_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000076 if switch is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000077 raise InputError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000078
79 for vlan_id in (current_vlan_id, base_vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +000080 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyrecb42ebf2014-12-02 12:36:45 +000081 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000082 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000083
84 port_id = self.get_port_by_switch_and_name(switch_id, name)
85 if port_id is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +000086 raise InputError("Already have a port %s on switch ID %d" % (name, int(switch_id)))
Steve McIntyre6a7fdb22014-12-05 15:17:30 +000087
Dave Pigott2649a1a2014-09-18 00:04:49 +010088 try:
Steve McIntyre4b918132014-12-05 17:04:46 +000089 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"
90 data = (name, switch_id,
Steve McIntyre90a4a972014-11-28 16:50:56 +000091 False, False,
Steve McIntyre4b918132014-12-05 17:04:46 +000092 current_vlan_id, base_vlan_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +000093 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +010094 port_id = self.cursor.fetchone()[0]
95 self.connection.commit()
96 except:
97 self.connection.rollback()
98 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +000099
Dave Pigott281203e2014-09-17 23:45:02 +0100100 return port_id
101
Steve McIntyreb005a2f2014-11-28 18:23:05 +0000102 # Create a new vlan in the database. We locally add a creation
103 # timestamp, for debug purposes. If vlans seems to be sticking
104 # around, we'll be able to see when they were created.
Steve McIntyre31b2df52014-12-02 12:37:54 +0000105 #
106 # Constraints:
107 # Names and tags must be unique
Steve McIntyre57f45912014-12-08 14:43:00 +0000108 # Tags must be in the range 1-4095 (802.1q spec)
Steve McIntyre49777e72014-12-08 16:00:46 +0000109 # Names can be any free-form text, length 1-32 characters
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000110 def create_vlan(self, name, tag, is_base_vlan):
Steve McIntyre31b2df52014-12-02 12:37:54 +0000111
Steve McIntyre57f45912014-12-08 14:43:00 +0000112 if int(tag) < 1 or int(tag) > 4095:
Steve McIntyre49777e72014-12-08 16:00:46 +0000113 raise InputError("VLAN tag %d is outside of the valid range (1-4095)" % int(tag))
114
115 if (len(name) < 1) or (len(name) > 32):
116 raise InputError("VLAN name %s is invalid (must be 1-32 chars)" % name)
Steve McIntyre57f45912014-12-08 14:43:00 +0000117
Steve McIntyrea34c1812014-12-05 15:27:55 +0000118 vlan_id = self.get_vlan_id_by_name(name)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000119 if vlan_id is not None:
120 raise InputError("VLAN name %s is already in use" % name)
121
Steve McIntyre50eb0602014-12-05 15:29:04 +0000122 vlan_id = self.get_vlan_id_by_tag(tag)
Steve McIntyre31b2df52014-12-02 12:37:54 +0000123 if vlan_id is not None:
124 raise InputError("VLAN tag %d is already in use" % int(tag))
125
Dave Pigott2649a1a2014-09-18 00:04:49 +0100126 try:
Steve McIntyred74d97c2014-11-28 14:44:39 +0000127 dt = datetime.datetime.now()
Steve McIntyre4b918132014-12-05 17:04:46 +0000128 sql = "INSERT INTO vlan (name, tag, is_base_vlan, creation_time) VALUES (%s, %s, %s, %s) RETURNING vlan_id"
129 data = (name, tag, is_base_vlan, dt)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000130 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100131 vlan_id = self.cursor.fetchone()[0]
132 self.connection.commit()
133 except:
134 self.connection.rollback()
135 raise
Steve McIntyree1febdb2014-12-02 12:39:14 +0000136
Dave Pigott281203e2014-09-17 23:45:02 +0100137 return vlan_id
138
Steve McIntyre2d685c72014-12-08 15:24:12 +0000139 # Internal helper function
Dave Pigott281203e2014-09-17 23:45:02 +0100140 def _delete_row(self, table, field, value):
Dave Pigott2649a1a2014-09-18 00:04:49 +0100141 try:
Steve McIntyree03de002014-12-02 17:14:14 +0000142 sql = "DELETE FROM %s WHERE %s = %s" % (table, field, '%s')
143 data = (value,)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000144 self.cursor.execute(sql, data)
Dave Pigott2649a1a2014-09-18 00:04:49 +0100145 self.connection.commit()
146 except:
147 self.connection.rollback()
148 raise
Dave Pigott281203e2014-09-17 23:45:02 +0100149
Steve McIntyre388f0e22014-12-02 17:19:04 +0000150 # Delete the specified switch
151 #
152 # Constraints:
153 # 1. The switch must exist
154 # 2. The switch may not be referenced by any ports -
155 # delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100156 def delete_switch(self, switch_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000157 switch = self.get_switch_by_id(switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000158 if switch is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000159 raise InputError("Switch ID %d does not exist" % int(switch_id))
Steve McIntyre388f0e22014-12-02 17:19:04 +0000160 ports = self.get_ports_by_switch(switch_id)
161 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000162 raise InputError("Cannot delete switch ID %d when it still has %d ports" %
163 (int(switch_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100164 self._delete_row("switch", "switch_id", switch_id)
Steve McIntyre388f0e22014-12-02 17:19:04 +0000165 return switch_id
Dave Pigott281203e2014-09-17 23:45:02 +0100166
Steve McIntyre6a968622014-12-02 18:01:41 +0000167 # Delete the specified port
168 #
169 # Constraints:
170 # 1. The port must exist
171 # 2. The port must not be locked
Dave Pigott281203e2014-09-17 23:45:02 +0100172 def delete_port(self, port_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000173 port = self.get_port_by_id(port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000174 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000175 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre6a968622014-12-02 18:01:41 +0000176 if port.is_locked:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000177 raise InputError("Cannot delete port ID %d as it is locked" % int(port_id))
Dave Pigott281203e2014-09-17 23:45:02 +0100178 self._delete_row("port", "port_id", port_id)
Steve McIntyre6a968622014-12-02 18:01:41 +0000179 return port_id
Dave Pigott281203e2014-09-17 23:45:02 +0100180
Steve McIntyre14552ac2014-12-05 15:23:57 +0000181 # Delete the specified VLAN
182 #
183 # Constraints:
184 # 1. The VLAN
185 # 2. The VLAN may not contain any ports - move or delete them first!
Dave Pigott281203e2014-09-17 23:45:02 +0100186 def delete_vlan(self, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000187 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000188 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000189 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000190 ports = self.get_ports_by_current_vlan(vlan_id)
191 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000192 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
193 (int(vlan_id), len(ports)))
Steve McIntyre14552ac2014-12-05 15:23:57 +0000194 ports = self.get_ports_by_base_vlan(vlan_id)
195 if ports is not None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000196 raise InputError("Cannot delete VLAN ID %d when it still has %d ports" %
197 (int(vlan_id), len(ports)))
Dave Pigott281203e2014-09-17 23:45:02 +0100198 self._delete_row("vlan", "vlan_id", vlan_id)
Steve McIntyre14552ac2014-12-05 15:23:57 +0000199 return vlan_id
Dave Pigott281203e2014-09-17 23:45:02 +0100200
Steve McIntyre2d685c72014-12-08 15:24:12 +0000201 # Grab one column from one row of a query on one column; useful as
202 # a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100203 def _get_element(self, select_field, table, compare_field, value):
Steve McIntyre95614c22014-11-28 17:02:44 +0000204
205 # We really want to use psycopg's type handling deal with the
206 # (potentially) user-supplied data in the value field, so we
207 # have to pass (sql,data) through to cursor.execute. However,
208 # we can't have psycopg do all the argument substitution here
209 # as it will quote all the params like the table name. That
210 # doesn't work. So, we substitute a "%s" for "%s" here so we
211 # keep it after python's own string substitution.
212 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
213
214 # Now, the next icky thing: we need to make sure that we're
215 # passing a dict so that psycopg2 can pick it apart properly
216 # for its own substitution code. We force this with the
217 # trailing comma here
218 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000219 self.cursor.execute(sql, data)
Steve McIntyre95614c22014-11-28 17:02:44 +0000220
Steve McIntyre58b57a42014-12-02 13:09:21 +0000221 if self.cursor.rowcount > 0:
222 return self.cursor.fetchone()[0]
223 else:
Steve McIntyrec831f9c2014-12-02 12:38:54 +0000224 return None
Dave Pigott281203e2014-09-17 23:45:02 +0100225
Steve McIntyre2d685c72014-12-08 15:24:12 +0000226 # Grab one column from one row of a query on 2 columns; useful as
227 # a quick wrapper
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000228 def _get_element2(self, select_field, table, compare_field1, value1, compare_field2, value2):
229
230 # We really want to use psycopg's type handling deal with the
231 # (potentially) user-supplied data in the value field, so we
232 # have to pass (sql,data) through to cursor.execute. However,
233 # we can't have psycopg do all the argument substitution here
234 # as it will quote all the params like the table name. That
235 # doesn't work. So, we substitute a "%s" for "%s" here so we
236 # keep it after python's own string substitution.
237 sql = "SELECT %s FROM %s WHERE %s = %s AND %s = %s" % (select_field, table, compare_field1, "%s", compare_field2, "%s")
238
Steve McIntyrea74c7fe2014-12-02 18:49:38 +0000239 data = (value1, value2)
240 self.cursor.execute(sql, data)
241
242 if self.cursor.rowcount > 0:
243 return self.cursor.fetchone()[0]
244 else:
245 return None
246
Steve McIntyre2d685c72014-12-08 15:24:12 +0000247 # Grab one column from multiple rows of a query; useful as a quick
248 # wrapper
Steve McIntyree9da15e2014-12-05 15:22:41 +0000249 def _get_multi_elements(self, select_field, table, compare_field, value):
250
251 # We really want to use psycopg's type handling deal with the
252 # (potentially) user-supplied data in the value field, so we
253 # have to pass (sql,data) through to cursor.execute. However,
254 # we can't have psycopg do all the argument substitution here
255 # as it will quote all the params like the table name. That
256 # doesn't work. So, we substitute a "%s" for "%s" here so we
257 # keep it after python's own string substitution.
258 sql = "SELECT %s FROM %s WHERE %s = %s" % (select_field, table, compare_field, "%s")
259
260 # Now, the next icky thing: we need to make sure that we're
261 # passing a dict so that psycopg2 can pick it apart properly
262 # for its own substitution code. We force this with the
263 # trailing comma here
264 data = (value, )
265 self.cursor.execute(sql, data)
266
267 if self.cursor.rowcount > 0:
268 results = []
269 for record in self.cursor:
Steve McIntyre52509622014-12-02 17:13:15 +0000270 results.append(record[0])
Steve McIntyree9da15e2014-12-05 15:22:41 +0000271 return results
Steve McIntyre52509622014-12-02 17:13:15 +0000272 else:
273 return None
274
Steve McIntyre2d685c72014-12-08 15:24:12 +0000275 # Simple lookup: look up a switch by ID, and return all the
276 # details of that switch.
277 #
278 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000279 def get_switch_by_id(self, switch_id):
Steve McIntyref3655062014-12-05 15:34:39 +0000280 return self._get_row("switch", "switch_id", switch_id)
281
Steve McIntyre2d685c72014-12-08 15:24:12 +0000282 # Simple lookup: look up a switch by name, and return the ID of
283 # that switch.
284 #
285 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000286 def get_switch_id_by_name(self, name):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100287 return self._get_element("switch_id", "switch", "name", name)
Dave Pigott281203e2014-09-17 23:45:02 +0100288
Steve McIntyre2d685c72014-12-08 15:24:12 +0000289 # Simple lookup: look up a switch by ID, and return the name of
290 # that switch.
291 #
292 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000293 def get_switch_name_by_id(self, switch_id):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100294 return self._get_element("name", "switch", "switch_id", switch_id)
Dave Pigott281203e2014-09-17 23:45:02 +0100295
Steve McIntyre2d685c72014-12-08 15:24:12 +0000296 # Simple lookup: look up a port by ID, and return all the details
297 # of that port.
298 #
299 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000300 def get_port_by_id(self, port_id):
Steve McIntyref3655062014-12-05 15:34:39 +0000301 return self._get_row("port", "port_id", port_id)
302
Steve McIntyre2d685c72014-12-08 15:24:12 +0000303 # Simple lookup: look up a switch by ID, and return the IDs of all
304 # the ports on that switch.
305 #
306 # Returns None on failure.
Steve McIntyreb67f3912014-12-02 17:14:36 +0000307 def get_ports_by_switch(self, switch_id):
308 return self._get_multi_elements("port_id", "port", "switch_id", switch_id)
309
Steve McIntyre2d685c72014-12-08 15:24:12 +0000310 # Simple lookup: look up a port by its name and its parent switch
311 # by ID, and return the ID of the port.
312 #
313 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000314 def get_port_by_switch_and_name(self, switch_id, name):
315 return self._get_element2("port_id", "port", "switch_id", switch_id, "name", name)
316
Steve McIntyre2d685c72014-12-08 15:24:12 +0000317 # Simple lookup: look up a port by ID, and return the current VLAN
318 # id of that port.
319 #
320 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000321 def get_current_vlan_id_by_port(self, port_id):
322 return self._get_element("current_vlan_id", "port", "port_id", port_id)
323
Steve McIntyre2d685c72014-12-08 15:24:12 +0000324 # Simple lookup: look up a port by ID, and return the base VLAN
325 # id of that port.
326 #
327 # Returns None on failure.
Steve McIntyredaae5502014-12-05 17:55:18 +0000328 def get_base_vlan_id_by_port(self, port_id):
329 return self._get_element("base_vlan_id", "port", "port_id", port_id)
330
Steve McIntyre2d685c72014-12-08 15:24:12 +0000331 # Simple lookup: look up a current VLAN by ID, and return the IDs
332 # of all the ports on that VLAN.
333 #
334 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000335 def get_ports_by_current_vlan(self, vlan_id):
336 return self._get_multi_elements("port_id", "port", "current_vlan_id", vlan_id)
337
Steve McIntyre2d685c72014-12-08 15:24:12 +0000338 # Simple lookup: look up a base VLAN by ID, and return the IDs
339 # of all the ports on that VLAN.
340 #
341 # Returns None on failure.
Steve McIntyre53a7bc82014-12-05 15:23:34 +0000342 def get_ports_by_base_vlan(self, vlan_id):
343 return self._get_multi_elements("port_id", "port", "base_vlan_id", vlan_id)
344
Steve McIntyre2d685c72014-12-08 15:24:12 +0000345 # Simple lookup: look up a VLAN by ID, and return all the details
346 # of that VLAN.
347 #
348 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000349 def get_vlan_by_id(self, vlan_id):
Steve McIntyref3655062014-12-05 15:34:39 +0000350 return self._get_row("vlan", "vlan_id", vlan_id)
351
Steve McIntyre2d685c72014-12-08 15:24:12 +0000352 # Simple lookup: look up a VLAN by name, and return the ID of that
353 # VLAN.
354 #
355 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000356 def get_vlan_id_by_name(self, name):
357 return self._get_element("vlan_id", "vlan", "name", name)
358
Steve McIntyre2d685c72014-12-08 15:24:12 +0000359 # Simple lookup: look up a VLAN by tag, and return the ID of that
360 # VLAN.
361 #
362 # Returns None on failure.
Steve McIntyref3655062014-12-05 15:34:39 +0000363 def get_vlan_id_by_tag(self, tag):
364 return self._get_element("vlan_id", "vlan", "tag", tag)
365
Steve McIntyre2d685c72014-12-08 15:24:12 +0000366 # Simple lookup: look up a VLAN by ID, and return the name of that
367 # VLAN.
368 #
369 # Returns None on failure.
Steve McIntyre549435f2014-12-05 15:42:46 +0000370 def get_vlan_name_by_id(self, vlan_id):
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100371 return self._get_element("vlan_name", "vlan", "vlan_id", vlan_id)
372
Steve McIntyre2d685c72014-12-08 15:24:12 +0000373 # Grab one row of a query on one column; useful as a quick wrapper
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100374 def _get_row(self, table, field, value):
Steve McIntyree0b842a2014-11-28 18:23:47 +0000375
376 # We really want to use psycopg's type handling deal with the
377 # (potentially) user-supplied data in the value field, so we
378 # have to pass (sql,data) through to cursor.execute. However,
379 # we can't have psycopg do all the argument substitution here
380 # as it will quote all the params like the table name. That
381 # doesn't work. So, we substitute a "%s" for "%s" here so we
382 # keep it after python's own string substitution.
383 sql = "SELECT * FROM %s WHERE %s = %s" % (table, field, "%s")
384
385 # Now, the next icky thing: we need to make sure that we're
386 # passing a dict so that psycopg2 can pick it apart properly
387 # for its own substitution code. We force this with the
388 # trailing comma here
389 data = (value, )
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000390 self.cursor.execute(sql, data)
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100391 return self.cursor.fetchone()
392
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000393 # (Un)Lock a port in the database. This can only be done through
394 # the admin interface, and will stop API users from modifying
395 # settings on the port. Use this to lock down ports that are used
396 # for PDUs and other core infrastructure
397 def set_port_is_locked(self, port_id, is_locked):
Steve McIntyre8c64d952014-12-05 16:22:44 +0000398 port = self.get_port_by_id(port_id)
399 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000400 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000401 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000402 sql = "UPDATE port SET is_locked=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000403 data = (is_locked, port_id)
Steve McIntyre3330f4b2014-11-28 18:11:02 +0000404 self.cursor.execute(sql, data)
405 port_id = self.cursor.fetchone()[0]
406 self.connection.commit()
407 except:
408 self.connection.rollback()
409 raise
410 return port_id
411
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000412 # Set the mode of a port in the database. Valid values for mode
413 # are "trunk" and "access"
414 def set_port_mode(self, port_id, mode):
415 port = self.get_port_by_id(port_id)
416 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000417 raise InputError("Port ID %d does not exist" % int(port_id))
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000418 if mode == "access":
419 is_trunk = False
420 elif mode == "trunk":
421 is_trunk = True
422 else:
423 raise InputError("Port mode %s is not valid" % mode)
424 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000425 sql = "UPDATE port SET is_trunk=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000426 data = (is_trunk, port_id)
Steve McIntyre4204d0d2014-12-05 16:24:10 +0000427 self.cursor.execute(sql, data)
428 port_id = self.cursor.fetchone()[0]
429 self.connection.commit()
430 except:
431 self.connection.rollback()
432 raise
433 return port_id
434
Steve McIntyre2d685c72014-12-08 15:24:12 +0000435 # Set the current vlan of a port in the database. The VLAN is
436 # passed by ID.
437 #
438 # Constraints:
439 # 1. The port must already exist
440 # 2. The port must not be a trunk port
441 # 3. The port must not be locked
442 # 1. The VLAN must already exist in the database
Steve McIntyre9eb78652014-12-05 17:51:53 +0000443 def set_current_vlan(self, port_id, vlan_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000444 port = self.get_port_by_id(port_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000445 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000446 raise InputError("Port ID %d does not exist" % int(port_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100447
Steve McIntyre6dd00be2014-12-05 17:29:35 +0000448 if port.is_trunk or port.is_locked:
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100449 raise CriticalError("The port is locked")
450
Steve McIntyre549435f2014-12-05 15:42:46 +0000451 vlan = self.get_vlan_by_id(vlan_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000452 if vlan is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000453 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100454
455 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000456 sql = "UPDATE port SET current_vlan_id=%s WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000457 data = (vlan_id, port_id)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000458 self.cursor.execute(sql, data)
Steve McIntyree1371102014-12-05 17:17:09 +0000459 port_id = self.cursor.fetchone()[0]
460 self.connection.commit()
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100461 except:
462 self.connection.rollback()
463 raise
Steve McIntyree1371102014-12-05 17:17:09 +0000464 return port_id
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100465
Steve McIntyre2d685c72014-12-08 15:24:12 +0000466 # Set the base vlan of a port in the database. The VLAN is
467 # passed by ID.
468 #
469 # Constraints:
470 # 1. The port must already exist
471 # 2. The port must not be a trunk port
472 # 3. The port must not be locked
473 # 1. The VLAN must already exist in the database
Steve McIntyredaae5502014-12-05 17:55:18 +0000474 def set_base_vlan(self, port_id, vlan_id):
475 port = self.get_port_by_id(port_id)
476 if port is None:
477 raise InputError("Port ID %d does not exist" % int(port_id))
478
479 if port.is_trunk or port.is_locked:
480 raise CriticalError("The port is locked")
481
482 vlan = self.get_vlan_by_id(vlan_id)
483 if vlan is None:
484 raise InputError("VLAN ID %d does not exist" % int(vlan_id))
485 if not vlan.is_base_vlan:
486 raise InputError("VLAN ID %d is not a base VLAN" % int(vlan_id))
487
488 try:
489 sql = "UPDATE port SET base_vlan_id=%s WHERE port_id=%s RETURNING port_id"
490 data = (vlan_id, port_id)
491 self.cursor.execute(sql, data)
492 port_id = self.cursor.fetchone()[0]
493 self.connection.commit()
494 except:
495 self.connection.rollback()
496 raise
497 return port_id
498
Steve McIntyre2d685c72014-12-08 15:24:12 +0000499 # Return a port back to its base VLAN
500 #
501 # Constraints:
502 # 1. The port must already exist
503 # 2. The port must not be a trunk port
504 # 3. The port must not be locked
Steve McIntyredaae5502014-12-05 17:55:18 +0000505 def restore_base_vlan(self, port_id):
Steve McIntyre549435f2014-12-05 15:42:46 +0000506 port = self.get_port_by_id(port_id)
Steve McIntyre028b3cc2014-12-05 16:24:46 +0000507 if port is None:
Steve McIntyrea1c75222014-12-05 16:57:13 +0000508 raise InputError("Port ID %d does not exist" % int(port_id))
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100509
Steve McIntyre6dd00be2014-12-05 17:29:35 +0000510 if port.is_trunk or port.is_locked:
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100511 raise CriticalError("The port is locked")
512
513 try:
Steve McIntyree1371102014-12-05 17:17:09 +0000514 sql = "UPDATE port SET current_vlan_id=base_vlan_id WHERE port_id=%s RETURNING port_id"
Steve McIntyre4b918132014-12-05 17:04:46 +0000515 data = (port_id,)
Steve McIntyredbd7fe52014-11-27 16:54:29 +0000516 self.cursor.execute(sql, data)
Steve McIntyree1371102014-12-05 17:17:09 +0000517 port_id = self.cursor.fetchone()[0]
518 self.connection.commit()
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100519 except:
520 self.connection.rollback()
521 raise
Steve McIntyree1371102014-12-05 17:17:09 +0000522 return port_id
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100523
Steve McIntyre2d685c72014-12-08 15:24:12 +0000524 # Trivial helper function to return all the rows in a given table
Dave Pigott281203e2014-09-17 23:45:02 +0100525 def _dump_table(self, table):
526 result = []
527 self.cursor.execute("SELECT * FROM %s" % table)
Dave Pigott281203e2014-09-17 23:45:02 +0100528 record = self.cursor.fetchone()
529 while record != None:
Steve McIntyree73eb122014-11-27 15:18:47 +0000530 result.append(record)
Dave Pigott281203e2014-09-17 23:45:02 +0100531 record = self.cursor.fetchone()
532 return result
533
534 def all_switches(self):
535 return self._dump_table("switch")
536
537 def all_ports(self):
538 return self._dump_table("port")
539
540 def all_vlans(self):
541 return self._dump_table("vlan")
Dave Pigott9b73f3a2014-09-18 22:55:42 +0100542