Feature 5649 Alternative images for VIM specific
[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("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'
268 elif isinstance(data[1], str):
269 return json.dumps(data)
270 else:
271 return json.dumps(str(data))
272
273 def __tuple2db_format_set(self, data):
274 """Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B),
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.
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:
283 return str(data[0]) + "=Null"
284 elif isinstance(data[1], str):
285 return str(data[0]) + '=' + json.dumps(data[1])
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")
290 else:
291 return str(data[0]) + '=' + json.dumps(str(data[1]))
292
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 >,<,<>,>=,<=,' LIKE ' 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
316
317 if k.endswith(">") or k.endswith("<") or k.endswith("=") or k.endswith(" LIKE "):
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) + ")")
339 else:
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
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):
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
366 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
367 if modified_time:
368 values += ",modified_at={:f}".format(modified_time)
369 cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE)
370 self.logger.debug(cmd)
371 self.cur.execute(cmd)
372 return self.cur.rowcount
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
398 def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0, confidential_data=False):
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
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)
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
451 def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False):
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()
468 return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data)
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):
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 """
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):
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'])
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):
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']))
591 else:
592 cmd += sql_dict['SELECT']
593 else:
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
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'
639 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
640 if WHERE_OR:
641 where_or = self.__create_where(WHERE_OR, use_or=True)
642 if WHERE_AND_OR == "AND":
643 cmd += " AND (" + where_or + ")"
644 else:
645 cmd += " OR " + where_or
646
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