# Copyright 2014 Huawei Technologies Co. Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Utils for database usage.""" import functools import inspect import logging import netaddr import re from inspect import isfunction from sqlalchemy import and_ from sqlalchemy import or_ from compass.db import exception from compass.db import models from compass.utils import util def model_query(session, model): """model query. Return sqlalchemy query object. """ if not issubclass(model, models.BASE): raise exception.DatabaseException("model should be sublass of BASE!") return session.query(model) def _default_list_condition_func(col_attr, value, condition_func): """The default condition func for a list of data. Given the condition func for single item of data, this function wrap the condition_func and return another condition func using or_ to merge the conditions of each single item to deal with a list of data item. Args: col_attr: the colomn name value: the column value need to be compared. condition_func: the sqlalchemy condition object like == Examples: col_attr is name, value is ['a', 'b', 'c'] and condition_func is ==, the returned condition is name == 'a' or name == 'b' or name == 'c' """ conditions = [] for sub_value in value: condition = condition_func(col_attr, sub_value) if condition is not None: conditions.append(condition) if conditions: return or_(*conditions) else: return None def _one_item_list_condition_func(col_attr, value, condition_func): """The wrapper condition func to deal with one item data list. For simplification, it is used to reduce generating too complex sql conditions. """ if value: return condition_func(col_attr, value[0]) else: return None def _model_condition_func( col_attr, value, item_condition_func, list_condition_func=_default_list_condition_func ): """Return sql condition based on value type.""" if isinstance(value, list): if not value: return None if len(value) == 1: return item_condition_func(col_attr, value) return list_condition_func( col_attr, value, item_condition_func ) else: return item_condition_func(col_attr, value) def _between_condition(col_attr, value): """Return sql range condition.""" if value[0] is not None and value[1] is not None: return col_attr.between(value[0], value[1]) if value[0] is not None: return col_attr >= value[0] if value[1] is not None: return col_attr <= value[1] return None def model_order_by(query, model, order_by): """append order by into sql query model.""" if not order_by: return query order_by_cols = [] for key in order_by: if isinstance(key, tuple): key, is_desc = key else: is_desc = False if isinstance(key, basestring): if hasattr(model, key): col_attr = getattr(model, key) else: continue else: col_attr = key if is_desc: order_by_cols.append(col_attr.desc()) else: order_by_cols.append(col_attr) return query.order_by(*order_by_cols) def _model_condition(col_attr, value): """Generate condition for one column. Example for col_attr is name: value is 'a': name == 'a' value is ['a']: name == 'a' value is ['a', 'b']: name == 'a' or name == 'b' value is {'eq': 'a'}: name == 'a' value is {'lt': 'a'}: name < 'a' value is {'le': 'a'}: name <= 'a' value is {'gt': 'a'}: name > 'a' value is {'ge': 'a'}: name >= 'a' value is {'ne': 'a'}: name != 'a' value is {'in': ['a', 'b']}: name in ['a', 'b'] value is {'notin': ['a', 'b']}: name not in ['a', 'b'] value is {'startswith': 'abc'}: name like 'abc%' value is {'endswith': 'abc'}: name like '%abc' value is {'like': 'abc'}: name like '%abc%' value is {'between': ('a', 'c')}: name >= 'a' and name <= 'c' value is [{'lt': 'a'}]: name < 'a' value is [{'lt': 'a'}, {'gt': c'}]: name < 'a' or name > 'c' value is {'lt': 'c', 'gt': 'a'}: name > 'a' and name < 'c' If value is a list, the condition is the or relationship among conditions of each item. If value is dict and there are multi keys in the dict, the relationship is and conditions of each key. Otherwise the condition is to compare the column with the value. """ if isinstance(value, list): basetype_values = [] composite_values = [] for item in value: if isinstance(item, (list, dict)): composite_values.append(item) else: basetype_values.append(item) conditions = [] if basetype_values: if len(basetype_values) == 1: condition = (col_attr == basetype_values[0]) else: condition = col_attr.in_(basetype_values) conditions.append(condition) for composite_value in composite_values: condition = _model_condition(col_attr, composite_value) if condition is not None: conditions.append(condition) if not conditions: return None if len(conditions) == 1: return conditions[0] return or_(*conditions) elif isinstance(value, dict): conditions = [] if 'eq' in value: conditions.append(_model_condition_func( col_attr, value['eq'], lambda attr, data: attr == data, lambda attr, data, item_condition_func: attr.in_(data) )) if 'lt' in value: conditions.append(_model_condition_func( col_attr, value['lt'], lambda attr, data: attr < data, _one_item_list_condition_func )) if 'gt' in value: conditions.append(_model_condition_func( col_attr, value['gt'], lambda attr, data: attr > data, _one_item_list_condition_func )) if 'le' in value: conditions.append(_model_condition_func( col_attr, value['le'], lambda attr, data: attr <= data, _one_item_list_condition_func )) if 'ge' in value: conditions.append(_model_condition_func( col_attr, value['ge'], lambda attr, data: attr >= data, _one_item_list_condition_func )) if 'ne' in value: conditions.append(_model_condition_func( col_attr, value['ne'], lambda attr, data: attr != data, lambda attr, data, item_condition_func: attr.notin_(data) )) if 'in' in value: conditions.append(col_attr.in_(value['in'])) if 'notin' in value: conditions.append(col_attr.notin_(value['notin'])) if 'startswith' in value: conditions.append(_model_condition_func( col_attr, value['startswith'], lambda attr, data: attr.like('%s%%' % data) )) if 'endswith' in value: conditions.append(_model_condition_func( col_attr, value['endswith'], lambda attr, data: attr.like('%%%s' % data) )) if 'like' in value: conditions.append(_model_condition_func( col_attr, value['like'], lambda attr, data: attr.like('%%%s%%' % data) )) if 'between' in value: conditions.append(_model_condition_func( col_attr, value['between'], _between_condition )) conditions = [ condition for condition in conditions if condition is not None ] if not conditions: return None if len(conditions) == 1: return conditions[0] return and_(conditions) else: condition = (col_attr == value) return condition def model_filter(query, model, **filters): """Append conditons to query for each possible column.""" for key, value in filters.items(): if isinstance(key, basestring): if hasattr(model, key): col_attr = getattr(model, key) else: continue else: col_attr = key condition = _model_condition(col_attr, value) if condition is not None: query = query.filter(condition) return query def replace_output(**output_mapping): """Decorator to recursively relace output by output mapping. The replacement detail is described in _replace_output. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return _replace_output( func(*args, **kwargs), **output_mapping ) return wrapper return decorator def _replace_output(data, **output_mapping): """Helper to replace output data. Example: data = {'a': 'hello'} output_mapping = {'a': 'b'} returns: {'b': 'hello'} data = {'a': {'b': 'hello'}} output_mapping = {'a': 'b'} returns: {'b': {'b': 'hello'}} data = {'a': {'b': 'hello'}} output_mapping = {'a': {'b': 'c'}} returns: {'a': {'c': 'hello'}} data = [{'a': 'hello'}, {'a': 'hi'}] output_mapping = {'a': 'b'} returns: [{'b': 'hello'}, {'b': 'hi'}] """ if isinstance(data, list): return [ _replace_output(item, **output_mapping) for item in data ] if not isinstance(data, dict): raise exception.InvalidResponse( '%s type is not dict' % data ) info = {} for key, value in data.items(): if key in output_mapping: output_key = output_mapping[key] if isinstance(output_key, basestring): info[output_key] = value else: info[key] = ( _replace_output(value, **output_key) ) else: info[key] = value return info def get_wrapped_func(func): """Get wrapped function instance. Example: @dec1 @dec2 myfunc(*args, **kwargs) get_wrapped_func(myfunc) returns function object with following attributes: __name__: 'myfunc' args: args kwargs: kwargs otherwise myfunc is function object with following attributes: __name__: partial object ... args: ... kwargs: ... """ if func.func_closure: for closure in func.func_closure: if isfunction(closure.cell_contents): return get_wrapped_func(closure.cell_contents) return func else: return func def wrap_to_dict(support_keys=[], **filters): """Decrator to convert returned object to dict. The details is decribed in _wrapper_dict. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return _wrapper_dict( func(*args, **kwargs), support_keys, **filters ) return wrapper return decorator def _wrapper_dict(data, support_keys, **filters): """Helper for warpping db object into dictionary. If data is list, convert it to a list of dict If data is Base model, convert it to dict for the data as a dict, filter it with the supported keys. For each filter_key, filter_value in filters, also filter data[filter_key] by filter_value recursively if it exists. Example: data is models.Switch, it will be converted to { 'id': 1, 'ip': '10.0.0.1', 'ip_int': 123456, 'credentials': {'version': 2, 'password': 'abc'} } Then if support_keys are ['id', 'ip', 'credentials'], it will be filtered to { 'id': 1, 'ip': '10.0.0.1', 'credentials': {'version': 2, 'password': 'abc'} } Then if filters is {'credentials': ['version']}, it will be filtered to { 'id': 1, 'ip': '10.0.0.1', 'credentials': {'version': 2} } """ logging.debug( 'wrap dict %s by support_keys=%s filters=%s', data, support_keys, filters ) if isinstance(data, list): return [ _wrapper_dict(item, support_keys, **filters) for item in data ] if isinstance(data, models.HelperMixin): data = data.to_dict() if not isinstance(data, dict): raise exception.InvalidResponse( 'response %s type is not dict' % data ) info = {} try: for key in support_keys: if key in data and data[key] is not None: if key in filters: filter_keys = filters[key] if isinstance(filter_keys, dict): info[key] = _wrapper_dict( data[key], filter_keys.keys(), **filter_keys ) else: info[key] = _wrapper_dict( data[key], filter_keys ) else: info[key] = data[key] return info except Exception as error: logging.exception(error) raise error def replace_filters(**kwarg_mapping): """Decorator to replace kwargs. Examples: kwargs: {'a': 'b'}, kwarg_mapping: {'a': 'c'} replaced kwargs to decorated func: {'c': 'b'} replace_filters is used to replace caller's input to make it understandable by models.py. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): replaced_kwargs = {} for key, value in kwargs.items(): if key in kwarg_mapping: replaced_kwargs[kwarg_mapping[key]] = value else: replaced_kwargs[key] = value return func(*args, **replaced_kwargs) return wrapper return decorator def supported_filters( support_keys=[], optional_support_keys=[], ignore_support_keys=[], ): """Decorator to check kwargs keys. keys in kwargs and in ignore_support_keys will be removed. If any unsupported keys found, a InvalidParameter exception raises. Args: support_keys: keys that must exist. optional_support_keys: keys that may exist. ignore_support_keys: keys should be ignored. Assumption: args without default value is supposed to exist. You can add them in support_keys or not but we will make sure it appears when we call the decorated function. We do best match on both args and kwargs to make sure if the key appears or not. Examples: decorated func: func(a, b, c=3, d=4, **kwargs) support_keys=['e'] and call func(e=5): raises: InvalidParameter: missing declared arg support_keys=['e'] and call func(1,2,3,4,5,e=6): raises: InvalidParameter: caller sending more args support_keys=['e'] and call func(1,2): raises: InvalidParameter: supported keys ['e'] missing support_keys=['d', 'e'] and call func(1,2,e=3): raises: InvalidParameter: supported keys ['d'] missing support_keys=['d', 'e'] and call func(1,2,d=4, e=3): passed support_keys=['d'], optional_support_keys=['e'] and call func(1,2, d=3): passed support_keys=['d'], optional_support_keys=['e'] and call func(1,2, d=3, e=4, f=5): raises: InvalidParameter: unsupported keys ['f'] support_keys=['d'], optional_support_keys=['e'], ignore_support_keys=['f'] and call func(1,2, d=3, e=4, f=5): passed to decorated keys: func(1,2, d=3, e=4) """ def decorator(func): @functools.wraps(func) def wrapper(*args, **filters): wrapped_func = get_wrapped_func(func) argspec = inspect.getargspec(wrapped_func) wrapped_args = argspec.args args_defaults = argspec.defaults # wrapped_must_args are positional args caller must pass in. if args_defaults: wrapped_must_args = wrapped_args[:-len(args_defaults)] else: wrapped_must_args = wrapped_args[:] # make sure any positional args without default value in # decorated function should appear in args or filters. if len(args) < len(wrapped_must_args): remain_args = wrapped_must_args[len(args):] for remain_arg in remain_args: if remain_arg not in filters: raise exception.InvalidParameter( 'function missing declared arg %s ' 'while caller sends args %s' % ( remain_arg, args ) ) # make sure args should be no more than positional args # declared in decorated function. if len(args) > len(wrapped_args): raise exception.InvalidParameter( 'function definition args %s while the caller ' 'sends args %s' % ( wrapped_args, args ) ) # exist_args are positional args caller has given. exist_args = dict(zip(wrapped_args, args)).keys() must_support_keys = set(support_keys) all_support_keys = must_support_keys | set(optional_support_keys) wrapped_supported_keys = set(filters) | set(exist_args) unsupported_keys = ( set(filters) - set(wrapped_args) - all_support_keys - set(ignore_support_keys) ) # unsupported_keys are the keys that are not in support_keys, # optional_support_keys, ignore_support_keys and are not passed in # by positional args. It means the decorated function may # not understand these parameters. if unsupported_keys: raise exception.InvalidParameter( 'filter keys %s are not supported for %s' % ( list(unsupported_keys), wrapped_func ) ) # missing_keys are the keys that must exist but missing in # both positional args or kwargs. missing_keys = must_support_keys - wrapped_supported_keys if missing_keys: raise exception.InvalidParameter( 'filter keys %s not found for %s' % ( list(missing_keys), wrapped_func ) ) # We filter kwargs to eliminate ignore_support_keys in kwargs # passed to decorated function. filtered_filters = dict([ (key, value) for key, value in filters.items() if key not in ignore_support_keys ]) return func(*args, **filtered_filters) return wrapper return decorator def input_filters( **filters ): """Decorator to filter kwargs. For key in kwargs, if the key exists and filters and the return of call filters[key] is False, the key will be removed from kwargs. The function definition of filters[key] is func(value, *args, **kwargs) compared with decorated function func(*args, **kwargs) The function is used to filter kwargs in case some kwargs should be removed conditionally depends on the related filters. Examples: filters={'a': func(value, *args, **kwargs)} @input_filters(**filters) decorated_func(*args, **kwargs) func returns False. Then when call decorated_func(a=1, b=2) it will be actually called the decorated func with b=2. a=1 will be removed since it does not pass filtering. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): filtered_kwargs = {} for key, value in kwargs.items(): if key in filters: if filters[key](value, *args, **kwargs): filtered_kwargs[key] = value else: logging.debug( 'ignore filtered input key %s' % key ) else: filtered_kwargs[key] = value return func(*args, **filtered_kwargs) return wrapper return decorator def _obj_equal_or_subset(check, obj): """Used by output filter to check if obj is in check.""" if check == obj: return True if not issubclass(obj.__class__, check.__class__): return False if isinstance(obj, dict): return _dict_equal_or_subset(check, obj) elif isinstance(obj, list): return _list_equal_or_subset(check, obj) else: return False def _list_equal_or_subset(check_list, obj_list): """Used by output filter to check if obj_list is in check_list""" if not isinstance(check_list, list): return False return set(check_list).issubset(set(obj_list)) def _dict_equal_or_subset(check_dict, obj_dict): """Used by output filter to check if obj_dict in check_dict.""" if not isinstance(check_dict, dict): return False for key, value in check_dict.items(): if ( key not in obj_dict or not _obj_equal_or_subset(check_dict[key], obj_dict[key]) ): return False return True def general_filter_callback(general_filter, obj): """General filter function to filter output. Since some fields stored in database is json encoded and we want to do the deep match for the json encoded field to do the filtering in some cases, we introduces the output_filters and general_filter_callback to deal with this kind of cases. We do special treatment for key 'resp_eq' to check if obj is the recursively subset of general_filter['resp_eq'] Example: obj: 'b' general_filter: {} returns: True obj: 'b' general_filter: {'resp_in': ['a', 'b']} returns: True obj: 'b' general_filter: {'resp_in': ['a']} returns: False obj: 'b' general_filter: {'resp_eq': 'b'} returns: True obj: 'b' general_filter: {'resp_eq': 'a'} returns: False obj: 'b' general_filter: {'resp_range': ('a', 'c')} returns: True obj: 'd' general_filter: {'resp_range': ('a', 'c')} returns: False If there are multi keys in dict, the output is filtered by and relationship. If the general_filter is a list, the output is filtered by or relationship. Supported general filters: [ 'resp_eq', 'resp_in', 'resp_lt', 'resp_le', 'resp_gt', 'resp_ge', 'resp_match', 'resp_range' ] """ if isinstance(general_filter, list): if not general_filter: return True return any([ general_filter_callback(item, obj) for item in general_filter ]) elif isinstance(general_filter, dict): if 'resp_eq' in general_filter: if not _obj_equal_or_subset( general_filter['resp_eq'], obj ): return False if 'resp_in' in general_filter: in_filters = general_filter['resp_in'] if not any([ _obj_equal_or_subset(in_filer, obj) for in_filer in in_filters ]): return False if 'resp_lt' in general_filter: if obj >= general_filter['resp_lt']: return False if 'resp_le' in general_filter: if obj > general_filter['resp_le']: return False if 'resp_gt' in general_filter: if obj <= general_filter['resp_gt']: return False if 'resp_ge' in general_filter: if obj < general_filter['resp_gt']: return False if 'resp_match' in general_filter: if not re.match(general_filter['resp_match'], obj): return False if 'resp_range' in general_filter: resp_range = general_filter['resp_range'] if not isinstance(resp_range, list): resp_range = [resp_range] in_range = False for range_start, range_end in resp_range: if range_start <= obj <= range_end: in_range = True if not in_range: return False return True else: return True def filter_output(filter_callbacks, kwargs, obj, missing_ok=False): """Filter ouput. For each key in filter_callbacks, if it exists in kwargs, kwargs[key] tells what we need to filter. If the call of filter_callbacks[key] returns False, it tells the obj should be filtered out of output. """ for callback_key, callback_value in filter_callbacks.items(): if callback_key not in kwargs: continue if callback_key not in obj: if missing_ok: continue else: raise exception.InvalidResponse( '%s is not in %s' % (callback_key, obj) ) if not callback_value( kwargs[callback_key], obj[callback_key] ): return False return True def output_filters(missing_ok=False, **filter_callbacks): """Decorator to filter output list. Each filter_callback should have the definition like: func({'resp_eq': 'a'}, 'a') """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): filtered_obj_list = [] obj_list = func(*args, **kwargs) for obj in obj_list: if filter_output( filter_callbacks, kwargs, obj, missing_ok ): filtered_obj_list.append(obj) return filtered_obj_list return wrapper return decorator def _input_validates(args_validators, kwargs_validators, *args, **kwargs): """Used by input_validators to validate inputs.""" for i, value in enumerate(args): if i < len(args_validators) and args_validators[i]: args_validators[i](value) for key, value in kwargs.items(): if kwargs_validators.get(key): kwargs_validators[key](value) def input_validates(*args_validators, **kwargs_validators): """Decorator to validate input. Each validator should have definition like: func('00:01:02:03:04:05') """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): _input_validates( args_validators, kwargs_validators, *args, **kwargs ) return func(*args, **kwargs) return wrapper return decorator def _input_validates_with_args( args_validators, kwargs_validators, *args, **kwargs ): """Validate input with validators. Each validator takes the arguments of the decorated function as its arguments. The function definition is like: func(value, *args, **kwargs) compared with the decorated function func(*args, **kwargs). """ for i, value in enumerate(args): if i < len(args_validators) and args_validators[i]: args_validators[i](value, *args, **kwargs) for key, value in kwargs.items(): if kwargs_validators.get(key): kwargs_validators[key](value, *args, **kwargs) def input_validates_with_args( *args_validators, **kwargs_validators ): """Decorator to validate input.""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): _input_validates_with_args( args_validators, kwargs_validators, *args, **kwargs ) return func(*args, **kwargs) return wrapper return decorator def _output_validates_with_args( kwargs_validators, obj, *args, **kwargs ): """Validate output with validators. Each validator takes the arguments of the decorated function as its arguments. The function definition is like: func(value, *args, **kwargs) compared with the decorated function func(*args, **kwargs). """ if isinstance(obj, list): for item in obj: _output_validates_with_args( kwargs_validators, item, *args, **kwargs ) return if isinstance(obj, models.HelperMixin): obj = obj.to_dict() if not isinstance(obj, dict): raise exception.InvalidResponse( 'response %s type is not dict' % str(obj) ) try: for key, value in obj.items(): if key in kwargs_validators: kwargs_validators[key](value, *args, **kwargs) except Exception as error: logging.exception(error) raise error def output_validates_with_args(**kwargs_validators): """Decorator to validate output. The validator can take the arguments of the decorated function as its arguments. """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): obj = func(*args, **kwargs) if isinstance(obj, list): for obj_item in obj: _output_validates_with_args( kwargs_validators, obj_item, *args, **kwargs ) else: _output_validates_with_args( kwargs_validators, obj, *args, **kwargs ) return obj return wrapper return decorator def _output_validates(kwargs_validators, obj): """Validate output. Each validator has following signature: func(value) """ if isinstance(obj, list): for item in obj: _output_validates(kwargs_validators, item) return if isinstance(obj, models.HelperMixin): obj = obj.to_dict() if not isinstance(obj, dict): raise exception.InvalidResponse( 'response %s type is not dict' % str(obj) ) try: for key, value in obj.items(): if key in kwargs_validators: kwargs_validators[key](value) except Exception as error: logging.exception(error) raise error def output_validates(**kwargs_validators): """Decorator to validate output.""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): obj = func(*args, **kwargs) if isinstance(obj, list): for obj_item in obj: _output_validates(kwargs_validators, obj_item) else: _output_validates(kwargs_validators, obj) return obj return wrapper return decorator def get_db_object(session, table, exception_when_missing=True, **kwargs): """Get db object. If not exception_when_missing and the db object can not be found, return None instead of raising exception. """ if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s get db object %s from table %s', id(session), kwargs, table.__name__) db_object = model_filter( model_query(session, table), table, **kwargs ).first() logging.debug( 'session %s got db object %s', id(session), db_object ) if db_object: return db_object if not exception_when_missing: return None raise exception.RecordNotExists( 'Cannot find the record in table %s: %s' % ( table.__name__, kwargs ) ) def add_db_object(session, table, exception_when_existing=True, *args, **kwargs): """Create db object. If not exception_when_existing and the db object exists, Instead of raising exception, updating the existing db object. """ if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s add object %s atributes %s to table %s', id(session), args, kwargs, table.__name__) argspec = inspect.getargspec(table.__init__) arg_names = argspec.args[1:] arg_defaults = argspec.defaults if not arg_defaults: arg_defaults = [] if not ( len(arg_names) - len(arg_defaults) <= len(args) <= len(arg_names) ): raise exception.InvalidParameter( 'arg names %s does not match arg values %s' % ( arg_names, args) ) db_keys = dict(zip(arg_names, args)) if db_keys: db_object = session.query(table).filter_by(**db_keys).first() else: db_object = None new_object = False if db_object: logging.debug( 'got db object %s: %s', db_keys, db_object ) if exception_when_existing: raise exception.DuplicatedRecord( '%s exists in table %s' % (db_keys, table.__name__) ) else: db_object = table(**db_keys) new_object = True for key, value in kwargs.items(): setattr(db_object, key, value) if new_object: session.add(db_object) session.flush() db_object.initialize() db_object.validate() logging.debug( 'session %s db object %s added', id(session), db_object ) return db_object def list_db_objects(session, table, order_by=[], **filters): """List db objects. If order by given, the db objects should be sorted by the ordered keys. """ if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s list db objects by filters %s in table %s', id(session), filters, table.__name__ ) db_objects = model_order_by( model_filter( model_query(session, table), table, **filters ), table, order_by ).all() logging.debug( 'session %s got listed db objects: %s', id(session), db_objects ) return db_objects def del_db_objects(session, table, **filters): """delete db objects.""" if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s delete db objects by filters %s in table %s', id(session), filters, table.__name__ ) query = model_filter( model_query(session, table), table, **filters ) db_objects = query.all() query.delete(synchronize_session=False) logging.debug( 'session %s db objects %s deleted', id(session), db_objects ) return db_objects def update_db_objects(session, table, updates={}, **filters): """Update db objects.""" if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s update db objects by filters %s in table %s', id(session), filters, table.__name__) db_objects = model_filter( model_query(session, table), table, **filters ).all() for db_object in db_objects: logging.debug('update db object %s: %s', db_object, updates) update_db_object(session, db_object, **updates) logging.debug( 'session %s db objects %s updated', id(session), db_objects ) return db_objects def update_db_object(session, db_object, **kwargs): """Update db object.""" if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s update db object %s by value %s', id(session), db_object, kwargs ) for key, value in kwargs.items(): setattr(db_object, key, value) session.flush() db_object.update() db_object.validate() logging.debug( 'session %s db object %s updated', id(session), db_object ) return db_object def del_db_object(session, db_object): """Delete db object.""" if not session: raise exception.DatabaseException('session param is None') with session.begin(subtransactions=True): logging.debug( 'session %s delete db object %s', id(session), db_object ) session.delete(db_object) logging.debug( 'session %s db object %s deleted', id(session), db_object ) return db_object def check_ip(ip): """Check ip is ip address formatted.""" try: netaddr.IPAddress(ip) except Exception as error: logging.exception(error) raise exception.InvalidParameter( 'ip address %s format uncorrect' % ip ) def check_mac(mac): """Check mac is mac address formatted.""" try: netaddr.EUI(mac) except Exception as error: logging.exception(error) raise exception.InvalidParameter( 'invalid mac address %s' % mac ) NAME_PATTERN = re.compile(r'[a-zA-Z0-9][a-zA-Z0-9_-]*') def check_name(name): """Check name meeting name format requirement.""" if not NAME_PATTERN.match(name): raise exception.InvalidParameter( 'name %s does not match the pattern %s' % ( name, NAME_PATTERN.pattern ) ) def _check_ipmi_credentials_ip(ip): check_ip(ip) def check_ipmi_credentials(ipmi_credentials): """Check ipmi credentials format is correct.""" if not ipmi_credentials: return if not isinstance(ipmi_credentials, dict): raise exception.InvalidParameter( 'invalid ipmi credentials %s' % ipmi_credentials ) for key in ipmi_credentials: if key not in ['ip', 'username', 'password']: raise exception.InvalidParameter( 'unrecognized field %s in ipmi credentials %s' % ( key, ipmi_credentials ) ) for key in ['ip', 'username', 'password']: if key not in ipmi_credentials: raise exception.InvalidParameter( 'no field %s in ipmi credentials %s' % ( key, ipmi_credentials ) ) check_ipmi_credential_field = '_check_ipmi_credentials_%s' % key this_module = globals() if check_ipmi_credential_field in this_module: this_module[check_ipmi_credential_field]( ipmi_credentials[key] ) else: logging.debug( 'function %s is not defined', check_ipmi_credential_field ) def _check_switch_credentials_version(version): if version not in ['1', '2c', '3']: raise exception.InvalidParameter( 'unknown snmp version %s' % version ) def check_switch_credentials(credentials): """Check switch credentials format is correct.""" if not credentials: return if not isinstance(credentials, dict): raise exception.InvalidParameter( 'credentials %s is not dict' % credentials ) for key in credentials: if key not in ['version', 'community']: raise exception.InvalidParameter( 'unrecognized key %s in credentials %s' % (key, credentials) ) for key in ['version', 'community']: if key not in credentials: raise exception.InvalidParameter( 'there is no %s field in credentials %s' % (key, credentials) ) key_check_func_name = '_check_switch_credentials_%s' % key this_module = globals() if key_check_func_name in this_module: this_module[key_check_func_name]( credentials[key] ) else: logging.debug( 'function %s is not defined', key_check_func_name )