glance/glance/common/artifacts/loader.py

191 lines
7.5 KiB
Python

# Copyright 2011-2012 OpenStack Foundation
# All Rights Reserved.
#
# 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
from oslo_config import cfg
import semantic_version
from stevedore import enabled
from glance.common.artifacts import definitions
from glance.common import exception
from glance.i18n import _, _LE, _LI, _LW
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
plugins_opts = [
cfg.BoolOpt('load_enabled', default=True,
help=_('When false, no artifacts can be loaded regardless of'
' available_plugins. When true, artifacts can be'
' loaded.')),
cfg.ListOpt('available_plugins', default=[],
help=_('A list of artifacts that are allowed in the'
' format name or name-version. Empty list means that'
' any artifact can be loaded.'))
]
CONF = cfg.CONF
CONF.register_opts(plugins_opts)
class ArtifactsPluginLoader(object):
def __init__(self, namespace):
self.mgr = enabled.EnabledExtensionManager(
check_func=self._gen_check_func(),
namespace=namespace,
propagate_map_exceptions=True,
on_load_failure_callback=self._on_load_failure)
self.plugin_map = {'by_typename': {},
'by_endpoint': {}}
def _add_extension(ext):
"""
Plugins can be loaded as entry_point=single plugin and
entry_point=PLUGIN_LIST, where PLUGIN_LIST is a python variable
holding a list of plugins
"""
def _load_one(plugin):
if issubclass(plugin, definitions.ArtifactType):
# make sure that have correct plugin name
art_name = plugin.metadata.type_name
if art_name != ext.name:
raise exception.ArtifactNonMatchingTypeName(
name=art_name, plugin=ext.name)
# make sure that no plugin with the same name and version
# already exists
exists = self._get_plugins(ext.name)
new_tv = plugin.metadata.type_version
if any(e.metadata.type_version == new_tv for e in exists):
raise exception.ArtifactDuplicateNameTypeVersion()
self._add_plugin("by_endpoint", plugin.metadata.endpoint,
plugin)
self._add_plugin("by_typename", plugin.metadata.type_name,
plugin)
if isinstance(ext.plugin, list):
for p in ext.plugin:
_load_one(p)
else:
_load_one(ext.plugin)
# (ivasilevskaya) that looks pretty bad as RuntimeError is too general,
# but stevedore has awful exception wrapping with no specific class
# for this very case (no extensions for given namespace found)
try:
self.mgr.map(_add_extension)
except RuntimeError as re:
LOG.error(_LE("Unable to load artifacts: %s") % re.message)
def _version(self, artifact):
return semantic_version.Version.coerce(artifact.metadata.type_version)
def _add_plugin(self, spec, name, plugin):
"""
Inserts a new plugin into a sorted by desc type_version list
of existing plugins in order to retrieve the latest by next()
"""
def _add(name, value):
self.plugin_map[spec][name] = value
old_order = copy.copy(self._get_plugins(name, spec=spec))
for i, p in enumerate(old_order):
if self._version(p) < self._version(plugin):
_add(name, old_order[0:i] + [plugin] + old_order[i:])
return
_add(name, old_order + [plugin])
def _get_plugins(self, name, spec="by_typename"):
if spec not in self.plugin_map.keys():
return []
return self.plugin_map[spec].get(name, [])
def _gen_check_func(self):
"""generates check_func for EnabledExtensionManager"""
def _all_forbidden(ext):
LOG.warn(_LW("Can't load artifact %s: load disabled in config") %
ext.name)
raise exception.ArtifactLoadError(name=ext.name)
def _all_allowed(ext):
LOG.info(
_LI("Artifact %s has been successfully loaded"), ext.name)
return True
if not CONF.load_enabled:
return _all_forbidden
if len(CONF.available_plugins) == 0:
return _all_allowed
available = []
for name in CONF.available_plugins:
type_name, version = (name.split('-', 1)
if '-' in name else (name, None))
available.append((type_name, version))
def _check_ext(ext):
try:
next(n for n, v in available
if n == ext.plugin.metadata.type_name and
(v is None or v == ext.plugin.metadata.type_version))
except StopIteration:
LOG.warn(_LW("Can't load artifact %s: not in"
" available_plugins list") % ext.name)
raise exception.ArtifactLoadError(name=ext.name)
LOG.info(
_LI("Artifact %s has been successfully loaded"), ext.name)
return True
return _check_ext
# this has to be done explicitly as stevedore is pretty ignorant when
# face to face with an Exception and tries to swallow it and print sth
# irrelevant instead of expected error message
def _on_load_failure(self, manager, ep, exc):
msg = (_LE("Could not load plugin from %(module)s: %(msg)s") %
{"module": ep.module_name, "msg": exc})
LOG.error(msg)
raise exc
def _find_class_in_collection(self, collection, name, version=None):
try:
def _cmp_version(plugin, version):
ver = semantic_version.Version.coerce
return (ver(plugin.metadata.type_version) ==
ver(version))
if version:
return next((p for p in collection
if _cmp_version(p, version)))
return next((p for p in collection))
except StopIteration:
raise exception.ArtifactPluginNotFound(
name="%s %s" % (name, "v %s" % version if version else ""))
def get_class_by_endpoint(self, name, version=None):
if version is None:
classlist = self._get_plugins(name, spec="by_endpoint")
if not classlist:
raise exception.ArtifactPluginNotFound(name=name)
return self._find_class_in_collection(classlist, name)
return self._find_class_in_collection(
self._get_plugins(name, spec="by_endpoint"), name, version)
def get_class_by_typename(self, name, version=None):
return self._find_class_in_collection(
self._get_plugins(name, spec="by_typename"), name, version)