| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | ## |
| 4 | # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| 5 | # 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 | ''' |
| 25 | Base class for openmano database manipulation |
| 26 | ''' |
| 27 | __author__="Alfonso Tierno" |
| 28 | __date__ ="$4-Apr-2016 10:05:01$" |
| 29 | |
| 30 | import MySQLdb as mdb |
| 31 | import uuid as myUuid |
| 32 | import utils as af |
| 33 | import json |
| 34 | #import yaml |
| 35 | import time |
| 36 | import logging |
| 37 | import datetime |
| 38 | from jsonschema import validate as js_v, exceptions as js_e |
| 39 | |
| 40 | HTTP_Bad_Request = 400 |
| 41 | HTTP_Unauthorized = 401 |
| 42 | HTTP_Not_Found = 404 |
| 43 | HTTP_Method_Not_Allowed = 405 |
| 44 | HTTP_Request_Timeout = 408 |
| 45 | HTTP_Conflict = 409 |
| 46 | HTTP_Service_Unavailable = 503 |
| 47 | HTTP_Internal_Server_Error = 500 |
| 48 | |
| 49 | def _check_valid_uuid(uuid): |
| 50 | id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"} |
| 51 | id_schema2 = {"type" : "string", "pattern": "^[a-fA-F0-9]{32}$"} |
| 52 | try: |
| 53 | js_v(uuid, id_schema) |
| 54 | return True |
| 55 | except js_e.ValidationError: |
| 56 | try: |
| 57 | js_v(uuid, id_schema2) |
| 58 | return True |
| 59 | except js_e.ValidationError: |
| 60 | return False |
| 61 | return False |
| 62 | |
| 63 | def _convert_datetime2str(var): |
| 64 | '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s' |
| 65 | It enters recursively in the dict var finding this kind of variables |
| 66 | ''' |
| 67 | if type(var) is dict: |
| 68 | for k,v in var.items(): |
| 69 | if type(v) is datetime.datetime: |
| 70 | var[k]= v.strftime('%Y-%m-%dT%H:%M:%S') |
| 71 | elif type(v) is dict or type(v) is list or type(v) is tuple: |
| 72 | _convert_datetime2str(v) |
| 73 | if len(var) == 0: return True |
| 74 | elif type(var) is list or type(var) is tuple: |
| 75 | for v in var: |
| 76 | _convert_datetime2str(v) |
| 77 | |
| tierno | 44528e4 | 2016-10-11 12:06:25 +0000 | [diff] [blame] | 78 | def _convert_bandwidth(data, reverse=False, logger=None): |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 79 | '''Check the field bandwidth recursivelly and when found, it removes units and convert to number |
| 80 | It assumes that bandwidth is well formed |
| 81 | Attributes: |
| 82 | 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid |
| 83 | 'reverse': by default convert form str to int (Mbps), if True it convert from number to units |
| 84 | Return: |
| 85 | None |
| 86 | ''' |
| 87 | if type(data) is dict: |
| 88 | for k in data.keys(): |
| 89 | if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list: |
| tierno | 44528e4 | 2016-10-11 12:06:25 +0000 | [diff] [blame] | 90 | _convert_bandwidth(data[k], reverse, logger) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 91 | if "bandwidth" in data: |
| 92 | try: |
| 93 | value=str(data["bandwidth"]) |
| 94 | if not reverse: |
| 95 | pos = value.find("bps") |
| 96 | if pos>0: |
| 97 | if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000 |
| 98 | elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000 |
| 99 | else: data["bandwidth"]= int(data["bandwidth"][:pos-1]) |
| 100 | else: |
| 101 | value = int(data["bandwidth"]) |
| 102 | if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps" |
| 103 | else: data["bandwidth"]=str(value) + " Mbps" |
| 104 | except: |
| tierno | 44528e4 | 2016-10-11 12:06:25 +0000 | [diff] [blame] | 105 | if logger: |
| 106 | logger.error("convert_bandwidth exception for type '%s' data '%s'", type(data["bandwidth"]), data["bandwidth"]) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 107 | return |
| 108 | if type(data) is tuple or type(data) is list: |
| 109 | for k in data: |
| 110 | if type(k) is dict or type(k) is tuple or type(k) is list: |
| tierno | 44528e4 | 2016-10-11 12:06:25 +0000 | [diff] [blame] | 111 | _convert_bandwidth(k, reverse, logger) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 112 | |
| 113 | def _convert_str2boolean(data, items): |
| 114 | '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean |
| 115 | Done recursively |
| 116 | Attributes: |
| 117 | 'data': dictionary variable to be checked. None or empty is considered valid |
| 118 | 'items': tuple of keys to convert |
| 119 | Return: |
| 120 | None |
| 121 | ''' |
| 122 | if type(data) is dict: |
| 123 | for k in data.keys(): |
| 124 | if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list: |
| 125 | _convert_str2boolean(data[k], items) |
| 126 | if k in items: |
| 127 | if type(data[k]) is str: |
| montesmoreno | 2a1fc4e | 2017-01-09 16:46:04 +0000 | [diff] [blame] | 128 | if data[k]=="false" or data[k]=="False" or data[k]=="0": data[k]=False |
| 129 | elif data[k]=="true" or data[k]=="True" or data[k]=="1": data[k]=True |
| 130 | elif type(data[k]) is int: |
| 131 | if data[k]==0: data[k]=False |
| 132 | elif data[k]==1: data[k]=True |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 133 | if type(data) is tuple or type(data) is list: |
| 134 | for k in data: |
| 135 | if type(k) is dict or type(k) is tuple or type(k) is list: |
| 136 | _convert_str2boolean(k, items) |
| 137 | |
| 138 | class db_base_Exception(Exception): |
| 139 | '''Common Exception for all database exceptions''' |
| 140 | |
| 141 | def __init__(self, message, http_code=HTTP_Bad_Request): |
| 142 | Exception.__init__(self, message) |
| 143 | self.http_code = http_code |
| 144 | |
| 145 | class db_base(): |
| 146 | tables_with_created_field=() |
| 147 | |
| tierno | b13f3cc | 2016-09-26 10:14:44 +0200 | [diff] [blame] | 148 | def __init__(self, host=None, user=None, passwd=None, database=None, log_name='db', log_level=None): |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 149 | self.host = host |
| 150 | self.user = user |
| 151 | self.passwd = passwd |
| 152 | self.database = database |
| 153 | self.con = None |
| 154 | self.log_level=log_level |
| 155 | self.logger = logging.getLogger(log_name) |
| tierno | b13f3cc | 2016-09-26 10:14:44 +0200 | [diff] [blame] | 156 | if self.log_level: |
| 157 | self.logger.setLevel( getattr(logging, log_level) ) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 158 | |
| 159 | def connect(self, host=None, user=None, passwd=None, database=None): |
| 160 | '''Connect to specific data base. |
| 161 | The first time a valid host, user, passwd and database must be provided, |
| 162 | Following calls can skip this parameters |
| 163 | ''' |
| 164 | try: |
| 165 | if host: self.host = host |
| 166 | if user: self.user = user |
| 167 | if passwd: self.passwd = passwd |
| 168 | if database: self.database = database |
| 169 | |
| 170 | self.con = mdb.connect(self.host, self.user, self.passwd, self.database) |
| tierno | 44528e4 | 2016-10-11 12:06:25 +0000 | [diff] [blame] | 171 | self.logger.debug("DB: connected to '%s' at '%s@%s'", self.database, self.user, self.host) |
| 172 | except mdb.Error as e: |
| 173 | raise db_base_Exception("Cannot connect to DataBase '{}' at '{}@{}' Error {}: {}".format( |
| 174 | self.database, self.user, self.host, e.args[0], e.args[1]), |
| 175 | http_code = HTTP_Unauthorized ) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 176 | |
| 177 | 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: |
| 198 | #self.logger.error("get_db_version DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd) |
| 199 | self._format_error(e, tries) |
| 200 | tries -= 1 |
| 201 | |
| 202 | def disconnect(self): |
| 203 | '''disconnect from specific data base''' |
| 204 | try: |
| 205 | self.con.close() |
| 206 | self.con = None |
| 207 | except mdb.Error as e: |
| 208 | self.logger.error("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1]) |
| 209 | return |
| 210 | except AttributeError as e: #self.con not defined |
| 211 | if e[0][-5:] == "'con'": |
| 212 | self.logger.warn("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1]) |
| 213 | return |
| 214 | else: |
| 215 | raise |
| 216 | |
| 217 | def _format_error(self, e, tries=1, command=None, extra=None): |
| 218 | '''Creates a text error base on the produced exception |
| 219 | Params: |
| 220 | e: mdb exception |
| 221 | retry: in case of timeout, if reconnecting to database and retry, or raise and exception |
| 222 | cmd: database command that produce the exception |
| 223 | command: if the intention is update or delete |
| 224 | extra: extra information to add to some commands |
| 225 | Return |
| 226 | HTTP error in negative, formatted error text |
| 227 | ''' |
| 228 | if isinstance(e,AttributeError ): |
| 229 | raise db_base_Exception("DB Exception " + str(e), HTTP_Internal_Server_Error) |
| 230 | 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 |
| 231 | if tries>1: |
| 232 | self.logger.warn("DB Exception '%s'. Retry", str(e)) |
| 233 | #reconnect |
| 234 | self.connect() |
| 235 | return |
| 236 | else: |
| 237 | raise db_base_Exception("Database connection timeout Try Again", HTTP_Request_Timeout) |
| 238 | |
| 239 | fk=e.args[1].find("foreign key constraint fails") |
| 240 | if fk>=0: |
| 241 | if command=="update": |
| 242 | raise db_base_Exception("tenant_id '{}' not found.".format(extra), HTTP_Not_Found) |
| 243 | elif command=="delete": |
| 244 | raise db_base_Exception("Resource is not free. There are {} that prevent deleting it.".format(extra), HTTP_Conflict) |
| 245 | de = e.args[1].find("Duplicate entry") |
| 246 | fk = e.args[1].find("for key") |
| 247 | uk = e.args[1].find("Unknown column") |
| 248 | wc = e.args[1].find("in 'where clause'") |
| 249 | fl = e.args[1].find("in 'field list'") |
| 250 | #print de, fk, uk, wc,fl |
| 251 | if de>=0: |
| 252 | if fk>=0: #error 1062 |
| 253 | raise db_base_Exception("Value {} already in use for {}".format(e.args[1][de+15:fk], e.args[1][fk+7:]), HTTP_Conflict) |
| 254 | if uk>=0: |
| 255 | if wc>=0: |
| 256 | raise db_base_Exception("Field {} can not be used for filtering".format(e.args[1][uk+14:wc]), HTTP_Bad_Request) |
| 257 | if fl>=0: |
| 258 | raise db_base_Exception("Field {} does not exist".format(e.args[1][uk+14:wc]), HTTP_Bad_Request) |
| 259 | raise db_base_Exception("Database internal Error {}: {}".format(e.args[0], e.args[1]), HTTP_Internal_Server_Error) |
| 260 | |
| 261 | def __str2db_format(self, data): |
| 262 | '''Convert string data to database format. |
| 263 | If data is None it returns the 'Null' text, |
| 264 | otherwise it returns the text surrounded by quotes ensuring internal quotes are escaped. |
| 265 | ''' |
| 266 | if data==None: |
| 267 | return 'Null' |
| tierno | 86fad56 | 2017-04-05 19:53:54 +0200 | [diff] [blame] | 268 | elif isinstance(data[1], str): |
| 269 | return json.dumps(data) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 270 | else: |
| 271 | return json.dumps(str(data)) |
| 272 | |
| 273 | def __tuple2db_format_set(self, data): |
| tierno | 868220c | 2017-09-26 00:11:05 +0200 | [diff] [blame] | 274 | """Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B), |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 275 | and it returns the text 'A="B"', where A is a field of a table and B is the value |
| 276 | If B is None it returns the 'A=Null' text, without surrounding Null by quotes |
| 277 | If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes, |
| 278 | and it ensures internal quotes of B are escaped. |
| tierno | 868220c | 2017-09-26 00:11:05 +0200 | [diff] [blame] | 279 | B can be also a dict with special keys: |
| 280 | {"INCREMENT": NUMBER}, then it produce "A=A+NUMBER" |
| 281 | """ |
| 282 | if data[1] == None: |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 283 | return str(data[0]) + "=Null" |
| tierno | 86fad56 | 2017-04-05 19:53:54 +0200 | [diff] [blame] | 284 | elif isinstance(data[1], str): |
| 285 | return str(data[0]) + '=' + json.dumps(data[1]) |
| tierno | 868220c | 2017-09-26 00:11:05 +0200 | [diff] [blame] | 286 | elif isinstance(data[1], dict): |
| 287 | if "INCREMENT" in data[1]: |
| 288 | return "{A}={A}{N:+d}".format(A=data[0], N=data[1]["INCREMENT"]) |
| 289 | raise db_base_Exception("Format error for UPDATE field") |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 290 | else: |
| 291 | return str(data[0]) + '=' + json.dumps(str(data[1])) |
| 292 | |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 293 | def __create_where(self, data, use_or=None): |
| 294 | """ |
| 295 | Compose the needed text for a SQL WHERE, parameter 'data' can be a dict or a list of dict. By default lists are |
| 296 | concatenated with OR and dict with AND, unless parameter 'use_or' indicates other thing. |
| 297 | If a dict it will generate 'key1="value1" AND key2="value2" AND ...'. |
| 298 | If value is None, it will produce 'key is null' |
| 299 | If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...' |
| 300 | keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "=" |
| 301 | The special keys "OR", "AND" with a dict value is used to create a nested WHERE |
| 302 | If a list, each item will be a dictionary that will be concatenated with OR by default |
| 303 | :param data: dict or list of dicts |
| 304 | :param use_or: Can be None (use default behaviour), True (use OR) or False (use AND) |
| 305 | :return: a string with the content to send to mysql |
| 306 | """ |
| 307 | cmd = [] |
| 308 | if isinstance(data, dict): |
| 309 | for k, v in data.items(): |
| 310 | if k == "OR": |
| 311 | cmd.append("(" + self.__create_where(v, use_or=True) + ")") |
| 312 | continue |
| 313 | elif k == "AND": |
| 314 | cmd.append("(" + self.__create_where(v, use_or=False) + ")") |
| 315 | continue |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 316 | |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 317 | if k.endswith(">") or k.endswith("<") or k.endswith("="): |
| 318 | pass |
| 319 | else: |
| 320 | k += "=" |
| 321 | |
| 322 | if v is None: |
| 323 | cmd.append(k.replace("=", " is").replace("<>", " is not") + " Null") |
| 324 | elif isinstance(v, (tuple, list)): |
| 325 | cmd2 = [] |
| 326 | for v2 in v: |
| 327 | if v2 is None: |
| 328 | cmd2.append(k.replace("=", " is").replace("<>", " is not") + " Null") |
| 329 | else: |
| 330 | cmd2.append(k + json.dumps(str(v2))) |
| 331 | cmd.append("(" + " OR ".join(cmd2) + ")") |
| 332 | else: |
| 333 | cmd.append(k + json.dumps(str(v))) |
| 334 | elif isinstance(data, (tuple, list)): |
| 335 | if use_or is None: |
| 336 | use_or = True |
| 337 | for k in data: |
| 338 | cmd.append("(" + self.__create_where(k) + ")") |
| tierno | 86fad56 | 2017-04-05 19:53:54 +0200 | [diff] [blame] | 339 | else: |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 340 | raise db_base_Exception("invalid WHERE clause at '{}'".format(data)) |
| 341 | if use_or: |
| 342 | return " OR ".join(cmd) |
| 343 | return " AND ".join(cmd) |
| 344 | |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 345 | def __remove_quotes(self, data): |
| 346 | '''remove single quotes ' of any string content of data dictionary''' |
| 347 | for k,v in data.items(): |
| 348 | if type(v) == str: |
| 349 | if "'" in v: |
| 350 | data[k] = data[k].replace("'","_") |
| 351 | |
| 352 | def _update_rows(self, table, UPDATE, WHERE, modified_time=0): |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 353 | """ Update one or several rows of a table. |
| 354 | :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values |
| 355 | :param table: database table to update |
| 356 | :param WHERE: dict or list of dicts to compose the SQL WHERE clause. |
| 357 | If a dict it will generate 'key1="value1" AND key2="value2" AND ...'. |
| 358 | If value is None, it will produce 'key is null' |
| 359 | If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...' |
| 360 | keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "=" |
| 361 | The special keys "OR", "AND" with a dict value is used to create a nested WHERE |
| 362 | If a list, each item will be a dictionary that will be concatenated with OR |
| 363 | :return: the number of updated rows, raises exception upon error |
| 364 | """ |
| 365 | # gettting uuid |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 366 | values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() )) |
| 367 | if modified_time: |
| 368 | values += ",modified_at={:f}".format(modified_time) |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 369 | cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 370 | self.logger.debug(cmd) |
| 371 | self.cur.execute(cmd) |
| 372 | return self.cur.rowcount |
| tierno | 8e69032 | 2017-08-10 15:58:50 +0200 | [diff] [blame] | 373 | |
| 374 | def _new_uuid(self, root_uuid=None, used_table=None, created_time=0): |
| 375 | """ |
| 376 | Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created |
| 377 | :param root_uuid: master uuid of the transaction |
| 378 | :param used_table: the table this uuid is intended for |
| 379 | :param created_time: time of creation |
| 380 | :return: the created uuid |
| 381 | """ |
| 382 | |
| 383 | uuid = str(myUuid.uuid1()) |
| 384 | # defining root_uuid if not provided |
| 385 | if root_uuid is None: |
| 386 | root_uuid = uuid |
| 387 | if created_time: |
| 388 | created_at = created_time |
| 389 | else: |
| 390 | created_at = time.time() |
| 391 | # inserting new uuid |
| 392 | cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format( |
| 393 | uuid, root_uuid, used_table, created_at) |
| 394 | self.logger.debug(cmd) |
| 395 | self.cur.execute(cmd) |
| 396 | return uuid |
| 397 | |
| gcalvino | c62cfa5 | 2017-10-05 18:21:25 +0200 | [diff] [blame] | 398 | def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0, confidential_data=False): |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 399 | ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created |
| 400 | Attribute |
| 401 | INSERT: dictionary with the key:value to insert |
| 402 | table: table where to insert |
| 403 | add_uuid: if True, it will create an uuid key entry at INSERT if not provided |
| 404 | created_time: time to add to the created_time column |
| 405 | It checks presence of uuid and add one automatically otherwise |
| 406 | Return: uuid |
| 407 | ''' |
| 408 | |
| 409 | if add_uuid: |
| 410 | #create uuid if not provided |
| 411 | if 'uuid' not in INSERT: |
| 412 | uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid |
| 413 | else: |
| 414 | uuid = str(INSERT['uuid']) |
| 415 | else: |
| 416 | uuid=None |
| 417 | if add_uuid: |
| 418 | #defining root_uuid if not provided |
| 419 | if root_uuid is None: |
| 420 | root_uuid = uuid |
| 421 | if created_time: |
| 422 | created_at = created_time |
| 423 | else: |
| 424 | created_at=time.time() |
| 425 | #inserting new uuid |
| 426 | cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at) |
| 427 | self.logger.debug(cmd) |
| 428 | self.cur.execute(cmd) |
| 429 | #insertion |
| 430 | cmd= "INSERT INTO " + table +" SET " + \ |
| 431 | ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() )) |
| 432 | if created_time: |
| 433 | cmd += ",created_at=%f" % created_time |
| gcalvino | c62cfa5 | 2017-10-05 18:21:25 +0200 | [diff] [blame] | 434 | if confidential_data: |
| 435 | index = cmd.find("SET") |
| 436 | subcmd = cmd[:index] + 'SET...' |
| 437 | self.logger.debug(subcmd) |
| 438 | else: |
| 439 | self.logger.debug(cmd) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 440 | self.cur.execute(cmd) |
| 441 | self.cur.rowcount |
| 442 | return uuid |
| 443 | |
| 444 | def _get_rows(self,table,uuid): |
| 445 | cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid)) |
| 446 | self.logger.debug(cmd) |
| 447 | self.cur.execute(cmd) |
| 448 | rows = self.cur.fetchall() |
| 449 | return rows |
| 450 | |
| gcalvino | c62cfa5 | 2017-10-05 18:21:25 +0200 | [diff] [blame] | 451 | def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False): |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 452 | ''' Add one row into a table. |
| 453 | Attribute |
| 454 | INSERT: dictionary with the key: value to insert |
| 455 | table: table where to insert |
| 456 | tenant_id: only useful for logs. If provided, logs will use this tenant_id |
| 457 | add_uuid: if True, it will create an uuid key entry at INSERT if not provided |
| 458 | It checks presence of uuid and add one automatically otherwise |
| 459 | Return: (result, uuid) where result can be 0 if error, or 1 if ok |
| 460 | ''' |
| 461 | if table in self.tables_with_created_field and created_time==0: |
| 462 | created_time=time.time() |
| 463 | tries = 2 |
| 464 | while tries: |
| 465 | try: |
| 466 | with self.con: |
| 467 | self.cur = self.con.cursor() |
| gcalvino | c62cfa5 | 2017-10-05 18:21:25 +0200 | [diff] [blame] | 468 | return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 469 | |
| 470 | except (mdb.Error, AttributeError) as e: |
| 471 | self._format_error(e, tries) |
| 472 | tries -= 1 |
| 473 | |
| 474 | def update_rows(self, table, UPDATE, WHERE, modified_time=0): |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 475 | """ Update one or several rows of a table. |
| 476 | :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values |
| 477 | :param table: database table to update |
| 478 | :param WHERE: dict or list of dicts to compose the SQL WHERE clause. |
| 479 | If a dict it will generate 'key1="value1" AND key2="value2" AND ...'. |
| 480 | If value is None, it will produce 'key is null' |
| 481 | If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...' |
| 482 | keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "=" |
| 483 | The special keys "OR", "AND" with a dict value is used to create a nested WHERE |
| 484 | If a list, each item will be a dictionary that will be concatenated with OR |
| 485 | :param modified_time: Can contain the time to be set to the table row |
| 486 | :return: the number of updated rows, raises exception upon error |
| 487 | """ |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 488 | if table in self.tables_with_created_field and modified_time==0: |
| 489 | modified_time=time.time() |
| 490 | tries = 2 |
| 491 | while tries: |
| 492 | try: |
| 493 | with self.con: |
| 494 | self.cur = self.con.cursor() |
| 495 | return self._update_rows(table, UPDATE, WHERE) |
| 496 | |
| 497 | except (mdb.Error, AttributeError) as e: |
| 498 | self._format_error(e, tries) |
| 499 | tries -= 1 |
| 500 | |
| 501 | def delete_row_by_id(self, table, uuid): |
| 502 | tries = 2 |
| 503 | while tries: |
| 504 | try: |
| 505 | with self.con: |
| 506 | #delete host |
| 507 | self.cur = self.con.cursor() |
| 508 | cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid) |
| 509 | self.logger.debug(cmd) |
| 510 | self.cur.execute(cmd) |
| 511 | deleted = self.cur.rowcount |
| 512 | if deleted: |
| 513 | #delete uuid |
| 514 | self.cur = self.con.cursor() |
| 515 | cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid) |
| 516 | self.logger.debug(cmd) |
| 517 | self.cur.execute(cmd) |
| 518 | return deleted |
| 519 | except (mdb.Error, AttributeError) as e: |
| 520 | self._format_error(e, tries, "delete", "dependencies") |
| 521 | tries -= 1 |
| 522 | |
| 523 | def delete_row(self, **sql_dict): |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 524 | """ Deletes rows from a table. |
| 525 | :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values |
| 526 | :param FROM: string with table name (Mandatory) |
| 527 | :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional) |
| 528 | If a dict it will generate 'key1="value1" AND key2="value2" AND ...'. |
| 529 | If value is None, it will produce 'key is null' |
| 530 | If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...' |
| 531 | keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "=" |
| 532 | The special keys "OR", "AND" with a dict value is used to create a nested WHERE |
| 533 | If a list, each item will be a dictionary that will be concatenated with OR |
| 534 | :return: the number of deleted rows, raises exception upon error |
| 535 | """ |
| 536 | # print sql_dict |
| 537 | cmd = "DELETE FROM " + str(sql_dict['FROM']) |
| 538 | if sql_dict.get('WHERE'): |
| 539 | cmd += " WHERE " + self.__create_where(sql_dict['WHERE']) |
| 540 | if sql_dict.get('LIMIT'): |
| 541 | cmd += " LIMIT " + str(sql_dict['LIMIT']) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 542 | tries = 2 |
| 543 | while tries: |
| 544 | try: |
| 545 | with self.con: |
| 546 | self.cur = self.con.cursor() |
| 547 | self.logger.debug(cmd) |
| 548 | self.cur.execute(cmd) |
| 549 | deleted = self.cur.rowcount |
| 550 | return deleted |
| 551 | except (mdb.Error, AttributeError) as e: |
| 552 | self._format_error(e, tries) |
| 553 | tries -= 1 |
| 554 | |
| 555 | def get_rows_by_id(self, table, uuid): |
| 556 | '''get row from a table based on uuid''' |
| 557 | tries = 2 |
| 558 | while tries: |
| 559 | try: |
| 560 | with self.con: |
| 561 | self.cur = self.con.cursor(mdb.cursors.DictCursor) |
| 562 | cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid)) |
| 563 | self.logger.debug(cmd) |
| 564 | self.cur.execute(cmd) |
| 565 | rows = self.cur.fetchall() |
| 566 | return rows |
| 567 | except (mdb.Error, AttributeError) as e: |
| 568 | self._format_error(e, tries) |
| 569 | tries -= 1 |
| 570 | |
| 571 | def get_rows(self, **sql_dict): |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 572 | """ Obtain rows from a table. |
| 573 | :param SELECT: list or tuple of fields to retrieve) (by default all) |
| 574 | :param FROM: string with table name (Mandatory) |
| 575 | :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional) |
| 576 | If a dict it will generate 'key1="value1" AND key2="value2" AND ...'. |
| 577 | If value is None, it will produce 'key is null' |
| 578 | If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...' |
| 579 | keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "=" |
| 580 | The special keys "OR", "AND" with a dict value is used to create a nested WHERE |
| 581 | If a list, each item will be a dictionary that will be concatenated with OR |
| 582 | :param LIMIT: limit the number of obtianied entries (Optional) |
| 583 | :param ORDER_BY: list or tuple of fields to order, add ' DESC' to each item if inverse order is required |
| 584 | :return: a list with dictionaries at each row, raises exception upon error |
| 585 | """ |
| 586 | # print sql_dict |
| 587 | cmd = "SELECT " |
| 588 | if 'SELECT' in sql_dict: |
| 589 | if isinstance(sql_dict['SELECT'], (tuple, list)): |
| 590 | cmd += ",".join(map(str, sql_dict['SELECT'])) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 591 | else: |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 592 | cmd += sql_dict['SELECT'] |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 593 | else: |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 594 | cmd += "*" |
| 595 | |
| 596 | cmd += " FROM " + str(sql_dict['FROM']) |
| 597 | if sql_dict.get('WHERE'): |
| 598 | cmd += " WHERE " + self.__create_where(sql_dict['WHERE']) |
| 599 | |
| 600 | if 'ORDER_BY' in sql_dict: |
| 601 | cmd += " ORDER BY " |
| 602 | if isinstance(sql_dict['ORDER_BY'], (tuple, list)): |
| 603 | cmd += ",".join(map(str, sql_dict['ORDER_BY'])) |
| 604 | else: |
| 605 | cmd += str(sql_dict['ORDER_BY']) |
| 606 | |
| 607 | if 'LIMIT' in sql_dict: |
| 608 | cmd += " LIMIT " + str(sql_dict['LIMIT']) |
| 609 | |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 610 | tries = 2 |
| 611 | while tries: |
| 612 | try: |
| 613 | with self.con: |
| 614 | self.cur = self.con.cursor(mdb.cursors.DictCursor) |
| 615 | self.logger.debug(cmd) |
| 616 | self.cur.execute(cmd) |
| 617 | rows = self.cur.fetchall() |
| 618 | return rows |
| 619 | except (mdb.Error, AttributeError) as e: |
| 620 | self._format_error(e, tries) |
| 621 | tries -= 1 |
| 622 | |
| 623 | def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"): |
| 624 | ''' Obtain One row from a table based on name or uuid. |
| 625 | Attribute: |
| 626 | table: string of table name |
| 627 | uuid_name: name or uuid. If not uuid format is found, it is considered a name |
| 628 | allow_severeral: if False return ERROR if more than one row are founded |
| 629 | error_item_text: in case of error it identifies the 'item' name for a proper output text |
| 630 | 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional) |
| 631 | 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional |
| 632 | Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found |
| 633 | if allow_several==True, a list of dictionaries with the row or rows, error if no item is found |
| 634 | ''' |
| 635 | |
| 636 | if error_item_text==None: |
| 637 | error_item_text = table |
| 638 | what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name' |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 639 | cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 640 | if WHERE_OR: |
| tierno | 3fcfdb7 | 2017-10-24 07:48:24 +0200 | [diff] [blame] | 641 | where_or = self.__create_where(WHERE_OR, use_or=True) |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 642 | if WHERE_AND_OR == "AND": |
| 643 | cmd += " AND (" + where_or + ")" |
| 644 | else: |
| 645 | cmd += " OR " + where_or |
| 646 | |
| tierno | f97fd27 | 2016-07-11 14:32:37 +0200 | [diff] [blame] | 647 | tries = 2 |
| 648 | while tries: |
| 649 | try: |
| 650 | with self.con: |
| 651 | self.cur = self.con.cursor(mdb.cursors.DictCursor) |
| 652 | self.logger.debug(cmd) |
| 653 | self.cur.execute(cmd) |
| 654 | number = self.cur.rowcount |
| 655 | if number==0: |
| 656 | return -HTTP_Not_Found, "No %s found with %s '%s'" %(error_item_text, what, uuid_name) |
| 657 | elif number>1 and not allow_serveral: |
| 658 | return -HTTP_Bad_Request, "More than one %s found with %s '%s'" %(error_item_text, what, uuid_name) |
| 659 | if allow_serveral: |
| 660 | rows = self.cur.fetchall() |
| 661 | else: |
| 662 | rows = self.cur.fetchone() |
| 663 | return rows |
| 664 | except (mdb.Error, AttributeError) as e: |
| 665 | self._format_error(e, tries) |
| 666 | tries -= 1 |
| 667 | |
| 668 | def get_uuid(self, uuid): |
| 669 | '''check in the database if this uuid is already present''' |
| 670 | for retry_ in range(0,2): |
| 671 | try: |
| 672 | with self.con: |
| 673 | self.cur = self.con.cursor(mdb.cursors.DictCursor) |
| 674 | self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'") |
| 675 | rows = self.cur.fetchall() |
| 676 | return self.cur.rowcount, rows |
| 677 | except (mdb.Error, AttributeError) as e: |
| 678 | print "nfvo_db.get_uuid DB Exception %d: %s" % (e.args[0], e.args[1]) |
| 679 | r,c = self._format_error(e) |
| 680 | if r!=-HTTP_Request_Timeout or retry_==1: return r,c |
| 681 | |
| 682 | def get_uuid_from_name(self, table, name): |
| 683 | '''Searchs in table the name and returns the uuid |
| 684 | ''' |
| 685 | tries = 2 |
| 686 | while tries: |
| 687 | try: |
| 688 | with self.con: |
| 689 | self.cur = self.con.cursor(mdb.cursors.DictCursor) |
| 690 | where_text = "name='" + name +"'" |
| 691 | self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text) |
| 692 | rows = self.cur.fetchall() |
| 693 | if self.cur.rowcount==0: |
| 694 | return 0, "Name %s not found in table %s" %(name, table) |
| 695 | elif self.cur.rowcount>1: |
| 696 | return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table) |
| 697 | return self.cur.rowcount, rows[0]["uuid"] |
| 698 | except (mdb.Error, AttributeError) as e: |
| 699 | self._format_error(e, tries) |
| 700 | tries -= 1 |
| 701 | |