blob: 7b48f43c7ceccb38237bf848c53392bb5d3ed0b1 [file] [log] [blame]
tiernof97fd272016-07-11 14:32:37 +02001# -*- coding: utf-8 -*-
2
3##
tierno92021022018-09-12 16:29:23 +02004# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
tiernof97fd272016-07-11 14:32:37 +02005# This file is part of openmano
6# All Rights Reserved.
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License. You may obtain
10# a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17# License for the specific language governing permissions and limitations
18# under the License.
19#
20# For those usages not covered by the Apache License, Version 2.0 please
21# contact with: nfvlabs@tid.es
22##
23
24'''
25Base class for openmano database manipulation
26'''
27__author__="Alfonso Tierno"
28__date__ ="$4-Apr-2016 10:05:01$"
29
30import MySQLdb as mdb
31import uuid as myUuid
32import utils as af
33import json
34#import yaml
35import time
36import logging
37import datetime
38from jsonschema import validate as js_v, exceptions as js_e
39
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010040from .http_tools import errors as httperrors
tiernof97fd272016-07-11 14:32:37 +020041
42def _check_valid_uuid(uuid):
43 id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
44 id_schema2 = {"type" : "string", "pattern": "^[a-fA-F0-9]{32}$"}
45 try:
46 js_v(uuid, id_schema)
47 return True
48 except js_e.ValidationError:
49 try:
50 js_v(uuid, id_schema2)
51 return True
52 except js_e.ValidationError:
53 return False
54 return False
55
56def _convert_datetime2str(var):
57 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
58 It enters recursively in the dict var finding this kind of variables
59 '''
60 if type(var) is dict:
61 for k,v in var.items():
62 if type(v) is datetime.datetime:
63 var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010064 elif type(v) is dict or type(v) is list or type(v) is tuple:
tiernof97fd272016-07-11 14:32:37 +020065 _convert_datetime2str(v)
66 if len(var) == 0: return True
67 elif type(var) is list or type(var) is tuple:
68 for v in var:
69 _convert_datetime2str(v)
70
tierno44528e42016-10-11 12:06:25 +000071def _convert_bandwidth(data, reverse=False, logger=None):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010072 '''Check the field bandwidth recursivelly and when found, it removes units and convert to number
tiernof97fd272016-07-11 14:32:37 +020073 It assumes that bandwidth is well formed
74 Attributes:
75 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
76 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
77 Return:
78 None
79 '''
80 if type(data) is dict:
81 for k in data.keys():
82 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
tierno44528e42016-10-11 12:06:25 +000083 _convert_bandwidth(data[k], reverse, logger)
tiernof97fd272016-07-11 14:32:37 +020084 if "bandwidth" in data:
85 try:
86 value=str(data["bandwidth"])
87 if not reverse:
88 pos = value.find("bps")
89 if pos>0:
90 if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
91 elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
92 else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
93 else:
94 value = int(data["bandwidth"])
95 if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
96 else: data["bandwidth"]=str(value) + " Mbps"
97 except:
tierno44528e42016-10-11 12:06:25 +000098 if logger:
99 logger.error("convert_bandwidth exception for type '%s' data '%s'", type(data["bandwidth"]), data["bandwidth"])
tiernof97fd272016-07-11 14:32:37 +0200100 return
101 if type(data) is tuple or type(data) is list:
102 for k in data:
103 if type(k) is dict or type(k) is tuple or type(k) is list:
tierno44528e42016-10-11 12:06:25 +0000104 _convert_bandwidth(k, reverse, logger)
tiernof97fd272016-07-11 14:32:37 +0200105
106def _convert_str2boolean(data, items):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100107 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
tiernof97fd272016-07-11 14:32:37 +0200108 Done recursively
109 Attributes:
110 'data': dictionary variable to be checked. None or empty is considered valid
111 'items': tuple of keys to convert
112 Return:
113 None
114 '''
115 if type(data) is dict:
116 for k in data.keys():
117 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
118 _convert_str2boolean(data[k], items)
119 if k in items:
120 if type(data[k]) is str:
montesmoreno2a1fc4e2017-01-09 16:46:04 +0000121 if data[k]=="false" or data[k]=="False" or data[k]=="0": data[k]=False
122 elif data[k]=="true" or data[k]=="True" or data[k]=="1": data[k]=True
123 elif type(data[k]) is int:
124 if data[k]==0: data[k]=False
125 elif data[k]==1: data[k]=True
tiernof97fd272016-07-11 14:32:37 +0200126 if type(data) is tuple or type(data) is list:
127 for k in data:
128 if type(k) is dict or type(k) is tuple or type(k) is list:
129 _convert_str2boolean(k, items)
130
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100131class db_base_Exception(httperrors.HttpMappedError):
tiernof97fd272016-07-11 14:32:37 +0200132 '''Common Exception for all database exceptions'''
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100133
134 def __init__(self, message, http_code=httperrors.Bad_Request):
135 super(db_base_Exception, self).__init__(message, http_code)
tiernof97fd272016-07-11 14:32:37 +0200136
137class db_base():
138 tables_with_created_field=()
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100139
tiernob13f3cc2016-09-26 10:14:44 +0200140 def __init__(self, host=None, user=None, passwd=None, database=None, log_name='db', log_level=None):
tiernof97fd272016-07-11 14:32:37 +0200141 self.host = host
142 self.user = user
143 self.passwd = passwd
144 self.database = database
145 self.con = None
146 self.log_level=log_level
147 self.logger = logging.getLogger(log_name)
tiernob13f3cc2016-09-26 10:14:44 +0200148 if self.log_level:
149 self.logger.setLevel( getattr(logging, log_level) )
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100150
tiernof97fd272016-07-11 14:32:37 +0200151 def connect(self, host=None, user=None, passwd=None, database=None):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100152 '''Connect to specific data base.
tiernof97fd272016-07-11 14:32:37 +0200153 The first time a valid host, user, passwd and database must be provided,
154 Following calls can skip this parameters
155 '''
156 try:
157 if host: self.host = host
158 if user: self.user = user
159 if passwd: self.passwd = passwd
160 if database: self.database = database
161
162 self.con = mdb.connect(self.host, self.user, self.passwd, self.database)
tierno44528e42016-10-11 12:06:25 +0000163 self.logger.debug("DB: connected to '%s' at '%s@%s'", self.database, self.user, self.host)
164 except mdb.Error as e:
165 raise db_base_Exception("Cannot connect to DataBase '{}' at '{}@{}' Error {}: {}".format(
166 self.database, self.user, self.host, e.args[0], e.args[1]),
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100167 http_code = httperrors.Unauthorized )
168
169 def escape(self, value):
170 return self.con.escape(value)
171
172
173 def escape_string(self, value):
174 return self.con.escape_string(value)
175
176
tiernof97fd272016-07-11 14:32:37 +0200177 def get_db_version(self):
178 ''' Obtain the database schema version.
179 Return: (negative, text) if error or version 0.0 where schema_version table is missing
180 (version_int, version_text) if ok
181 '''
182 cmd = "SELECT version_int,version FROM schema_version"
183 tries = 2
184 while tries:
185 try:
186 with self.con:
187 self.cur = self.con.cursor()
188 self.logger.debug(cmd)
189 self.cur.execute(cmd)
190 rows = self.cur.fetchall()
191 highest_version_int=0
192 highest_version=""
193 for row in rows: #look for the latest version
194 if row[0]>highest_version_int:
195 highest_version_int, highest_version = row[0:2]
196 return highest_version_int, highest_version
197 except (mdb.Error, AttributeError) as e:
tiernofc5f80b2018-05-29 16:00:43 +0200198 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
tiernof97fd272016-07-11 14:32:37 +0200199 #self.logger.error("get_db_version DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd)
200 self._format_error(e, tries)
201 tries -= 1
202
203 def disconnect(self):
204 '''disconnect from specific data base'''
205 try:
206 self.con.close()
207 self.con = None
208 except mdb.Error as e:
209 self.logger.error("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1])
210 return
211 except AttributeError as e: #self.con not defined
212 if e[0][-5:] == "'con'":
213 self.logger.warn("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1])
214 return
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100215 else:
tiernof97fd272016-07-11 14:32:37 +0200216 raise
217
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100218 def _format_error(self, e, tries=1, command=None, extra=None, table=None):
tiernof97fd272016-07-11 14:32:37 +0200219 '''Creates a text error base on the produced exception
220 Params:
221 e: mdb exception
222 retry: in case of timeout, if reconnecting to database and retry, or raise and exception
223 cmd: database command that produce the exception
224 command: if the intention is update or delete
225 extra: extra information to add to some commands
226 Return
227 HTTP error in negative, formatted error text
228 '''
229 if isinstance(e,AttributeError ):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100230 self.logger.debug(str(e), exc_info=True)
231 raise db_base_Exception("DB Exception " + str(e), httperrors.Internal_Server_Error)
tiernof97fd272016-07-11 14:32:37 +0200232 if e.args[0]==2006 or e.args[0]==2013 : #MySQL server has gone away (((or))) Exception 2013: Lost connection to MySQL server during query
233 if tries>1:
234 self.logger.warn("DB Exception '%s'. Retry", str(e))
235 #reconnect
236 self.connect()
237 return
238 else:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100239 raise db_base_Exception("Database connection timeout Try Again", httperrors.Request_Timeout)
240
tiernof97fd272016-07-11 14:32:37 +0200241 fk=e.args[1].find("foreign key constraint fails")
242 if fk>=0:
243 if command=="update":
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100244 raise db_base_Exception("tenant_id '{}' not found.".format(extra), httperrors.Not_Found)
tiernof97fd272016-07-11 14:32:37 +0200245 elif command=="delete":
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100246 raise db_base_Exception("Resource is not free. There are {} that prevent deleting it.".format(extra), httperrors.Conflict)
tiernof97fd272016-07-11 14:32:37 +0200247 de = e.args[1].find("Duplicate entry")
248 fk = e.args[1].find("for key")
249 uk = e.args[1].find("Unknown column")
250 wc = e.args[1].find("in 'where clause'")
251 fl = e.args[1].find("in 'field list'")
252 #print de, fk, uk, wc,fl
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100253 table_info = ' (table `{}`)'.format(table) if table else ''
tiernof97fd272016-07-11 14:32:37 +0200254 if de>=0:
255 if fk>=0: #error 1062
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100256 raise db_base_Exception(
257 "Value {} already in use for {}{}".format(
258 e.args[1][de+15:fk], e.args[1][fk+7:], table_info),
259 httperrors.Conflict)
tiernof97fd272016-07-11 14:32:37 +0200260 if uk>=0:
261 if wc>=0:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100262 raise db_base_Exception(
263 "Field {} can not be used for filtering{}".format(
264 e.args[1][uk+14:wc], table_info),
265 httperrors.Bad_Request)
tiernof97fd272016-07-11 14:32:37 +0200266 if fl>=0:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100267 raise db_base_Exception(
268 "Field {} does not exist{}".format(
269 e.args[1][uk+14:wc], table_info),
270 httperrors.Bad_Request)
271 raise db_base_Exception(
272 "Database internal Error{} {}: {}".format(
273 table_info, e.args[0], e.args[1]),
274 httperrors.Internal_Server_Error)
275
tiernof97fd272016-07-11 14:32:37 +0200276 def __str2db_format(self, data):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100277 '''Convert string data to database format.
tiernof97fd272016-07-11 14:32:37 +0200278 If data is None it returns the 'Null' text,
279 otherwise it returns the text surrounded by quotes ensuring internal quotes are escaped.
280 '''
281 if data==None:
282 return 'Null'
tierno86fad562017-04-05 19:53:54 +0200283 elif isinstance(data[1], str):
284 return json.dumps(data)
tiernof97fd272016-07-11 14:32:37 +0200285 else:
286 return json.dumps(str(data))
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100287
tiernof97fd272016-07-11 14:32:37 +0200288 def __tuple2db_format_set(self, data):
tierno868220c2017-09-26 00:11:05 +0200289 """Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B),
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100290 and it returns the text 'A="B"', where A is a field of a table and B is the value
tiernof97fd272016-07-11 14:32:37 +0200291 If B is None it returns the 'A=Null' text, without surrounding Null by quotes
292 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
293 and it ensures internal quotes of B are escaped.
tierno868220c2017-09-26 00:11:05 +0200294 B can be also a dict with special keys:
295 {"INCREMENT": NUMBER}, then it produce "A=A+NUMBER"
296 """
297 if data[1] == None:
tiernof97fd272016-07-11 14:32:37 +0200298 return str(data[0]) + "=Null"
tierno86fad562017-04-05 19:53:54 +0200299 elif isinstance(data[1], str):
300 return str(data[0]) + '=' + json.dumps(data[1])
tierno868220c2017-09-26 00:11:05 +0200301 elif isinstance(data[1], dict):
302 if "INCREMENT" in data[1]:
303 return "{A}={A}{N:+d}".format(A=data[0], N=data[1]["INCREMENT"])
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100304 raise db_base_Exception("Format error for UPDATE field: {!r}".format(data[0]))
tiernof97fd272016-07-11 14:32:37 +0200305 else:
306 return str(data[0]) + '=' + json.dumps(str(data[1]))
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100307
tierno3fcfdb72017-10-24 07:48:24 +0200308 def __create_where(self, data, use_or=None):
309 """
310 Compose the needed text for a SQL WHERE, parameter 'data' can be a dict or a list of dict. By default lists are
311 concatenated with OR and dict with AND, unless parameter 'use_or' indicates other thing.
312 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
313 If value is None, it will produce 'key is null'
314 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
tierno16e3dd42018-04-24 12:52:40 +0200315 keys can be suffixed by >,<,<>,>=,<=,' LIKE ' so that this is used to compare key and value instead of "="
tierno3fcfdb72017-10-24 07:48:24 +0200316 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
317 If a list, each item will be a dictionary that will be concatenated with OR by default
318 :param data: dict or list of dicts
319 :param use_or: Can be None (use default behaviour), True (use OR) or False (use AND)
320 :return: a string with the content to send to mysql
321 """
322 cmd = []
323 if isinstance(data, dict):
324 for k, v in data.items():
325 if k == "OR":
326 cmd.append("(" + self.__create_where(v, use_or=True) + ")")
327 continue
328 elif k == "AND":
329 cmd.append("(" + self.__create_where(v, use_or=False) + ")")
330 continue
tiernof97fd272016-07-11 14:32:37 +0200331
tierno16e3dd42018-04-24 12:52:40 +0200332 if k.endswith(">") or k.endswith("<") or k.endswith("=") or k.endswith(" LIKE "):
tierno3fcfdb72017-10-24 07:48:24 +0200333 pass
334 else:
335 k += "="
336
337 if v is None:
338 cmd.append(k.replace("=", " is").replace("<>", " is not") + " Null")
339 elif isinstance(v, (tuple, list)):
340 cmd2 = []
341 for v2 in v:
342 if v2 is None:
343 cmd2.append(k.replace("=", " is").replace("<>", " is not") + " Null")
344 else:
345 cmd2.append(k + json.dumps(str(v2)))
346 cmd.append("(" + " OR ".join(cmd2) + ")")
347 else:
348 cmd.append(k + json.dumps(str(v)))
349 elif isinstance(data, (tuple, list)):
350 if use_or is None:
351 use_or = True
352 for k in data:
353 cmd.append("(" + self.__create_where(k) + ")")
tierno86fad562017-04-05 19:53:54 +0200354 else:
tierno3fcfdb72017-10-24 07:48:24 +0200355 raise db_base_Exception("invalid WHERE clause at '{}'".format(data))
356 if use_or:
357 return " OR ".join(cmd)
358 return " AND ".join(cmd)
359
tiernof97fd272016-07-11 14:32:37 +0200360 def __remove_quotes(self, data):
361 '''remove single quotes ' of any string content of data dictionary'''
362 for k,v in data.items():
363 if type(v) == str:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100364 if "'" in v:
tiernof97fd272016-07-11 14:32:37 +0200365 data[k] = data[k].replace("'","_")
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100366
tiernof97fd272016-07-11 14:32:37 +0200367 def _update_rows(self, table, UPDATE, WHERE, modified_time=0):
tierno3fcfdb72017-10-24 07:48:24 +0200368 """ Update one or several rows of a table.
369 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
370 :param table: database table to update
371 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
372 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
373 If value is None, it will produce 'key is null'
374 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
375 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
376 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
377 If a list, each item will be a dictionary that will be concatenated with OR
378 :return: the number of updated rows, raises exception upon error
379 """
380 # gettting uuid
tiernof97fd272016-07-11 14:32:37 +0200381 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
382 if modified_time:
383 values += ",modified_at={:f}".format(modified_time)
tierno3fcfdb72017-10-24 07:48:24 +0200384 cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE)
tiernof97fd272016-07-11 14:32:37 +0200385 self.logger.debug(cmd)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100386 self.cur.execute(cmd)
tiernof97fd272016-07-11 14:32:37 +0200387 return self.cur.rowcount
tierno8e690322017-08-10 15:58:50 +0200388
389 def _new_uuid(self, root_uuid=None, used_table=None, created_time=0):
390 """
391 Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created
392 :param root_uuid: master uuid of the transaction
393 :param used_table: the table this uuid is intended for
394 :param created_time: time of creation
395 :return: the created uuid
396 """
397
398 uuid = str(myUuid.uuid1())
399 # defining root_uuid if not provided
400 if root_uuid is None:
401 root_uuid = uuid
402 if created_time:
403 created_at = created_time
404 else:
405 created_at = time.time()
406 # inserting new uuid
407 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(
408 uuid, root_uuid, used_table, created_at)
409 self.logger.debug(cmd)
410 self.cur.execute(cmd)
411 return uuid
412
gcalvinoc62cfa52017-10-05 18:21:25 +0200413 def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0, confidential_data=False):
tiernof97fd272016-07-11 14:32:37 +0200414 ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100415 Attribute
tiernof97fd272016-07-11 14:32:37 +0200416 INSERT: dictionary with the key:value to insert
417 table: table where to insert
418 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
419 created_time: time to add to the created_time column
420 It checks presence of uuid and add one automatically otherwise
421 Return: uuid
422 '''
423
424 if add_uuid:
425 #create uuid if not provided
426 if 'uuid' not in INSERT:
427 uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100428 else:
tiernof97fd272016-07-11 14:32:37 +0200429 uuid = str(INSERT['uuid'])
430 else:
431 uuid=None
432 if add_uuid:
433 #defining root_uuid if not provided
434 if root_uuid is None:
435 root_uuid = uuid
436 if created_time:
437 created_at = created_time
438 else:
439 created_at=time.time()
440 #inserting new uuid
441 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at)
442 self.logger.debug(cmd)
443 self.cur.execute(cmd)
444 #insertion
445 cmd= "INSERT INTO " + table +" SET " + \
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100446 ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() ))
tiernof97fd272016-07-11 14:32:37 +0200447 if created_time:
448 cmd += ",created_at=%f" % created_time
gcalvinoc62cfa52017-10-05 18:21:25 +0200449 if confidential_data:
450 index = cmd.find("SET")
451 subcmd = cmd[:index] + 'SET...'
452 self.logger.debug(subcmd)
453 else:
454 self.logger.debug(cmd)
tiernof97fd272016-07-11 14:32:37 +0200455 self.cur.execute(cmd)
456 self.cur.rowcount
457 return uuid
458
459 def _get_rows(self,table,uuid):
460 cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid))
461 self.logger.debug(cmd)
462 self.cur.execute(cmd)
463 rows = self.cur.fetchall()
464 return rows
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100465
gcalvinoc62cfa52017-10-05 18:21:25 +0200466 def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False):
tiernof97fd272016-07-11 14:32:37 +0200467 ''' Add one row into a table.
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100468 Attribute
tiernof97fd272016-07-11 14:32:37 +0200469 INSERT: dictionary with the key: value to insert
470 table: table where to insert
471 tenant_id: only useful for logs. If provided, logs will use this tenant_id
472 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
473 It checks presence of uuid and add one automatically otherwise
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100474 Return: uuid
tiernof97fd272016-07-11 14:32:37 +0200475 '''
476 if table in self.tables_with_created_field and created_time==0:
477 created_time=time.time()
478 tries = 2
479 while tries:
480 try:
481 with self.con:
482 self.cur = self.con.cursor()
gcalvinoc62cfa52017-10-05 18:21:25 +0200483 return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100484
tiernof97fd272016-07-11 14:32:37 +0200485 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100486 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200487 tries -= 1
488
489 def update_rows(self, table, UPDATE, WHERE, modified_time=0):
tierno3fcfdb72017-10-24 07:48:24 +0200490 """ Update one or several rows of a table.
491 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
492 :param table: database table to update
493 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
494 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
495 If value is None, it will produce 'key is null'
496 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
497 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
498 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
499 If a list, each item will be a dictionary that will be concatenated with OR
500 :param modified_time: Can contain the time to be set to the table row
501 :return: the number of updated rows, raises exception upon error
502 """
tiernof97fd272016-07-11 14:32:37 +0200503 if table in self.tables_with_created_field and modified_time==0:
504 modified_time=time.time()
505 tries = 2
506 while tries:
507 try:
508 with self.con:
509 self.cur = self.con.cursor()
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100510 return self._update_rows(
511 table, UPDATE, WHERE, modified_time)
tiernof97fd272016-07-11 14:32:37 +0200512 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100513 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200514 tries -= 1
515
tiernofc5f80b2018-05-29 16:00:43 +0200516 def _delete_row_by_id_internal(self, table, uuid):
517 cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid)
518 self.logger.debug(cmd)
519 self.cur.execute(cmd)
520 deleted = self.cur.rowcount
521 # delete uuid
522 self.cur = self.con.cursor()
523 cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid)
524 self.logger.debug(cmd)
525 self.cur.execute(cmd)
526 return deleted
527
tiernof97fd272016-07-11 14:32:37 +0200528 def delete_row_by_id(self, table, uuid):
529 tries = 2
530 while tries:
531 try:
532 with self.con:
tiernof97fd272016-07-11 14:32:37 +0200533 self.cur = self.con.cursor()
tiernofc5f80b2018-05-29 16:00:43 +0200534 return self._delete_row_by_id_internal(table, uuid)
tiernof97fd272016-07-11 14:32:37 +0200535 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100536 self._format_error(
537 e, tries, "delete", "dependencies", table=table)
tiernof97fd272016-07-11 14:32:37 +0200538 tries -= 1
539
540 def delete_row(self, **sql_dict):
tierno3fcfdb72017-10-24 07:48:24 +0200541 """ Deletes rows from a table.
542 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
543 :param FROM: string with table name (Mandatory)
544 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
545 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
546 If value is None, it will produce 'key is null'
547 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
548 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
549 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
550 If a list, each item will be a dictionary that will be concatenated with OR
551 :return: the number of deleted rows, raises exception upon error
552 """
553 # print sql_dict
554 cmd = "DELETE FROM " + str(sql_dict['FROM'])
555 if sql_dict.get('WHERE'):
556 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
557 if sql_dict.get('LIMIT'):
558 cmd += " LIMIT " + str(sql_dict['LIMIT'])
tiernof97fd272016-07-11 14:32:37 +0200559 tries = 2
560 while tries:
561 try:
562 with self.con:
563 self.cur = self.con.cursor()
564 self.logger.debug(cmd)
565 self.cur.execute(cmd)
566 deleted = self.cur.rowcount
567 return deleted
568 except (mdb.Error, AttributeError) as e:
569 self._format_error(e, tries)
570 tries -= 1
571
572 def get_rows_by_id(self, table, uuid):
573 '''get row from a table based on uuid'''
574 tries = 2
575 while tries:
576 try:
577 with self.con:
578 self.cur = self.con.cursor(mdb.cursors.DictCursor)
579 cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid))
580 self.logger.debug(cmd)
581 self.cur.execute(cmd)
582 rows = self.cur.fetchall()
583 return rows
584 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100585 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200586 tries -= 1
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100587
tiernof97fd272016-07-11 14:32:37 +0200588 def get_rows(self, **sql_dict):
tierno3fcfdb72017-10-24 07:48:24 +0200589 """ Obtain rows from a table.
590 :param SELECT: list or tuple of fields to retrieve) (by default all)
591 :param FROM: string with table name (Mandatory)
592 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
593 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
594 If value is None, it will produce 'key is null'
595 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
596 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
597 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
598 If a list, each item will be a dictionary that will be concatenated with OR
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100599 :param LIMIT: limit the number of obtained entries (Optional)
tierno3fcfdb72017-10-24 07:48:24 +0200600 :param ORDER_BY: list or tuple of fields to order, add ' DESC' to each item if inverse order is required
601 :return: a list with dictionaries at each row, raises exception upon error
602 """
603 # print sql_dict
604 cmd = "SELECT "
605 if 'SELECT' in sql_dict:
606 if isinstance(sql_dict['SELECT'], (tuple, list)):
607 cmd += ",".join(map(str, sql_dict['SELECT']))
tiernof97fd272016-07-11 14:32:37 +0200608 else:
tierno3fcfdb72017-10-24 07:48:24 +0200609 cmd += sql_dict['SELECT']
tiernof97fd272016-07-11 14:32:37 +0200610 else:
tierno3fcfdb72017-10-24 07:48:24 +0200611 cmd += "*"
612
613 cmd += " FROM " + str(sql_dict['FROM'])
614 if sql_dict.get('WHERE'):
615 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
616
617 if 'ORDER_BY' in sql_dict:
618 cmd += " ORDER BY "
619 if isinstance(sql_dict['ORDER_BY'], (tuple, list)):
620 cmd += ",".join(map(str, sql_dict['ORDER_BY']))
621 else:
622 cmd += str(sql_dict['ORDER_BY'])
623
624 if 'LIMIT' in sql_dict:
625 cmd += " LIMIT " + str(sql_dict['LIMIT'])
626
tiernof97fd272016-07-11 14:32:37 +0200627 tries = 2
628 while tries:
629 try:
630 with self.con:
631 self.cur = self.con.cursor(mdb.cursors.DictCursor)
632 self.logger.debug(cmd)
633 self.cur.execute(cmd)
634 rows = self.cur.fetchall()
635 return rows
636 except (mdb.Error, AttributeError) as e:
tiernofc5f80b2018-05-29 16:00:43 +0200637 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
tiernof97fd272016-07-11 14:32:37 +0200638 self._format_error(e, tries)
639 tries -= 1
640
641 def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"):
642 ''' Obtain One row from a table based on name or uuid.
643 Attribute:
644 table: string of table name
645 uuid_name: name or uuid. If not uuid format is found, it is considered a name
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100646 allow_severeral: if False return ERROR if more than one row are founded
647 error_item_text: in case of error it identifies the 'item' name for a proper output text
tiernof97fd272016-07-11 14:32:37 +0200648 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100649 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional
tiernof97fd272016-07-11 14:32:37 +0200650 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
651 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
652 '''
653
654 if error_item_text==None:
655 error_item_text = table
656 what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name'
tierno3fcfdb72017-10-24 07:48:24 +0200657 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
tiernof97fd272016-07-11 14:32:37 +0200658 if WHERE_OR:
tierno3fcfdb72017-10-24 07:48:24 +0200659 where_or = self.__create_where(WHERE_OR, use_or=True)
tiernof97fd272016-07-11 14:32:37 +0200660 if WHERE_AND_OR == "AND":
661 cmd += " AND (" + where_or + ")"
662 else:
663 cmd += " OR " + where_or
664
tiernof97fd272016-07-11 14:32:37 +0200665 tries = 2
666 while tries:
667 try:
668 with self.con:
669 self.cur = self.con.cursor(mdb.cursors.DictCursor)
670 self.logger.debug(cmd)
671 self.cur.execute(cmd)
672 number = self.cur.rowcount
tiernocec213a2018-06-27 16:06:17 +0200673 if number == 0:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100674 raise db_base_Exception("No {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=httperrors.Not_Found)
tiernocec213a2018-06-27 16:06:17 +0200675 elif number > 1 and not allow_serveral:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100676 raise db_base_Exception("More than one {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=httperrors.Conflict)
tiernof97fd272016-07-11 14:32:37 +0200677 if allow_serveral:
678 rows = self.cur.fetchall()
679 else:
680 rows = self.cur.fetchone()
681 return rows
682 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100683 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200684 tries -= 1
685
686 def get_uuid(self, uuid):
687 '''check in the database if this uuid is already present'''
tiernocec213a2018-06-27 16:06:17 +0200688 tries = 2
689 while tries:
tiernof97fd272016-07-11 14:32:37 +0200690 try:
691 with self.con:
692 self.cur = self.con.cursor(mdb.cursors.DictCursor)
693 self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'")
694 rows = self.cur.fetchall()
695 return self.cur.rowcount, rows
696 except (mdb.Error, AttributeError) as e:
tiernocec213a2018-06-27 16:06:17 +0200697 self._format_error(e, tries)
698 tries -= 1
tiernof97fd272016-07-11 14:32:37 +0200699
700 def get_uuid_from_name(self, table, name):
701 '''Searchs in table the name and returns the uuid
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100702 '''
tiernof97fd272016-07-11 14:32:37 +0200703 tries = 2
704 while tries:
705 try:
706 with self.con:
707 self.cur = self.con.cursor(mdb.cursors.DictCursor)
708 where_text = "name='" + name +"'"
709 self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text)
710 rows = self.cur.fetchall()
711 if self.cur.rowcount==0:
712 return 0, "Name %s not found in table %s" %(name, table)
713 elif self.cur.rowcount>1:
714 return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table)
715 return self.cur.rowcount, rows[0]["uuid"]
716 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100717 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200718 tries -= 1
719