bc87e2f8a7
Ie214f0fc9467171b2feb17b2356e834237786bb7 Iaba3df12b2ff0f415ee149e7173529ef2ad31b15 Change-Id: I969361b7a13f57db72ad08f68d98b2730fd0840b
223 lines
6.8 KiB
Python
223 lines
6.8 KiB
Python
# Copyright (c) 2013 Mirantis, Inc.
|
|
#
|
|
# 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.
|
|
|
|
from collections import deque
|
|
from functools import wraps
|
|
|
|
import eventlet
|
|
from jsonschema import validate
|
|
import types
|
|
from muranoapi.openstack.common import log as logging
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class TraverseHelper(object):
|
|
value_type = (types.StringTypes, types.IntType, types.FloatType,
|
|
types.BooleanType)
|
|
|
|
@staticmethod
|
|
def get(path, source):
|
|
"""
|
|
Provides the ability to traverse a data source made up of any
|
|
combination of lists and dicts. Has simple rules for selecting item of
|
|
the list:
|
|
|
|
* each item should have id property
|
|
* to select item from the list, specify id value
|
|
|
|
Examples:
|
|
source = {'obj': {'attr': True}}
|
|
value = TraverseHelper.get('/obj/attr', source)
|
|
|
|
source = {'obj': [
|
|
{'id': '1', 'value': 1},
|
|
{'id': '2s', 'value': 2},
|
|
]}
|
|
value = TraverseHelper.get('/obj/2s/value', source)
|
|
|
|
|
|
:param path: string with path to desired value
|
|
:param source: python object (list or dict)
|
|
:return: object
|
|
:raise: ValueError if object is malformed
|
|
"""
|
|
queue = deque(filter(lambda x: x, path.split('/')))
|
|
|
|
while len(queue):
|
|
path = queue.popleft()
|
|
|
|
if isinstance(source, types.ListType):
|
|
idx_source = source
|
|
source = next((i for i in source if i['id'] == path), None)
|
|
if source is None and path.isdigit():
|
|
source = idx_source[int(path)]
|
|
elif isinstance(source, types.DictionaryType):
|
|
source = source[path]
|
|
elif isinstance(source, TraverseHelper.value_type):
|
|
break
|
|
else:
|
|
raise ValueError('Source object or path is malformed')
|
|
|
|
return source
|
|
|
|
@staticmethod
|
|
def update(path, value, source):
|
|
"""
|
|
Updates value selected with specified path.
|
|
|
|
Warning: Root object could not be updated
|
|
|
|
:param path: string with path to desired value
|
|
:param value: value
|
|
:param source: python object (list or dict)
|
|
"""
|
|
parent_path = '/'.join(path.split('/')[:-1])
|
|
node = TraverseHelper.get(parent_path, source)
|
|
key = path[1:].split('/')[-1]
|
|
node[key] = value
|
|
|
|
@staticmethod
|
|
def insert(path, value, source):
|
|
"""
|
|
Inserts new item to selected list.
|
|
|
|
:param path: string with path to desired value
|
|
:param value: value
|
|
:param source: List
|
|
"""
|
|
node = TraverseHelper.get(path, source)
|
|
node.append(value)
|
|
|
|
@staticmethod
|
|
def extend(path, value, source):
|
|
"""
|
|
Extend list by appending elements from the iterable.
|
|
|
|
:param path: string with path to desired value
|
|
:param value: value
|
|
:param source: List
|
|
"""
|
|
node = TraverseHelper.get(path, source)
|
|
node.extend(value)
|
|
|
|
@staticmethod
|
|
def remove(path, source):
|
|
"""
|
|
Removes selected item from source.
|
|
|
|
:param path: string with path to desired value
|
|
:param source: python object (list or dict)
|
|
"""
|
|
parent_path = '/'.join(path.split('/')[:-1])
|
|
node = TraverseHelper.get(parent_path, source)
|
|
key = path[1:].split('/')[-1]
|
|
|
|
if isinstance(node, types.ListType):
|
|
item = next((i for i in node if i['id'] == key), None)
|
|
if item is None and key.isdigit():
|
|
del node[int(key)]
|
|
else:
|
|
node.remove(item)
|
|
elif isinstance(node, types.DictionaryType):
|
|
del node[key]
|
|
else:
|
|
raise ValueError('Source object or path is malformed')
|
|
|
|
|
|
def build_entity_map(value):
|
|
def build_entity_map_recursive(value, id_map):
|
|
if isinstance(value, types.DictionaryType):
|
|
if 'id' in value:
|
|
id_map[value['id']] = value
|
|
for k, v in value.iteritems():
|
|
build_entity_map_recursive(v, id_map)
|
|
if isinstance(value, types.ListType):
|
|
for item in value:
|
|
build_entity_map_recursive(item, id_map)
|
|
|
|
id_map = {}
|
|
build_entity_map_recursive(value, id_map)
|
|
return id_map
|
|
|
|
|
|
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2):
|
|
"""Retry calling the decorated function using an exponential backoff.
|
|
|
|
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
|
|
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
|
|
|
|
:param ExceptionToCheck: the exception to check. may be a tuple of
|
|
exceptions to check
|
|
:type ExceptionToCheck: Exception or tuple
|
|
:param tries: number of times to try (not retry) before giving up
|
|
:type tries: int
|
|
:param delay: initial delay between retries in seconds
|
|
:type delay: int
|
|
:param backoff: backoff multiplier e.g. value of 2 will double the delay
|
|
each retry
|
|
:type backoff: int
|
|
"""
|
|
|
|
def deco_retry(f):
|
|
@wraps(f)
|
|
def f_retry(*args, **kwargs):
|
|
mtries, mdelay = tries, delay
|
|
forever = mtries == -1
|
|
while forever or mtries > 1:
|
|
try:
|
|
return f(*args, **kwargs)
|
|
except ExceptionToCheck as e:
|
|
|
|
log.exception(e)
|
|
log.info("Retrying in {0} seconds...".format(mdelay))
|
|
|
|
eventlet.sleep(mdelay)
|
|
|
|
if not forever:
|
|
mtries -= 1
|
|
|
|
if mdelay < 60:
|
|
mdelay *= backoff
|
|
return f(*args, **kwargs)
|
|
|
|
return f_retry
|
|
|
|
return deco_retry
|
|
|
|
|
|
def handle(f):
|
|
"""Handles exception in wrapped function and writes to log."""
|
|
|
|
@wraps(f)
|
|
def f_handle(*args, **kwargs):
|
|
try:
|
|
return f(*args, **kwargs)
|
|
except Exception as e:
|
|
log.exception(e)
|
|
|
|
return f_handle
|
|
|
|
|
|
def validate_body(schema):
|
|
def deco_validate_body(f):
|
|
@wraps(f)
|
|
def f_validate_body(*args, **kwargs):
|
|
if 'body' in kwargs:
|
|
validate(kwargs['body'], schema)
|
|
return f(*args, **kwargs)
|
|
return f_validate_body
|
|
return deco_validate_body
|