1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 Base class for openmano database manipulation
27 __author__
="Alfonso Tierno"
28 __date__
="$4-Apr-2016 10:05:01$"
38 from jsonschema
import validate
as js_v
, exceptions
as js_e
40 HTTP_Bad_Request
= 400
41 HTTP_Unauthorized
= 401
43 HTTP_Method_Not_Allowed
= 405
44 HTTP_Request_Timeout
= 408
46 HTTP_Service_Unavailable
= 503
47 HTTP_Internal_Server_Error
= 500
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}$"}
55 except js_e
.ValidationError
:
57 js_v(uuid
, id_schema2
)
59 except js_e
.ValidationError
:
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
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:
76 _convert_datetime2str(v
)
78 def _convert_bandwidth(data
, reverse
=False):
79 '''Check the field bandwidth recursivelly and when found, it removes units and convert to number
80 It assumes that bandwidth is well formed
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
87 if type(data
) is dict:
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
)
91 if "bandwidth" in data
:
93 value
=str(data
["bandwidth"])
95 pos
= value
.find("bps")
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])
101 value
= int(data
["bandwidth"])
102 if value
% 1000 == 0: data
["bandwidth"]=str(value
/1000) + " Gbps"
103 else: data
["bandwidth"]=str(value
) + " Mbps"
105 print "convert_bandwidth exception for type", type(data
["bandwidth"]), " data", data
["bandwidth"]
107 if type(data
) is tuple or type(data
) is list:
109 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
110 _convert_bandwidth(k
, reverse
)
112 def _convert_str2boolean(data
, items
):
113 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
116 'data': dictionary variable to be checked. None or empty is considered valid
117 'items': tuple of keys to convert
121 if type(data
) is dict:
122 for k
in data
.keys():
123 if type(data
[k
]) is dict or type(data
[k
]) is tuple or type(data
[k
]) is list:
124 _convert_str2boolean(data
[k
], items
)
126 if type(data
[k
]) is str:
127 if data
[k
]=="false" or data
[k
]=="False": data
[k
]=False
128 elif data
[k
]=="true" or data
[k
]=="True": data
[k
]=True
129 if type(data
) is tuple or type(data
) is list:
131 if type(k
) is dict or type(k
) is tuple or type(k
) is list:
132 _convert_str2boolean(k
, items
)
134 class db_base_Exception(Exception):
135 '''Common Exception for all database exceptions'''
137 def __init__(self
, message
, http_code
=HTTP_Bad_Request
):
138 Exception.__init
__(self
, message
)
139 self
.http_code
= http_code
142 tables_with_created_field
=()
144 def __init__(self
, host
=None, user
=None, passwd
=None, database
=None, log_name
='db', log_level
="ERROR"):
148 self
.database
= database
150 self
.log_level
=log_level
151 self
.logger
= logging
.getLogger(log_name
)
152 self
.logger
.setLevel( getattr(logging
, log_level
) )
154 def connect(self
, host
=None, user
=None, passwd
=None, database
=None):
155 '''Connect to specific data base.
156 The first time a valid host, user, passwd and database must be provided,
157 Following calls can skip this parameters
160 if host
: self
.host
= host
161 if user
: self
.user
= user
162 if passwd
: self
.passwd
= passwd
163 if database
: self
.database
= database
165 self
.con
= mdb
.connect(self
.host
, self
.user
, self
.passwd
, self
.database
)
166 print "DB: connected to %s@%s -> %s" % (self
.user
, self
.host
, self
.database
)
168 raise db_base_Exception("Cannot connect to DB {}@{} -> {} Error {}: {}".format(self
.user
, self
.host
, self
.database
, e
.args
[0], e
.args
[1]),
169 code
= HTTP_Internal_Server_Error
)
171 def get_db_version(self
):
172 ''' Obtain the database schema version.
173 Return: (negative, text) if error or version 0.0 where schema_version table is missing
174 (version_int, version_text) if ok
176 cmd
= "SELECT version_int,version FROM schema_version"
181 self
.cur
= self
.con
.cursor()
182 self
.logger
.debug(cmd
)
183 self
.cur
.execute(cmd
)
184 rows
= self
.cur
.fetchall()
185 highest_version_int
=0
187 for row
in rows
: #look for the latest version
188 if row
[0]>highest_version_int
:
189 highest_version_int
, highest_version
= row
[0:2]
190 return highest_version_int
, highest_version
191 except (mdb
.Error
, AttributeError) as e
:
192 #self.logger.error("get_db_version DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd)
193 self
._format
_error
(e
, tries
)
196 def disconnect(self
):
197 '''disconnect from specific data base'''
201 except mdb
.Error
as e
:
202 self
.logger
.error("while disconnecting from DB: Error %d: %s",e
.args
[0], e
.args
[1])
204 except AttributeError as e
: #self.con not defined
205 if e
[0][-5:] == "'con'":
206 self
.logger
.warn("while disconnecting from DB: Error %d: %s",e
.args
[0], e
.args
[1])
211 def _format_error(self
, e
, tries
=1, command
=None, extra
=None):
212 '''Creates a text error base on the produced exception
215 retry: in case of timeout, if reconnecting to database and retry, or raise and exception
216 cmd: database command that produce the exception
217 command: if the intention is update or delete
218 extra: extra information to add to some commands
220 HTTP error in negative, formatted error text
222 if isinstance(e
,AttributeError ):
223 raise db_base_Exception("DB Exception " + str(e
), HTTP_Internal_Server_Error
)
224 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
226 self
.logger
.warn("DB Exception '%s'. Retry", str(e
))
231 raise db_base_Exception("Database connection timeout Try Again", HTTP_Request_Timeout
)
233 fk
=e
.args
[1].find("foreign key constraint fails")
235 if command
=="update":
236 raise db_base_Exception("tenant_id '{}' not found.".format(extra
), HTTP_Not_Found
)
237 elif command
=="delete":
238 raise db_base_Exception("Resource is not free. There are {} that prevent deleting it.".format(extra
), HTTP_Conflict
)
239 de
= e
.args
[1].find("Duplicate entry")
240 fk
= e
.args
[1].find("for key")
241 uk
= e
.args
[1].find("Unknown column")
242 wc
= e
.args
[1].find("in 'where clause'")
243 fl
= e
.args
[1].find("in 'field list'")
244 #print de, fk, uk, wc,fl
246 if fk
>=0: #error 1062
247 raise db_base_Exception("Value {} already in use for {}".format(e
.args
[1][de
+15:fk
], e
.args
[1][fk
+7:]), HTTP_Conflict
)
250 raise db_base_Exception("Field {} can not be used for filtering".format(e
.args
[1][uk
+14:wc
]), HTTP_Bad_Request
)
252 raise db_base_Exception("Field {} does not exist".format(e
.args
[1][uk
+14:wc
]), HTTP_Bad_Request
)
253 raise db_base_Exception("Database internal Error {}: {}".format(e
.args
[0], e
.args
[1]), HTTP_Internal_Server_Error
)
255 def __str2db_format(self
, data
):
256 '''Convert string data to database format.
257 If data is None it returns the 'Null' text,
258 otherwise it returns the text surrounded by quotes ensuring internal quotes are escaped.
263 return json
.dumps(str(data
))
265 def __tuple2db_format_set(self
, data
):
266 '''Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B),
267 and it returns the text 'A="B"', where A is a field of a table and B is the value
268 If B is None it returns the 'A=Null' text, without surrounding Null by quotes
269 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
270 and it ensures internal quotes of B are escaped.
273 return str(data
[0]) + "=Null"
275 return str(data
[0]) + '=' + json
.dumps(str(data
[1]))
277 def __tuple2db_format_where(self
, data
):
278 '''Compose the needed text for a SQL WHERE, parameter 'data' is a pair tuple (A,B),
279 and it returns the text 'A="B"', where A is a field of a table and B is the value
280 If B is None it returns the 'A is Null' text, without surrounding Null by quotes
281 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
282 and it ensures internal quotes of B are escaped.
285 return str(data
[0]) + " is Null"
287 # if type(data[1]) is tuple: #this can only happen in a WHERE_OR clause
291 # text.append(str(data[0]) + " is Null")
295 # text.append( str(data[0]) + "='" + out + "'" )
296 # elif '"' not in out:
297 # text.append( str(data[0]) + '="' + out + '"' )
299 # text.append( str(data[0]) + '=' + json.dumps(out) )
300 # return " OR ".join(text)
303 return str(data
[0]) + '=' + json
.dumps(out
)
305 def __tuple2db_format_where_not(self
, data
):
306 '''Compose the needed text for a SQL WHERE(not). parameter 'data' is a pair tuple (A,B),
307 and it returns the text 'A<>"B"', where A is a field of a table and B is the value
308 If B is None it returns the 'A is not Null' text, without surrounding Null by quotes
309 If B is not None it returns the text "A<>'B'" or 'A<>"B"' where B is surrounded by quotes,
310 and it ensures internal quotes of B are escaped.
313 return str(data
[0]) + " is not Null"
315 return str(data
[0]) + '<>' + json
.dumps(out
)
317 def __remove_quotes(self
, data
):
318 '''remove single quotes ' of any string content of data dictionary'''
319 for k
,v
in data
.items():
322 data
[k
] = data
[k
].replace("'","_")
324 def _update_rows(self
, table
, UPDATE
, WHERE
, modified_time
=0):
325 ''' Update one or several rows into a table.
327 UPDATE: dictionary with the key: value to change
328 table: table where to update
329 WHERE: dictionary of elements to update
330 Return: the number of updated rows, exception if error
333 values
= ",".join(map(self
.__tuple
2db
_format
_set
, UPDATE
.iteritems() ))
335 values
+= ",modified_at={:f}".format(modified_time
)
336 cmd
= "UPDATE " + table
+" SET " + values
+\
337 " WHERE " + " and ".join(map(self
.__tuple
2db
_format
_where
, WHERE
.iteritems() ))
338 self
.logger
.debug(cmd
)
339 self
.cur
.execute(cmd
)
340 return self
.cur
.rowcount
342 def _new_row_internal(self
, table
, INSERT
, add_uuid
=False, root_uuid
=None, created_time
=0):
343 ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created
345 INSERT: dictionary with the key:value to insert
346 table: table where to insert
347 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
348 created_time: time to add to the created_time column
349 It checks presence of uuid and add one automatically otherwise
354 #create uuid if not provided
355 if 'uuid' not in INSERT
:
356 uuid
= INSERT
['uuid'] = str(myUuid
.uuid1()) # create_uuid
358 uuid
= str(INSERT
['uuid'])
362 #defining root_uuid if not provided
363 if root_uuid
is None:
366 created_at
= created_time
368 created_at
=time
.time()
370 cmd
= "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid
, root_uuid
, table
, created_at
)
371 self
.logger
.debug(cmd
)
372 self
.cur
.execute(cmd
)
374 cmd
= "INSERT INTO " + table
+" SET " + \
375 ",".join(map(self
.__tuple
2db
_format
_set
, INSERT
.iteritems() ))
377 cmd
+= ",created_at=%f" % created_time
378 self
.logger
.debug(cmd
)
379 self
.cur
.execute(cmd
)
383 def _get_rows(self
,table
,uuid
):
384 cmd
= "SELECT * FROM {} WHERE uuid='{}'".format(str(table
), str(uuid
))
385 self
.logger
.debug(cmd
)
386 self
.cur
.execute(cmd
)
387 rows
= self
.cur
.fetchall()
390 def new_row(self
, table
, INSERT
, add_uuid
=False, created_time
=0):
391 ''' Add one row into a table.
393 INSERT: dictionary with the key: value to insert
394 table: table where to insert
395 tenant_id: only useful for logs. If provided, logs will use this tenant_id
396 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
397 It checks presence of uuid and add one automatically otherwise
398 Return: (result, uuid) where result can be 0 if error, or 1 if ok
400 if table
in self
.tables_with_created_field
and created_time
==0:
401 created_time
=time
.time()
406 self
.cur
= self
.con
.cursor()
407 return self
._new
_row
_internal
(table
, INSERT
, add_uuid
, None, created_time
)
409 except (mdb
.Error
, AttributeError) as e
:
410 self
._format
_error
(e
, tries
)
413 def update_rows(self
, table
, UPDATE
, WHERE
, modified_time
=0):
414 ''' Update one or several rows into a table.
416 UPDATE: dictionary with the key: value to change
417 table: table where to update
418 WHERE: dictionary of elements to update
419 Return: (result, descriptive text) where result indicates the number of updated files
421 if table
in self
.tables_with_created_field
and modified_time
==0:
422 modified_time
=time
.time()
427 self
.cur
= self
.con
.cursor()
428 return self
._update
_rows
(table
, UPDATE
, WHERE
)
430 except (mdb
.Error
, AttributeError) as e
:
431 self
._format
_error
(e
, tries
)
434 def delete_row_by_id(self
, table
, uuid
):
440 self
.cur
= self
.con
.cursor()
441 cmd
= "DELETE FROM {} WHERE uuid = '{}'".format(table
, uuid
)
442 self
.logger
.debug(cmd
)
443 self
.cur
.execute(cmd
)
444 deleted
= self
.cur
.rowcount
447 self
.cur
= self
.con
.cursor()
448 cmd
= "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid
)
449 self
.logger
.debug(cmd
)
450 self
.cur
.execute(cmd
)
452 except (mdb
.Error
, AttributeError) as e
:
453 self
._format
_error
(e
, tries
, "delete", "dependencies")
456 def delete_row(self
, **sql_dict
):
457 ''' Deletes rows from a table.
458 Attribute sql_dir: dictionary with the following key: value
459 'FROM': string of table name (Mandatory)
460 'WHERE': dict of key:values, translated to key=value AND ... (Optional)
461 'WHERE_NOT': dict of key:values, translated to key<>value AND ... (Optional)
462 if value is None, it is translated to key is not null
463 'LIMIT': limit of number of rows (Optional)
464 Return: the number of deleted or exception if error
467 from_
= "FROM " + str(sql_dict
['FROM'])
468 #print 'from_', from_
469 if 'WHERE' in sql_dict
and len(sql_dict
['WHERE']) > 0:
471 where_
= "WHERE " + " AND ".join(map(self
.__tuple
2db
_format
_where
, w
.iteritems()))
473 if 'WHERE_NOT' in sql_dict
and len(sql_dict
['WHERE_NOT']) > 0:
474 w
=sql_dict
['WHERE_NOT']
475 where_2
= " AND ".join(map(self
.__tuple
2db
_format
_where
_not
, w
.iteritems()))
476 if len(where_
)==0: where_
= "WHERE " + where_2
477 else: where_
= where_
+ " AND " + where_2
478 #print 'where_', where_
479 limit_
= "LIMIT " + str(sql_dict
['LIMIT']) if 'LIMIT' in sql_dict
else ""
480 #print 'limit_', limit_
481 cmd
= " ".join( ("DELETE", from_
, where_
, limit_
) )
486 self
.cur
= self
.con
.cursor()
487 self
.logger
.debug(cmd
)
488 self
.cur
.execute(cmd
)
489 deleted
= self
.cur
.rowcount
491 except (mdb
.Error
, AttributeError) as e
:
492 self
._format
_error
(e
, tries
)
495 def get_rows_by_id(self
, table
, uuid
):
496 '''get row from a table based on uuid'''
501 self
.cur
= self
.con
.cursor(mdb
.cursors
.DictCursor
)
502 cmd
="SELECT * FROM {} where uuid='{}'".format(str(table
), str(uuid
))
503 self
.logger
.debug(cmd
)
504 self
.cur
.execute(cmd
)
505 rows
= self
.cur
.fetchall()
507 except (mdb
.Error
, AttributeError) as e
:
508 self
._format
_error
(e
, tries
)
511 def get_rows(self
, **sql_dict
):
512 ''' Obtain rows from a table.
513 Attribute sql_dir: dictionary with the following key: value
514 'SELECT': list or tuple of fields to retrieve) (by default all)
515 'FROM': string of table name (Mandatory)
516 'WHERE': dict of key:values, translated to key=value (key is null) AND ... (Optional)
517 'WHERE_NOT': dict of key:values, translated to key<>value (key is not null) AND ... (Optional)
518 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
519 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional)
520 'LIMIT': limit of number of rows (Optional)
521 'ORDER_BY': list or tuple of fields to order
522 Return: a list with dictionaries at each row
525 select_
= "SELECT " + ("*" if 'SELECT' not in sql_dict
else ",".join(map(str,sql_dict
['SELECT'])) )
526 #print 'select_', select_
527 from_
= "FROM " + str(sql_dict
['FROM'])
528 #print 'from_', from_
531 w
=sql_dict
.get('WHERE')
533 where_and
= " AND ".join(map(self
.__tuple
2db
_format
_where
, w
.iteritems() ))
534 w
=sql_dict
.get('WHERE_NOT')
536 if where_and
: where_and
+= " AND "
537 where_and
+= " AND ".join(map(self
.__tuple
2db
_format
_where
_not
, w
.iteritems() ) )
538 w
=sql_dict
.get('WHERE_OR')
540 where_or
= " OR ".join(map(self
.__tuple
2db
_format
_where
, w
.iteritems() ))
541 if where_and
and where_or
:
542 if sql_dict
.get("WHERE_AND_OR") == "AND":
543 where_
= "WHERE " + where_and
+ " AND (" + where_or
+ ")"
545 where_
= "WHERE (" + where_and
+ ") OR " + where_or
546 elif where_and
and not where_or
:
547 where_
= "WHERE " + where_and
548 elif not where_and
and where_or
:
549 where_
= "WHERE " + where_or
552 #print 'where_', where_
553 limit_
= "LIMIT " + str(sql_dict
['LIMIT']) if 'LIMIT' in sql_dict
else ""
554 order_
= "ORDER BY " + ",".join(map(str,sql_dict
['SELECT'])) if 'ORDER_BY' in sql_dict
else ""
556 #print 'limit_', limit_
557 cmd
= " ".join( (select_
, from_
, where_
, limit_
, order_
) )
562 self
.cur
= self
.con
.cursor(mdb
.cursors
.DictCursor
)
563 self
.logger
.debug(cmd
)
564 self
.cur
.execute(cmd
)
565 rows
= self
.cur
.fetchall()
567 except (mdb
.Error
, AttributeError) as e
:
568 self
._format
_error
(e
, tries
)
571 def get_table_by_uuid_name(self
, table
, uuid_name
, error_item_text
=None, allow_serveral
=False, WHERE_OR
={}, WHERE_AND_OR
="OR"):
572 ''' Obtain One row from a table based on name or uuid.
574 table: string of table name
575 uuid_name: name or uuid. If not uuid format is found, it is considered a name
576 allow_severeral: if False return ERROR if more than one row are founded
577 error_item_text: in case of error it identifies the 'item' name for a proper output text
578 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
579 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional
580 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
581 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
584 if error_item_text
==None:
585 error_item_text
= table
586 what
= 'uuid' if af
.check_valid_uuid(uuid_name
) else 'name'
587 cmd
= " SELECT * FROM {} WHERE {}='{}'".format(table
, what
, uuid_name
)
589 where_or
= " OR ".join(map(self
.__tuple
2db
_format
_where
, WHERE_OR
.iteritems() ))
590 if WHERE_AND_OR
== "AND":
591 cmd
+= " AND (" + where_or
+ ")"
593 cmd
+= " OR " + where_or
600 self
.cur
= self
.con
.cursor(mdb
.cursors
.DictCursor
)
601 self
.logger
.debug(cmd
)
602 self
.cur
.execute(cmd
)
603 number
= self
.cur
.rowcount
605 return -HTTP_Not_Found
, "No %s found with %s '%s'" %(error_item_text
, what
, uuid_name
)
606 elif number
>1 and not allow_serveral
:
607 return -HTTP_Bad_Request
, "More than one %s found with %s '%s'" %(error_item_text
, what
, uuid_name
)
609 rows
= self
.cur
.fetchall()
611 rows
= self
.cur
.fetchone()
613 except (mdb
.Error
, AttributeError) as e
:
614 self
._format
_error
(e
, tries
)
617 def get_uuid(self
, uuid
):
618 '''check in the database if this uuid is already present'''
619 for retry_
in range(0,2):
622 self
.cur
= self
.con
.cursor(mdb
.cursors
.DictCursor
)
623 self
.cur
.execute("SELECT * FROM uuids where uuid='" + str(uuid
) + "'")
624 rows
= self
.cur
.fetchall()
625 return self
.cur
.rowcount
, rows
626 except (mdb
.Error
, AttributeError) as e
:
627 print "nfvo_db.get_uuid DB Exception %d: %s" % (e
.args
[0], e
.args
[1])
628 r
,c
= self
._format
_error
(e
)
629 if r
!=-HTTP_Request_Timeout
or retry_
==1: return r
,c
631 def get_uuid_from_name(self
, table
, name
):
632 '''Searchs in table the name and returns the uuid
638 self
.cur
= self
.con
.cursor(mdb
.cursors
.DictCursor
)
639 where_text
= "name='" + name
+"'"
640 self
.cur
.execute("SELECT * FROM " + table
+ " WHERE "+ where_text
)
641 rows
= self
.cur
.fetchall()
642 if self
.cur
.rowcount
==0:
643 return 0, "Name %s not found in table %s" %(name
, table
)
644 elif self
.cur
.rowcount
>1:
645 return self
.cur
.rowcount
, "More than one VNF with name %s found in table %s" %(name
, table
)
646 return self
.cur
.rowcount
, rows
[0]["uuid"]
647 except (mdb
.Error
, AttributeError) as e
:
648 self
._format
_error
(e
, tries
)