Feature 1413 resiliency to single component failure
[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 __tuple2db_format_where(self, data):
294 '''Compose the needed text for a SQL WHERE, parameter 'data' is a pair tuple (A,B),
295 and it returns the text 'A="B"', where A is a field of a table and B is the value
296 If B is None it returns the 'A is Null' text, without surrounding Null by quotes
297 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
298 and it ensures internal quotes of B are escaped.
299 '''
300 if data[1]==None:
301 return str(data[0]) + " is Null"
302 elif isinstance(data[1], str):
303 return str(data[0]) + '=' + json.dumps(data[1])
304 else:
305 return str(data[0]) + '=' + json.dumps(str(data[1]))
306
307 def __tuple2db_format_where_not(self, data):
308 '''Compose the needed text for a SQL WHERE(not). parameter 'data' is a pair tuple (A,B),
309 and it returns the text 'A<>"B"', where A is a field of a table and B is the value
310 If B is None it returns the 'A is not Null' text, without surrounding Null by quotes
311 If B is not None it returns the text "A<>'B'" or 'A<>"B"' where B is surrounded by quotes,
312 and it ensures internal quotes of B are escaped.
313 '''
314 if data[1]==None:
315 return str(data[0]) + " is not Null"
316 elif isinstance(data[1], str):
317 return str(data[0]) + '<>' + json.dumps(data[1])
318 else:
319 return str(data[0]) + '<>' + json.dumps(str(data[1]))
320
321 def __remove_quotes(self, data):
322 '''remove single quotes ' of any string content of data dictionary'''
323 for k,v in data.items():
324 if type(v) == str:
325 if "'" in v:
326 data[k] = data[k].replace("'","_")
327
328 def _update_rows(self, table, UPDATE, WHERE, modified_time=0):
329 ''' Update one or several rows into a table.
330 Atributes
331 UPDATE: dictionary with the key: value to change
332 table: table where to update
333 WHERE: dictionary of elements to update
334 Return: the number of updated rows, exception if error
335 '''
336 #gettting uuid
337 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
338 if modified_time:
339 values += ",modified_at={:f}".format(modified_time)
340 cmd= "UPDATE " + table +" SET " + values +\
341 " WHERE " + " and ".join(map(self.__tuple2db_format_where, WHERE.iteritems() ))
342 self.logger.debug(cmd)
343 self.cur.execute(cmd)
344 return self.cur.rowcount
345
346 def _new_uuid(self, root_uuid=None, used_table=None, created_time=0):
347 """
348 Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created
349 :param root_uuid: master uuid of the transaction
350 :param used_table: the table this uuid is intended for
351 :param created_time: time of creation
352 :return: the created uuid
353 """
354
355 uuid = str(myUuid.uuid1())
356 # defining root_uuid if not provided
357 if root_uuid is None:
358 root_uuid = uuid
359 if created_time:
360 created_at = created_time
361 else:
362 created_at = time.time()
363 # inserting new uuid
364 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(
365 uuid, root_uuid, used_table, created_at)
366 self.logger.debug(cmd)
367 self.cur.execute(cmd)
368 return uuid
369
370 def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0):
371 ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created
372 Attribute
373 INSERT: dictionary with the key:value to insert
374 table: table where to insert
375 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
376 created_time: time to add to the created_time column
377 It checks presence of uuid and add one automatically otherwise
378 Return: uuid
379 '''
380
381 if add_uuid:
382 #create uuid if not provided
383 if 'uuid' not in INSERT:
384 uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
385 else:
386 uuid = str(INSERT['uuid'])
387 else:
388 uuid=None
389 if add_uuid:
390 #defining root_uuid if not provided
391 if root_uuid is None:
392 root_uuid = uuid
393 if created_time:
394 created_at = created_time
395 else:
396 created_at=time.time()
397 #inserting new uuid
398 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at)
399 self.logger.debug(cmd)
400 self.cur.execute(cmd)
401 #insertion
402 cmd= "INSERT INTO " + table +" SET " + \
403 ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() ))
404 if created_time:
405 cmd += ",created_at=%f" % created_time
406 self.logger.debug(cmd)
407 self.cur.execute(cmd)
408 self.cur.rowcount
409 return uuid
410
411 def _get_rows(self,table,uuid):
412 cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid))
413 self.logger.debug(cmd)
414 self.cur.execute(cmd)
415 rows = self.cur.fetchall()
416 return rows
417
418 def new_row(self, table, INSERT, add_uuid=False, created_time=0):
419 ''' Add one row into a table.
420 Attribute
421 INSERT: dictionary with the key: value to insert
422 table: table where to insert
423 tenant_id: only useful for logs. If provided, logs will use this tenant_id
424 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
425 It checks presence of uuid and add one automatically otherwise
426 Return: (result, uuid) where result can be 0 if error, or 1 if ok
427 '''
428 if table in self.tables_with_created_field and created_time==0:
429 created_time=time.time()
430 tries = 2
431 while tries:
432 try:
433 with self.con:
434 self.cur = self.con.cursor()
435 return self._new_row_internal(table, INSERT, add_uuid, None, created_time)
436
437 except (mdb.Error, AttributeError) as e:
438 self._format_error(e, tries)
439 tries -= 1
440
441 def update_rows(self, table, UPDATE, WHERE, modified_time=0):
442 ''' Update one or several rows into a table.
443 Atributes
444 UPDATE: dictionary with the key: value to change
445 table: table where to update
446 WHERE: dictionary of elements to update
447 Return: (result, descriptive text) where result indicates the number of updated files
448 '''
449 if table in self.tables_with_created_field and modified_time==0:
450 modified_time=time.time()
451 tries = 2
452 while tries:
453 try:
454 with self.con:
455 self.cur = self.con.cursor()
456 return self._update_rows(table, UPDATE, WHERE)
457
458 except (mdb.Error, AttributeError) as e:
459 self._format_error(e, tries)
460 tries -= 1
461
462 def delete_row_by_id(self, table, uuid):
463 tries = 2
464 while tries:
465 try:
466 with self.con:
467 #delete host
468 self.cur = self.con.cursor()
469 cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid)
470 self.logger.debug(cmd)
471 self.cur.execute(cmd)
472 deleted = self.cur.rowcount
473 if deleted:
474 #delete uuid
475 self.cur = self.con.cursor()
476 cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid)
477 self.logger.debug(cmd)
478 self.cur.execute(cmd)
479 return deleted
480 except (mdb.Error, AttributeError) as e:
481 self._format_error(e, tries, "delete", "dependencies")
482 tries -= 1
483
484 def delete_row(self, **sql_dict):
485 ''' Deletes rows from a table.
486 Attribute sql_dir: dictionary with the following key: value
487 'FROM': string of table name (Mandatory)
488 'WHERE': dict of key:values, translated to key=value AND ... (Optional)
489 'WHERE_NOT': dict of key:values, translated to key<>value AND ... (Optional)
490 if value is None, it is translated to key is not null
491 'LIMIT': limit of number of rows (Optional)
492 Return: the number of deleted or exception if error
493 '''
494 #print sql_dict
495 from_ = "FROM " + str(sql_dict['FROM'])
496 #print 'from_', from_
497 if 'WHERE' in sql_dict and len(sql_dict['WHERE']) > 0:
498 w=sql_dict['WHERE']
499 where_ = "WHERE " + " AND ".join(map(self.__tuple2db_format_where, w.iteritems()))
500 else: where_ = ""
501 if 'WHERE_NOT' in sql_dict and len(sql_dict['WHERE_NOT']) > 0:
502 w=sql_dict['WHERE_NOT']
503 where_2 = " AND ".join(map(self.__tuple2db_format_where_not, w.iteritems()))
504 if len(where_)==0: where_ = "WHERE " + where_2
505 else: where_ = where_ + " AND " + where_2
506 #print 'where_', where_
507 limit_ = "LIMIT " + str(sql_dict['LIMIT']) if 'LIMIT' in sql_dict else ""
508 #print 'limit_', limit_
509 cmd = " ".join( ("DELETE", from_, where_, limit_) )
510 tries = 2
511 while tries:
512 try:
513 with self.con:
514 self.cur = self.con.cursor()
515 self.logger.debug(cmd)
516 self.cur.execute(cmd)
517 deleted = self.cur.rowcount
518 return deleted
519 except (mdb.Error, AttributeError) as e:
520 self._format_error(e, tries)
521 tries -= 1
522
523 def get_rows_by_id(self, table, uuid):
524 '''get row from a table based on uuid'''
525 tries = 2
526 while tries:
527 try:
528 with self.con:
529 self.cur = self.con.cursor(mdb.cursors.DictCursor)
530 cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid))
531 self.logger.debug(cmd)
532 self.cur.execute(cmd)
533 rows = self.cur.fetchall()
534 return rows
535 except (mdb.Error, AttributeError) as e:
536 self._format_error(e, tries)
537 tries -= 1
538
539 def get_rows(self, **sql_dict):
540 ''' Obtain rows from a table.
541 Attribute sql_dir: dictionary with the following key: value
542 'SELECT': list or tuple of fields to retrieve) (by default all)
543 'FROM': string of table name (Mandatory)
544 'WHERE': dict of key:values, translated to key=value (key is null) AND ... (Optional)
545 'WHERE_NOT': dict of key:values, translated to key<>value (key is not null) AND ... (Optional)
546 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
547 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional)
548 'LIMIT': limit of number of rows (Optional)
549 'ORDER_BY': list or tuple of fields to order, add ' DESC' to each item if inverse order is required
550 Return: a list with dictionaries at each row
551 '''
552 #print sql_dict
553 select_= "SELECT " + ("*" if 'SELECT' not in sql_dict else ",".join(map(str,sql_dict['SELECT'])) )
554 #print 'select_', select_
555 from_ = "FROM " + str(sql_dict['FROM'])
556 #print 'from_', from_
557 where_and = ""
558 where_or = ""
559 w=sql_dict.get('WHERE')
560 if w:
561 where_and = " AND ".join(map(self.__tuple2db_format_where, w.iteritems() ))
562 w=sql_dict.get('WHERE_NOT')
563 if w:
564 if where_and: where_and += " AND "
565 where_and += " AND ".join(map(self.__tuple2db_format_where_not, w.iteritems() ) )
566 w=sql_dict.get('WHERE_OR')
567 if w:
568 where_or = " OR ".join(map(self.__tuple2db_format_where, w.iteritems() ))
569 if where_and and where_or:
570 if sql_dict.get("WHERE_AND_OR") == "AND":
571 where_ = "WHERE " + where_and + " AND (" + where_or + ")"
572 else:
573 where_ = "WHERE (" + where_and + ") OR " + where_or
574 elif where_and and not where_or:
575 where_ = "WHERE " + where_and
576 elif not where_and and where_or:
577 where_ = "WHERE " + where_or
578 else:
579 where_ = ""
580 #print 'where_', where_
581 limit_ = "LIMIT " + str(sql_dict['LIMIT']) if 'LIMIT' in sql_dict else ""
582 order_ = "ORDER BY " + ",".join(map(str,sql_dict['ORDER_BY'])) if 'ORDER_BY' in sql_dict else ""
583
584 #print 'limit_', limit_
585 cmd = " ".join( (select_, from_, where_, limit_, order_) )
586 tries = 2
587 while tries:
588 try:
589 with self.con:
590 self.cur = self.con.cursor(mdb.cursors.DictCursor)
591 self.logger.debug(cmd)
592 self.cur.execute(cmd)
593 rows = self.cur.fetchall()
594 return rows
595 except (mdb.Error, AttributeError) as e:
596 self._format_error(e, tries)
597 tries -= 1
598
599 def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"):
600 ''' Obtain One row from a table based on name or uuid.
601 Attribute:
602 table: string of table name
603 uuid_name: name or uuid. If not uuid format is found, it is considered a name
604 allow_severeral: if False return ERROR if more than one row are founded
605 error_item_text: in case of error it identifies the 'item' name for a proper output text
606 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
607 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional
608 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
609 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
610 '''
611
612 if error_item_text==None:
613 error_item_text = table
614 what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name'
615 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
616 if WHERE_OR:
617 where_or = " OR ".join(map(self.__tuple2db_format_where, WHERE_OR.iteritems() ))
618 if WHERE_AND_OR == "AND":
619 cmd += " AND (" + where_or + ")"
620 else:
621 cmd += " OR " + where_or
622
623
624 tries = 2
625 while tries:
626 try:
627 with self.con:
628 self.cur = self.con.cursor(mdb.cursors.DictCursor)
629 self.logger.debug(cmd)
630 self.cur.execute(cmd)
631 number = self.cur.rowcount
632 if number==0:
633 return -HTTP_Not_Found, "No %s found with %s '%s'" %(error_item_text, what, uuid_name)
634 elif number>1 and not allow_serveral:
635 return -HTTP_Bad_Request, "More than one %s found with %s '%s'" %(error_item_text, what, uuid_name)
636 if allow_serveral:
637 rows = self.cur.fetchall()
638 else:
639 rows = self.cur.fetchone()
640 return rows
641 except (mdb.Error, AttributeError) as e:
642 self._format_error(e, tries)
643 tries -= 1
644
645 def get_uuid(self, uuid):
646 '''check in the database if this uuid is already present'''
647 for retry_ in range(0,2):
648 try:
649 with self.con:
650 self.cur = self.con.cursor(mdb.cursors.DictCursor)
651 self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'")
652 rows = self.cur.fetchall()
653 return self.cur.rowcount, rows
654 except (mdb.Error, AttributeError) as e:
655 print "nfvo_db.get_uuid DB Exception %d: %s" % (e.args[0], e.args[1])
656 r,c = self._format_error(e)
657 if r!=-HTTP_Request_Timeout or retry_==1: return r,c
658
659 def get_uuid_from_name(self, table, name):
660 '''Searchs in table the name and returns the uuid
661 '''
662 tries = 2
663 while tries:
664 try:
665 with self.con:
666 self.cur = self.con.cursor(mdb.cursors.DictCursor)
667 where_text = "name='" + name +"'"
668 self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text)
669 rows = self.cur.fetchall()
670 if self.cur.rowcount==0:
671 return 0, "Name %s not found in table %s" %(name, table)
672 elif self.cur.rowcount>1:
673 return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table)
674 return self.cur.rowcount, rows[0]["uuid"]
675 except (mdb.Error, AttributeError) as e:
676 self._format_error(e, tries)
677 tries -= 1
678