blob: e7a4fb327bb43753c890c6dacba70ba13fb8a56d [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
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100172 def escape_string(self, value):
tiernoa3ebc362018-12-05 16:23:38 +0000173 if isinstance(value, "unicode"):
174 value = value.encode("utf8")
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100175 return self.con.escape_string(value)
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):
tiernoa3ebc362018-12-05 16:23:38 +0000277 """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.
tiernoa3ebc362018-12-05 16:23:38 +0000280 """
281 if data is None:
tiernof97fd272016-07-11 14:32:37 +0200282 return 'Null'
tiernoa3ebc362018-12-05 16:23:38 +0000283 elif isinstance(data[1], (str, unicode)):
tierno86fad562017-04-05 19:53:54 +0200284 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 """
tiernoa3ebc362018-12-05 16:23:38 +0000297 if data[1] is None:
tiernof97fd272016-07-11 14:32:37 +0200298 return str(data[0]) + "=Null"
tiernoa3ebc362018-12-05 16:23:38 +0000299 elif isinstance(data[1], (str, unicode)):
tierno86fad562017-04-05 19:53:54 +0200300 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")
tiernoa3ebc362018-12-05 16:23:38 +0000344 elif isinstance(v2, (str, unicode)):
345 cmd2.append(k + json.dumps(v2))
tierno3fcfdb72017-10-24 07:48:24 +0200346 else:
347 cmd2.append(k + json.dumps(str(v2)))
348 cmd.append("(" + " OR ".join(cmd2) + ")")
tiernoa3ebc362018-12-05 16:23:38 +0000349 elif isinstance(v, (str, unicode)):
350 cmd.append(k + json.dumps(v))
tierno3fcfdb72017-10-24 07:48:24 +0200351 else:
352 cmd.append(k + json.dumps(str(v)))
353 elif isinstance(data, (tuple, list)):
354 if use_or is None:
355 use_or = True
356 for k in data:
357 cmd.append("(" + self.__create_where(k) + ")")
tierno86fad562017-04-05 19:53:54 +0200358 else:
tierno3fcfdb72017-10-24 07:48:24 +0200359 raise db_base_Exception("invalid WHERE clause at '{}'".format(data))
360 if use_or:
361 return " OR ".join(cmd)
362 return " AND ".join(cmd)
363
tiernof97fd272016-07-11 14:32:37 +0200364 def __remove_quotes(self, data):
365 '''remove single quotes ' of any string content of data dictionary'''
366 for k,v in data.items():
367 if type(v) == str:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100368 if "'" in v:
tiernof97fd272016-07-11 14:32:37 +0200369 data[k] = data[k].replace("'","_")
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100370
tiernof97fd272016-07-11 14:32:37 +0200371 def _update_rows(self, table, UPDATE, WHERE, modified_time=0):
tierno3fcfdb72017-10-24 07:48:24 +0200372 """ Update one or several rows of a table.
373 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
374 :param table: database table to update
375 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
376 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
377 If value is None, it will produce 'key is null'
378 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
379 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
380 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
381 If a list, each item will be a dictionary that will be concatenated with OR
382 :return: the number of updated rows, raises exception upon error
383 """
384 # gettting uuid
tiernof97fd272016-07-11 14:32:37 +0200385 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
386 if modified_time:
387 values += ",modified_at={:f}".format(modified_time)
tierno3fcfdb72017-10-24 07:48:24 +0200388 cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE)
tiernof97fd272016-07-11 14:32:37 +0200389 self.logger.debug(cmd)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100390 self.cur.execute(cmd)
tiernof97fd272016-07-11 14:32:37 +0200391 return self.cur.rowcount
tierno8e690322017-08-10 15:58:50 +0200392
393 def _new_uuid(self, root_uuid=None, used_table=None, created_time=0):
394 """
395 Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created
396 :param root_uuid: master uuid of the transaction
397 :param used_table: the table this uuid is intended for
398 :param created_time: time of creation
399 :return: the created uuid
400 """
401
402 uuid = str(myUuid.uuid1())
403 # defining root_uuid if not provided
404 if root_uuid is None:
405 root_uuid = uuid
406 if created_time:
407 created_at = created_time
408 else:
409 created_at = time.time()
410 # inserting new uuid
411 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(
412 uuid, root_uuid, used_table, created_at)
413 self.logger.debug(cmd)
414 self.cur.execute(cmd)
415 return uuid
416
gcalvinoc62cfa52017-10-05 18:21:25 +0200417 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 +0200418 ''' 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 +0100419 Attribute
tiernof97fd272016-07-11 14:32:37 +0200420 INSERT: dictionary with the key:value to insert
421 table: table where to insert
422 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
423 created_time: time to add to the created_time column
424 It checks presence of uuid and add one automatically otherwise
425 Return: uuid
426 '''
427
428 if add_uuid:
429 #create uuid if not provided
430 if 'uuid' not in INSERT:
431 uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100432 else:
tiernof97fd272016-07-11 14:32:37 +0200433 uuid = str(INSERT['uuid'])
434 else:
435 uuid=None
436 if add_uuid:
437 #defining root_uuid if not provided
438 if root_uuid is None:
439 root_uuid = uuid
440 if created_time:
441 created_at = created_time
442 else:
443 created_at=time.time()
444 #inserting new uuid
445 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at)
446 self.logger.debug(cmd)
447 self.cur.execute(cmd)
448 #insertion
449 cmd= "INSERT INTO " + table +" SET " + \
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100450 ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() ))
tiernof97fd272016-07-11 14:32:37 +0200451 if created_time:
452 cmd += ",created_at=%f" % created_time
gcalvinoc62cfa52017-10-05 18:21:25 +0200453 if confidential_data:
454 index = cmd.find("SET")
455 subcmd = cmd[:index] + 'SET...'
456 self.logger.debug(subcmd)
457 else:
458 self.logger.debug(cmd)
tiernof97fd272016-07-11 14:32:37 +0200459 self.cur.execute(cmd)
460 self.cur.rowcount
461 return uuid
462
463 def _get_rows(self,table,uuid):
464 cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid))
465 self.logger.debug(cmd)
466 self.cur.execute(cmd)
467 rows = self.cur.fetchall()
468 return rows
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100469
gcalvinoc62cfa52017-10-05 18:21:25 +0200470 def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False):
tiernof97fd272016-07-11 14:32:37 +0200471 ''' Add one row into a table.
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100472 Attribute
tiernof97fd272016-07-11 14:32:37 +0200473 INSERT: dictionary with the key: value to insert
474 table: table where to insert
475 tenant_id: only useful for logs. If provided, logs will use this tenant_id
476 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
477 It checks presence of uuid and add one automatically otherwise
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100478 Return: uuid
tiernof97fd272016-07-11 14:32:37 +0200479 '''
480 if table in self.tables_with_created_field and created_time==0:
481 created_time=time.time()
482 tries = 2
483 while tries:
484 try:
485 with self.con:
486 self.cur = self.con.cursor()
gcalvinoc62cfa52017-10-05 18:21:25 +0200487 return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100488
tiernof97fd272016-07-11 14:32:37 +0200489 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100490 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200491 tries -= 1
492
493 def update_rows(self, table, UPDATE, WHERE, modified_time=0):
tierno3fcfdb72017-10-24 07:48:24 +0200494 """ Update one or several rows of a table.
495 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
496 :param table: database table to update
497 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
498 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
499 If value is None, it will produce 'key is null'
500 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
501 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
502 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
503 If a list, each item will be a dictionary that will be concatenated with OR
504 :param modified_time: Can contain the time to be set to the table row
505 :return: the number of updated rows, raises exception upon error
506 """
tiernof97fd272016-07-11 14:32:37 +0200507 if table in self.tables_with_created_field and modified_time==0:
508 modified_time=time.time()
509 tries = 2
510 while tries:
511 try:
512 with self.con:
513 self.cur = self.con.cursor()
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100514 return self._update_rows(
515 table, UPDATE, WHERE, modified_time)
tiernof97fd272016-07-11 14:32:37 +0200516 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100517 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200518 tries -= 1
519
tiernofc5f80b2018-05-29 16:00:43 +0200520 def _delete_row_by_id_internal(self, table, uuid):
521 cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid)
522 self.logger.debug(cmd)
523 self.cur.execute(cmd)
524 deleted = self.cur.rowcount
525 # delete uuid
526 self.cur = self.con.cursor()
527 cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid)
528 self.logger.debug(cmd)
529 self.cur.execute(cmd)
530 return deleted
531
tiernof97fd272016-07-11 14:32:37 +0200532 def delete_row_by_id(self, table, uuid):
533 tries = 2
534 while tries:
535 try:
536 with self.con:
tiernof97fd272016-07-11 14:32:37 +0200537 self.cur = self.con.cursor()
tiernofc5f80b2018-05-29 16:00:43 +0200538 return self._delete_row_by_id_internal(table, uuid)
tiernof97fd272016-07-11 14:32:37 +0200539 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100540 self._format_error(
541 e, tries, "delete", "dependencies", table=table)
tiernof97fd272016-07-11 14:32:37 +0200542 tries -= 1
543
544 def delete_row(self, **sql_dict):
tierno3fcfdb72017-10-24 07:48:24 +0200545 """ Deletes rows from a table.
546 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
547 :param FROM: string with table name (Mandatory)
548 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
549 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
550 If value is None, it will produce 'key is null'
551 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
552 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
553 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
554 If a list, each item will be a dictionary that will be concatenated with OR
555 :return: the number of deleted rows, raises exception upon error
556 """
557 # print sql_dict
558 cmd = "DELETE FROM " + str(sql_dict['FROM'])
559 if sql_dict.get('WHERE'):
560 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
561 if sql_dict.get('LIMIT'):
562 cmd += " LIMIT " + str(sql_dict['LIMIT'])
tiernof97fd272016-07-11 14:32:37 +0200563 tries = 2
564 while tries:
565 try:
566 with self.con:
567 self.cur = self.con.cursor()
568 self.logger.debug(cmd)
569 self.cur.execute(cmd)
570 deleted = self.cur.rowcount
571 return deleted
572 except (mdb.Error, AttributeError) as e:
573 self._format_error(e, tries)
574 tries -= 1
575
576 def get_rows_by_id(self, table, uuid):
577 '''get row from a table based on uuid'''
578 tries = 2
579 while tries:
580 try:
581 with self.con:
582 self.cur = self.con.cursor(mdb.cursors.DictCursor)
583 cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid))
584 self.logger.debug(cmd)
585 self.cur.execute(cmd)
586 rows = self.cur.fetchall()
587 return rows
588 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100589 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200590 tries -= 1
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100591
tiernof97fd272016-07-11 14:32:37 +0200592 def get_rows(self, **sql_dict):
tierno3fcfdb72017-10-24 07:48:24 +0200593 """ Obtain rows from a table.
594 :param SELECT: list or tuple of fields to retrieve) (by default all)
595 :param FROM: string with table name (Mandatory)
596 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
597 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
598 If value is None, it will produce 'key is null'
599 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
600 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
601 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
602 If a list, each item will be a dictionary that will be concatenated with OR
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100603 :param LIMIT: limit the number of obtained entries (Optional)
tierno3fcfdb72017-10-24 07:48:24 +0200604 :param ORDER_BY: list or tuple of fields to order, add ' DESC' to each item if inverse order is required
605 :return: a list with dictionaries at each row, raises exception upon error
606 """
607 # print sql_dict
608 cmd = "SELECT "
609 if 'SELECT' in sql_dict:
610 if isinstance(sql_dict['SELECT'], (tuple, list)):
611 cmd += ",".join(map(str, sql_dict['SELECT']))
tiernof97fd272016-07-11 14:32:37 +0200612 else:
tierno3fcfdb72017-10-24 07:48:24 +0200613 cmd += sql_dict['SELECT']
tiernof97fd272016-07-11 14:32:37 +0200614 else:
tierno3fcfdb72017-10-24 07:48:24 +0200615 cmd += "*"
616
617 cmd += " FROM " + str(sql_dict['FROM'])
618 if sql_dict.get('WHERE'):
619 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
620
621 if 'ORDER_BY' in sql_dict:
622 cmd += " ORDER BY "
623 if isinstance(sql_dict['ORDER_BY'], (tuple, list)):
624 cmd += ",".join(map(str, sql_dict['ORDER_BY']))
625 else:
626 cmd += str(sql_dict['ORDER_BY'])
627
628 if 'LIMIT' in sql_dict:
629 cmd += " LIMIT " + str(sql_dict['LIMIT'])
630
tiernof97fd272016-07-11 14:32:37 +0200631 tries = 2
632 while tries:
633 try:
634 with self.con:
635 self.cur = self.con.cursor(mdb.cursors.DictCursor)
636 self.logger.debug(cmd)
637 self.cur.execute(cmd)
638 rows = self.cur.fetchall()
639 return rows
640 except (mdb.Error, AttributeError) as e:
tiernofc5f80b2018-05-29 16:00:43 +0200641 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
tiernof97fd272016-07-11 14:32:37 +0200642 self._format_error(e, tries)
643 tries -= 1
644
645 def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"):
646 ''' Obtain One row from a table based on name or uuid.
647 Attribute:
648 table: string of table name
649 uuid_name: name or uuid. If not uuid format is found, it is considered a name
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100650 allow_severeral: if False return ERROR if more than one row are founded
651 error_item_text: in case of error it identifies the 'item' name for a proper output text
tiernof97fd272016-07-11 14:32:37 +0200652 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100653 '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 +0200654 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
655 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
656 '''
657
658 if error_item_text==None:
659 error_item_text = table
660 what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name'
tierno3fcfdb72017-10-24 07:48:24 +0200661 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
tiernof97fd272016-07-11 14:32:37 +0200662 if WHERE_OR:
tierno3fcfdb72017-10-24 07:48:24 +0200663 where_or = self.__create_where(WHERE_OR, use_or=True)
tiernof97fd272016-07-11 14:32:37 +0200664 if WHERE_AND_OR == "AND":
665 cmd += " AND (" + where_or + ")"
666 else:
667 cmd += " OR " + where_or
668
tiernof97fd272016-07-11 14:32:37 +0200669 tries = 2
670 while tries:
671 try:
672 with self.con:
673 self.cur = self.con.cursor(mdb.cursors.DictCursor)
674 self.logger.debug(cmd)
675 self.cur.execute(cmd)
676 number = self.cur.rowcount
tiernocec213a2018-06-27 16:06:17 +0200677 if number == 0:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100678 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 +0200679 elif number > 1 and not allow_serveral:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100680 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 +0200681 if allow_serveral:
682 rows = self.cur.fetchall()
683 else:
684 rows = self.cur.fetchone()
685 return rows
686 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100687 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200688 tries -= 1
689
690 def get_uuid(self, uuid):
691 '''check in the database if this uuid is already present'''
tiernocec213a2018-06-27 16:06:17 +0200692 tries = 2
693 while tries:
tiernof97fd272016-07-11 14:32:37 +0200694 try:
695 with self.con:
696 self.cur = self.con.cursor(mdb.cursors.DictCursor)
697 self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'")
698 rows = self.cur.fetchall()
699 return self.cur.rowcount, rows
700 except (mdb.Error, AttributeError) as e:
tiernocec213a2018-06-27 16:06:17 +0200701 self._format_error(e, tries)
702 tries -= 1
tiernof97fd272016-07-11 14:32:37 +0200703
704 def get_uuid_from_name(self, table, name):
705 '''Searchs in table the name and returns the uuid
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100706 '''
tiernof97fd272016-07-11 14:32:37 +0200707 tries = 2
708 while tries:
709 try:
710 with self.con:
711 self.cur = self.con.cursor(mdb.cursors.DictCursor)
712 where_text = "name='" + name +"'"
713 self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text)
714 rows = self.cur.fetchall()
715 if self.cur.rowcount==0:
716 return 0, "Name %s not found in table %s" %(name, table)
717 elif self.cur.rowcount>1:
718 return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table)
719 return self.cur.rowcount, rows[0]["uuid"]
720 except (mdb.Error, AttributeError) as e:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100721 self._format_error(e, tries, table=table)
tiernof97fd272016-07-11 14:32:37 +0200722 tries -= 1
723