
There were a lot of instances of exceptions being raised or logged incorrectly. When reraising an exception, "raise" should be called, not "raise ex". The second form will cause the original traceback to be modified to the new location. LOG.exception is the same as LOG.error with the additional behavior of logging any exception that is in the current scope. Therefore, doing something like "LOG.exception(ex)" is redundant and causes the exception message to be logged twice. Logging should be done with some sort of textual message without the exception object. Change-Id: I149c8fe7ef4b6628f910943587ab4302cc371441 Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
241 lines
7.9 KiB
Python
241 lines
7.9 KiB
Python
# Copyright (c) 2013 Rackspace, 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.
|
|
|
|
import copy
|
|
import hashlib
|
|
import json
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
from osprofiler import profiler
|
|
import six
|
|
from stevedore import driver
|
|
|
|
from zaqar.common import errors
|
|
from zaqar.common import utils
|
|
from zaqar.storage import configuration
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def dynamic_conf(uri, options, conf=None):
|
|
"""Given metadata, yields a dynamic configuration.
|
|
|
|
:param uri: pool location
|
|
:type uri: six.text_type
|
|
:param options: additional pool metadata
|
|
:type options: dict
|
|
:param conf: Optional conf object to copy
|
|
:type conf: `oslo_config.cfg.ConfigOpts`
|
|
:returns: Configuration object suitable for constructing storage
|
|
drivers
|
|
:rtype: oslo_config.cfg.ConfigOpts
|
|
"""
|
|
storage_type = six.moves.urllib_parse.urlparse(uri).scheme
|
|
|
|
# NOTE(cpp-cabrera): parse storage-specific opts:
|
|
# 'drivers:storage:{type}'
|
|
options['uri'] = uri
|
|
storage_opts = utils.dict_to_conf(options)
|
|
storage_group = u'drivers:message_store:%s' % storage_type
|
|
|
|
# NOTE(cpp-cabrera): register those options!
|
|
if conf is None:
|
|
conf = cfg.ConfigOpts()
|
|
else:
|
|
conf_wrap = configuration.Configuration(conf)
|
|
conf = copy.copy(conf_wrap)
|
|
|
|
if storage_group not in conf:
|
|
conf.register_opts(storage_opts, group=storage_group)
|
|
|
|
if 'drivers' not in conf:
|
|
# NOTE(cpp-cabrera): parse general opts: 'drivers'
|
|
driver_opts = utils.dict_to_conf({'message_store': storage_type})
|
|
conf.register_opts(driver_opts, group=u'drivers')
|
|
|
|
conf.set_override('message_store', storage_type, 'drivers')
|
|
|
|
for opt in options:
|
|
if opt in conf[storage_group]:
|
|
conf.set_override(opt, options[opt], group=storage_group)
|
|
return conf
|
|
|
|
|
|
def load_storage_impl(uri, control_mode=False, default_store=None):
|
|
"""Loads a storage driver implementation and returns it.
|
|
|
|
:param uri: The connection uri to parse and load a driver for.
|
|
:param control_mode: (Default False). Determines which
|
|
driver type to load; if False, the data driver is
|
|
loaded. If True, the control driver is loaded.
|
|
:param default_store: The default store to load if no scheme
|
|
is parsed.
|
|
"""
|
|
|
|
mode = 'control' if control_mode else 'data'
|
|
driver_type = 'zaqar.{0}.storage'.format(mode)
|
|
storage_type = six.moves.urllib_parse.urlparse(uri).scheme or default_store
|
|
|
|
try:
|
|
mgr = driver.DriverManager(driver_type, storage_type,
|
|
invoke_on_load=False)
|
|
|
|
return mgr.driver
|
|
|
|
except Exception as exc:
|
|
LOG.exception('Error loading storage driver')
|
|
raise errors.InvalidDriver(exc)
|
|
|
|
|
|
def load_storage_driver(conf, cache, storage_type=None,
|
|
control_mode=False, control_driver=None):
|
|
"""Loads a storage driver and returns it.
|
|
|
|
The driver's initializer will be passed conf and cache as
|
|
its positional args.
|
|
|
|
:param conf: Configuration instance to use for loading the
|
|
driver. Must include a 'drivers' group.
|
|
:param cache: Cache instance that the driver can (optionally)
|
|
use to reduce latency for some operations.
|
|
:param storage_type: The storage_type to load. If None, then
|
|
the `drivers` option will be used.
|
|
:param control_mode: (Default False). Determines which
|
|
driver type to load; if False, the data driver is
|
|
loaded. If True, the control driver is loaded.
|
|
:param control_driver: (Default None). The control driver
|
|
instance to pass to the storage driver. Needed to access
|
|
the queue controller, mainly.
|
|
"""
|
|
if control_mode:
|
|
mode = 'control'
|
|
storage_type = storage_type or conf['drivers'].management_store
|
|
else:
|
|
mode = 'data'
|
|
storage_type = storage_type or conf['drivers'].message_store
|
|
|
|
driver_type = 'zaqar.{0}.storage'.format(mode)
|
|
|
|
_invoke_args = (conf, cache)
|
|
if control_driver is not None:
|
|
_invoke_args = (conf, cache, control_driver)
|
|
|
|
try:
|
|
mgr = driver.DriverManager(driver_type,
|
|
storage_type,
|
|
invoke_on_load=True,
|
|
invoke_args=_invoke_args)
|
|
|
|
if conf.profiler.enabled:
|
|
if ((mode == "control" and conf.profiler.trace_management_store) or
|
|
(mode == "data" and conf.profiler.trace_message_store)):
|
|
trace_name = '{0}_{1}_driver'.format(storage_type, mode)
|
|
return profiler.trace_cls(trace_name,
|
|
trace_private=True)(mgr.driver)
|
|
else:
|
|
return mgr.driver
|
|
|
|
except Exception as exc:
|
|
LOG.exception('Failed to load "%s" driver for "%s"',
|
|
driver_type, storage_type)
|
|
raise errors.InvalidDriver(exc)
|
|
|
|
|
|
def keyify(key, iterable):
|
|
"""Make an iterator from an iterable of dicts compared with a key.
|
|
|
|
:param key: A key exists for all dict inside the iterable object
|
|
:param iterable: The input iterable object
|
|
"""
|
|
|
|
class Keyed(object):
|
|
def __init__(self, obj):
|
|
self.obj = obj
|
|
|
|
def __eq__(self, other):
|
|
return self.obj[key] == other.obj[key]
|
|
|
|
def __ne__(self, other):
|
|
return self.obj[key] != other.obj[key]
|
|
|
|
def __lt__(self, other):
|
|
return self.obj[key] < other.obj[key]
|
|
|
|
def __le__(self, other):
|
|
return self.obj[key] <= other.obj[key]
|
|
|
|
def __gt__(self, other):
|
|
return self.obj[key] > other.obj[key]
|
|
|
|
def __ge__(self, other):
|
|
return self.obj[key] >= other.obj[key]
|
|
|
|
for item in iterable:
|
|
yield Keyed(item)
|
|
|
|
|
|
def can_connect(uri, conf=None):
|
|
"""Given a URI, verifies whether it's possible to connect to it.
|
|
|
|
:param uri: connection string to a storage endpoint
|
|
:type uri: six.text_type
|
|
:returns: True if can connect else False
|
|
:rtype: bool
|
|
"""
|
|
# NOTE(cabrera): create a mock configuration containing only
|
|
# the URI field. This should be sufficient to initialize a
|
|
# storage driver.
|
|
conf = dynamic_conf(uri, {}, conf=conf)
|
|
storage_type = six.moves.urllib_parse.urlparse(uri).scheme
|
|
|
|
try:
|
|
ctrl = load_storage_driver(conf, None,
|
|
storage_type=conf.drivers.management_store,
|
|
control_mode=True)
|
|
driver = load_storage_driver(conf, None,
|
|
storage_type=storage_type,
|
|
control_driver=ctrl)
|
|
return driver.is_alive()
|
|
except Exception as exc:
|
|
LOG.debug('Can\'t connect to: %s \n%s', (uri, exc))
|
|
return False
|
|
|
|
|
|
def get_checksum(body, algorithm='MD5'):
|
|
"""According to the algorithm to get the message body checksum.
|
|
|
|
:param body: The message body.
|
|
:type body: six.text_type
|
|
:param algorithm: The algorithm type, default is MD5.
|
|
:type algorithm: six.text_type
|
|
:returns: The message body checksum.
|
|
:rtype: six.text_type
|
|
"""
|
|
|
|
checksum = '%s:' % algorithm
|
|
|
|
if body is None:
|
|
return ''
|
|
else:
|
|
checksum_body = json.dumps(body).encode('utf-8')
|
|
# TODO(yangzhenyu): We may support other algorithms in future
|
|
# versions, including SHA1, SHA256, SHA512, and so on.
|
|
if algorithm == 'MD5':
|
|
md5 = hashlib.md5()
|
|
md5.update(checksum_body)
|
|
checksum += md5.hexdigest()
|
|
|
|
return checksum
|