vdu scaling
[osm/RO.git] / osm_ro / db_base.py
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
78 def _convert_bandwidth(data, reverse=False, logger=None):
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:
90 _convert_bandwidth(data[k], reverse, logger)
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:
105 if logger:
106 logger.error("convert_bandwidth exception for type '%s' data '%s'", type(data["bandwidth"]), data["bandwidth"])
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:
111 _convert_bandwidth(k, reverse, logger)
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:
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
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
148 def __init__(self, host=None, user=None, passwd=None, database=None, log_name='db', log_level=None):
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)
156 if self.log_level:
157 self.logger.setLevel( getattr(logging, log_level) )
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)
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 )
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("Exception '{}' with command '{}'".format(e, cmd))
199 #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
215 else:
216 raise
217
218 def _format_error(self, e, tries=1, command=None, extra=None):
219 '''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 ):
230 raise db_base_Exception("DB Exception " + str(e), HTTP_Internal_Server_Error)
231 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
232 if tries>1:
233 self.logger.warn("DB Exception '%s'. Retry", str(e))
234 #reconnect
235 self.connect()
236 return
237 else:
238 raise db_base_Exception("Database connection timeout Try Again", HTTP_Request_Timeout)
239
240 fk=e.args[1].find("foreign key constraint fails")
241 if fk>=0:
242 if command=="update":
243 raise db_base_Exception("tenant_id '{}' not found.".format(extra), HTTP_Not_Found)
244 elif command=="delete":
245 raise db_base_Exception("Resource is not free. There are {} that prevent deleting it.".format(extra), HTTP_Conflict)
246 de = e.args[1].find("Duplicate entry")
247 fk = e.args[1].find("for key")
248 uk = e.args[1].find("Unknown column")
249 wc = e.args[1].find("in 'where clause'")
250 fl = e.args[1].find("in 'field list'")
251 #print de, fk, uk, wc,fl
252 if de>=0:
253 if fk>=0: #error 1062
254 raise db_base_Exception("Value {} already in use for {}".format(e.args[1][de+15:fk], e.args[1][fk+7:]), HTTP_Conflict)
255 if uk>=0:
256 if wc>=0:
257 raise db_base_Exception("Field {} can not be used for filtering".format(e.args[1][uk+14:wc]), HTTP_Bad_Request)
258 if fl>=0:
259 raise db_base_Exception("Field {} does not exist".format(e.args[1][uk+14:wc]), HTTP_Bad_Request)
260 raise db_base_Exception("Database internal Error {}: {}".format(e.args[0], e.args[1]), HTTP_Internal_Server_Error)
261
262 def __str2db_format(self, data):
263 '''Convert string data to database format.
264 If data is None it returns the 'Null' text,
265 otherwise it returns the text surrounded by quotes ensuring internal quotes are escaped.
266 '''
267 if data==None:
268 return 'Null'
269 elif isinstance(data[1], str):
270 return json.dumps(data)
271 else:
272 return json.dumps(str(data))
273
274 def __tuple2db_format_set(self, data):
275 """Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B),
276 and it returns the text 'A="B"', where A is a field of a table and B is the value
277 If B is None it returns the 'A=Null' text, without surrounding Null by quotes
278 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
279 and it ensures internal quotes of B are escaped.
280 B can be also a dict with special keys:
281 {"INCREMENT": NUMBER}, then it produce "A=A+NUMBER"
282 """
283 if data[1] == None:
284 return str(data[0]) + "=Null"
285 elif isinstance(data[1], str):
286 return str(data[0]) + '=' + json.dumps(data[1])
287 elif isinstance(data[1], dict):
288 if "INCREMENT" in data[1]:
289 return "{A}={A}{N:+d}".format(A=data[0], N=data[1]["INCREMENT"])
290 raise db_base_Exception("Format error for UPDATE field")
291 else:
292 return str(data[0]) + '=' + json.dumps(str(data[1]))
293
294 def __create_where(self, data, use_or=None):
295 """
296 Compose the needed text for a SQL WHERE, parameter 'data' can be a dict or a list of dict. By default lists are
297 concatenated with OR and dict with AND, unless parameter 'use_or' indicates other thing.
298 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
299 If value is None, it will produce 'key is null'
300 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
301 keys can be suffixed by >,<,<>,>=,<=,' LIKE ' so that this is used to compare key and value instead of "="
302 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
303 If a list, each item will be a dictionary that will be concatenated with OR by default
304 :param data: dict or list of dicts
305 :param use_or: Can be None (use default behaviour), True (use OR) or False (use AND)
306 :return: a string with the content to send to mysql
307 """
308 cmd = []
309 if isinstance(data, dict):
310 for k, v in data.items():
311 if k == "OR":
312 cmd.append("(" + self.__create_where(v, use_or=True) + ")")
313 continue
314 elif k == "AND":
315 cmd.append("(" + self.__create_where(v, use_or=False) + ")")
316 continue
317
318 if k.endswith(">") or k.endswith("<") or k.endswith("=") or k.endswith(" LIKE "):
319 pass
320 else:
321 k += "="
322
323 if v is None:
324 cmd.append(k.replace("=", " is").replace("<>", " is not") + " Null")
325 elif isinstance(v, (tuple, list)):
326 cmd2 = []
327 for v2 in v:
328 if v2 is None:
329 cmd2.append(k.replace("=", " is").replace("<>", " is not") + " Null")
330 else:
331 cmd2.append(k + json.dumps(str(v2)))
332 cmd.append("(" + " OR ".join(cmd2) + ")")
333 else:
334 cmd.append(k + json.dumps(str(v)))
335 elif isinstance(data, (tuple, list)):
336 if use_or is None:
337 use_or = True
338 for k in data:
339 cmd.append("(" + self.__create_where(k) + ")")
340 else:
341 raise db_base_Exception("invalid WHERE clause at '{}'".format(data))
342 if use_or:
343 return " OR ".join(cmd)
344 return " AND ".join(cmd)
345
346 def __remove_quotes(self, data):
347 '''remove single quotes ' of any string content of data dictionary'''
348 for k,v in data.items():
349 if type(v) == str:
350 if "'" in v:
351 data[k] = data[k].replace("'","_")
352
353 def _update_rows(self, table, UPDATE, WHERE, modified_time=0):
354 """ Update one or several rows of a table.
355 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
356 :param table: database table to update
357 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
358 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
359 If value is None, it will produce 'key is null'
360 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
361 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
362 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
363 If a list, each item will be a dictionary that will be concatenated with OR
364 :return: the number of updated rows, raises exception upon error
365 """
366 # gettting uuid
367 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
368 if modified_time:
369 values += ",modified_at={:f}".format(modified_time)
370 cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE)
371 self.logger.debug(cmd)
372 self.cur.execute(cmd)
373 return self.cur.rowcount
374
375 def _new_uuid(self, root_uuid=None, used_table=None, created_time=0):
376 """
377 Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created
378 :param root_uuid: master uuid of the transaction
379 :param used_table: the table this uuid is intended for
380 :param created_time: time of creation
381 :return: the created uuid
382 """
383
384 uuid = str(myUuid.uuid1())
385 # defining root_uuid if not provided
386 if root_uuid is None:
387 root_uuid = uuid
388 if created_time:
389 created_at = created_time
390 else:
391 created_at = time.time()
392 # inserting new uuid
393 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(
394 uuid, root_uuid, used_table, created_at)
395 self.logger.debug(cmd)
396 self.cur.execute(cmd)
397 return uuid
398
399 def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0, confidential_data=False):
400 ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created
401 Attribute
402 INSERT: dictionary with the key:value to insert
403 table: table where to insert
404 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
405 created_time: time to add to the created_time column
406 It checks presence of uuid and add one automatically otherwise
407 Return: uuid
408 '''
409
410 if add_uuid:
411 #create uuid if not provided
412 if 'uuid' not in INSERT:
413 uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
414 else:
415 uuid = str(INSERT['uuid'])
416 else:
417 uuid=None
418 if add_uuid:
419 #defining root_uuid if not provided
420 if root_uuid is None:
421 root_uuid = uuid
422 if created_time:
423 created_at = created_time
424 else:
425 created_at=time.time()
426 #inserting new uuid
427 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at)
428 self.logger.debug(cmd)
429 self.cur.execute(cmd)
430 #insertion
431 cmd= "INSERT INTO " + table +" SET " + \
432 ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() ))
433 if created_time:
434 cmd += ",created_at=%f" % created_time
435 if confidential_data:
436 index = cmd.find("SET")
437 subcmd = cmd[:index] + 'SET...'
438 self.logger.debug(subcmd)
439 else:
440 self.logger.debug(cmd)
441 self.cur.execute(cmd)
442 self.cur.rowcount
443 return uuid
444
445 def _get_rows(self,table,uuid):
446 cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid))
447 self.logger.debug(cmd)
448 self.cur.execute(cmd)
449 rows = self.cur.fetchall()
450 return rows
451
452 def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False):
453 ''' Add one row into a table.
454 Attribute
455 INSERT: dictionary with the key: value to insert
456 table: table where to insert
457 tenant_id: only useful for logs. If provided, logs will use this tenant_id
458 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
459 It checks presence of uuid and add one automatically otherwise
460 Return: (result, uuid) where result can be 0 if error, or 1 if ok
461 '''
462 if table in self.tables_with_created_field and created_time==0:
463 created_time=time.time()
464 tries = 2
465 while tries:
466 try:
467 with self.con:
468 self.cur = self.con.cursor()
469 return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data)
470
471 except (mdb.Error, AttributeError) as e:
472 self._format_error(e, tries)
473 tries -= 1
474
475 def update_rows(self, table, UPDATE, WHERE, modified_time=0):
476 """ Update one or several rows of a table.
477 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
478 :param table: database table to update
479 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
480 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
481 If value is None, it will produce 'key is null'
482 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
483 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
484 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
485 If a list, each item will be a dictionary that will be concatenated with OR
486 :param modified_time: Can contain the time to be set to the table row
487 :return: the number of updated rows, raises exception upon error
488 """
489 if table in self.tables_with_created_field and modified_time==0:
490 modified_time=time.time()
491 tries = 2
492 while tries:
493 try:
494 with self.con:
495 self.cur = self.con.cursor()
496 return self._update_rows(table, UPDATE, WHERE)
497
498 except (mdb.Error, AttributeError) as e:
499 self._format_error(e, tries)
500 tries -= 1
501
502 def _delete_row_by_id_internal(self, table, uuid):
503 cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid)
504 self.logger.debug(cmd)
505 self.cur.execute(cmd)
506 deleted = self.cur.rowcount
507 # delete uuid
508 self.cur = self.con.cursor()
509 cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid)
510 self.logger.debug(cmd)
511 self.cur.execute(cmd)
512 return deleted
513
514 def delete_row_by_id(self, table, uuid):
515 tries = 2
516 while tries:
517 try:
518 with self.con:
519 self.cur = self.con.cursor()
520 return self._delete_row_by_id_internal(table, uuid)
521 except (mdb.Error, AttributeError) as e:
522 self._format_error(e, tries, "delete", "dependencies")
523 tries -= 1
524
525 def delete_row(self, **sql_dict):
526 """ Deletes rows from a table.
527 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
528 :param FROM: string with table name (Mandatory)
529 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
530 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
531 If value is None, it will produce 'key is null'
532 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
533 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
534 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
535 If a list, each item will be a dictionary that will be concatenated with OR
536 :return: the number of deleted rows, raises exception upon error
537 """
538 # print sql_dict
539 cmd = "DELETE FROM " + str(sql_dict['FROM'])
540 if sql_dict.get('WHERE'):
541 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
542 if sql_dict.get('LIMIT'):
543 cmd += " LIMIT " + str(sql_dict['LIMIT'])
544 tries = 2
545 while tries:
546 try:
547 with self.con:
548 self.cur = self.con.cursor()
549 self.logger.debug(cmd)
550 self.cur.execute(cmd)
551 deleted = self.cur.rowcount
552 return deleted
553 except (mdb.Error, AttributeError) as e:
554 self._format_error(e, tries)
555 tries -= 1
556
557 def get_rows_by_id(self, table, uuid):
558 '''get row from a table based on uuid'''
559 tries = 2
560 while tries:
561 try:
562 with self.con:
563 self.cur = self.con.cursor(mdb.cursors.DictCursor)
564 cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid))
565 self.logger.debug(cmd)
566 self.cur.execute(cmd)
567 rows = self.cur.fetchall()
568 return rows
569 except (mdb.Error, AttributeError) as e:
570 self._format_error(e, tries)
571 tries -= 1
572
573 def get_rows(self, **sql_dict):
574 """ Obtain rows from a table.
575 :param SELECT: list or tuple of fields to retrieve) (by default all)
576 :param FROM: string with table name (Mandatory)
577 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
578 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
579 If value is None, it will produce 'key is null'
580 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
581 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
582 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
583 If a list, each item will be a dictionary that will be concatenated with OR
584 :param LIMIT: limit the number of obtianied entries (Optional)
585 :param ORDER_BY: list or tuple of fields to order, add ' DESC' to each item if inverse order is required
586 :return: a list with dictionaries at each row, raises exception upon error
587 """
588 # print sql_dict
589 cmd = "SELECT "
590 if 'SELECT' in sql_dict:
591 if isinstance(sql_dict['SELECT'], (tuple, list)):
592 cmd += ",".join(map(str, sql_dict['SELECT']))
593 else:
594 cmd += sql_dict['SELECT']
595 else:
596 cmd += "*"
597
598 cmd += " FROM " + str(sql_dict['FROM'])
599 if sql_dict.get('WHERE'):
600 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
601
602 if 'ORDER_BY' in sql_dict:
603 cmd += " ORDER BY "
604 if isinstance(sql_dict['ORDER_BY'], (tuple, list)):
605 cmd += ",".join(map(str, sql_dict['ORDER_BY']))
606 else:
607 cmd += str(sql_dict['ORDER_BY'])
608
609 if 'LIMIT' in sql_dict:
610 cmd += " LIMIT " + str(sql_dict['LIMIT'])
611
612 tries = 2
613 while tries:
614 try:
615 with self.con:
616 self.cur = self.con.cursor(mdb.cursors.DictCursor)
617 self.logger.debug(cmd)
618 self.cur.execute(cmd)
619 rows = self.cur.fetchall()
620 return rows
621 except (mdb.Error, AttributeError) as e:
622 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
623 self._format_error(e, tries)
624 tries -= 1
625
626 def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"):
627 ''' Obtain One row from a table based on name or uuid.
628 Attribute:
629 table: string of table name
630 uuid_name: name or uuid. If not uuid format is found, it is considered a name
631 allow_severeral: if False return ERROR if more than one row are founded
632 error_item_text: in case of error it identifies the 'item' name for a proper output text
633 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
634 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional
635 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
636 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
637 '''
638
639 if error_item_text==None:
640 error_item_text = table
641 what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name'
642 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
643 if WHERE_OR:
644 where_or = self.__create_where(WHERE_OR, use_or=True)
645 if WHERE_AND_OR == "AND":
646 cmd += " AND (" + where_or + ")"
647 else:
648 cmd += " OR " + where_or
649
650 tries = 2
651 while tries:
652 try:
653 with self.con:
654 self.cur = self.con.cursor(mdb.cursors.DictCursor)
655 self.logger.debug(cmd)
656 self.cur.execute(cmd)
657 number = self.cur.rowcount
658 if number == 0:
659 raise db_base_Exception("No {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=HTTP_Not_Found)
660 elif number > 1 and not allow_serveral:
661 raise db_base_Exception("More than one {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=HTTP_Conflict)
662 if allow_serveral:
663 rows = self.cur.fetchall()
664 else:
665 rows = self.cur.fetchone()
666 return rows
667 except (mdb.Error, AttributeError) as e:
668 self._format_error(e, tries)
669 tries -= 1
670
671 def get_uuid(self, uuid):
672 '''check in the database if this uuid is already present'''
673 tries = 2
674 while tries:
675 try:
676 with self.con:
677 self.cur = self.con.cursor(mdb.cursors.DictCursor)
678 self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'")
679 rows = self.cur.fetchall()
680 return self.cur.rowcount, rows
681 except (mdb.Error, AttributeError) as e:
682 self._format_error(e, tries)
683 tries -= 1
684
685 def get_uuid_from_name(self, table, name):
686 '''Searchs in table the name and returns the uuid
687 '''
688 tries = 2
689 while tries:
690 try:
691 with self.con:
692 self.cur = self.con.cursor(mdb.cursors.DictCursor)
693 where_text = "name='" + name +"'"
694 self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text)
695 rows = self.cur.fetchall()
696 if self.cur.rowcount==0:
697 return 0, "Name %s not found in table %s" %(name, table)
698 elif self.cur.rowcount>1:
699 return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table)
700 return self.cur.rowcount, rows[0]["uuid"]
701 except (mdb.Error, AttributeError) as e:
702 self._format_error(e, tries)
703 tries -= 1
704