Files
manila/manila/api/extensions.py
T
Goutham Pacha Ravi 8553962997 Clean up some policy code
oslo policy handles the mapping of
credentials from a context object to values
that oslo policy cares about. This mapping
includes some deprecations and compatibility
handling code that we must take advantage of [1].
So, stop mapping context to policy values
on our end, and rely on oslo.policy handling
this for us.

enforce and authorize methods in policy.py
do the same thing, but with a subtle
difference. The "enforce" method doesn't
raise errors when unregistered policies are
invoked - this shouldn't ever be the case
for any policies written/maintained within
manila - however, we support API extensions
and don't dictate what must be done there. So
add docstrings to clarify that we shouldn't
invoke enforce, ever.

Also handle InvalidPolicyScope exceptions
and raise the oslo.policy library version
since some test enhancements have been
committed in the latest version.

[1] d3185debdb/oslo_policy/policy.py (L1077-L1096)

Change-Id: I069bf7143d6ff66b3dcdc34c9b52d48f5808481b
Signed-off-by: Goutham Pacha Ravi <gouthampravi@gmail.com>
2021-02-25 23:41:57 -08:00

341 lines
11 KiB
Python

# Copyright 2011 OpenStack LLC.
# Copyright 2011 Justin Santa Barbara
# 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 os
from oslo_config import cfg
from oslo_log import log
from oslo_utils import importutils
import webob.dec
import webob.exc
from manila.api.openstack import wsgi
from manila import exception
from manila import policy
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class ExtensionDescriptor(object):
"""Base class that defines the contract for extensions.
Note that you don't have to derive from this class to have a valid
extension; it is purely a convenience.
"""
# The name of the extension, e.g., 'Fox In Socks'
name = None
# The alias for the extension, e.g., 'FOXNSOX'
alias = None
# Description comes from the docstring for the class
# The timestamp when the extension was last updated, e.g.,
# '2011-01-22T13:25:27-06:00'
updated = None
def __init__(self, ext_mgr):
"""Register extension with the extension manager."""
ext_mgr.register(self)
self.ext_mgr = ext_mgr
def get_resources(self):
"""List of extensions.ResourceExtension extension objects.
Resources define new nouns, and are accessible through URLs.
"""
resources = []
return resources
def get_controller_extensions(self):
"""List of extensions.ControllerExtension extension objects.
Controller extensions are used to extend existing controllers.
"""
controller_exts = []
return controller_exts
class ExtensionsResource(wsgi.Resource):
def __init__(self, extension_manager):
self.extension_manager = extension_manager
super(ExtensionsResource, self).__init__(None)
def _translate(self, ext):
ext_data = {}
ext_data['name'] = ext.name
ext_data['alias'] = ext.alias
ext_data['description'] = ext.__doc__
ext_data['updated'] = ext.updated
ext_data['links'] = [] # TODO(dprince): implement extension links
return ext_data
def index(self, req):
extensions = []
for _alias, ext in self.extension_manager.extensions.items():
extensions.append(self._translate(ext))
return dict(extensions=extensions)
def show(self, req, id):
try:
# NOTE(dprince): the extensions alias is used as the 'id' for show
ext = self.extension_manager.extensions[id]
except KeyError:
raise webob.exc.HTTPNotFound()
return dict(extension=self._translate(ext))
def delete(self, req, id):
raise webob.exc.HTTPNotFound()
def create(self, req):
raise webob.exc.HTTPNotFound()
class ExtensionManager(object):
"""Load extensions from the configured extension path.
See manila/tests/api/extensions/foxinsocks/extension.py for an
example extension implementation.
"""
def __init__(self):
LOG.info('Initializing extension manager.')
self.cls_list = CONF.osapi_share_extension
self.extensions = {}
self._load_extensions()
def register(self, ext):
# Do nothing if the extension doesn't check out
if not self._check_extension(ext):
return
alias = ext.alias
LOG.info('Loaded extension: %s', alias)
if alias in self.extensions:
raise exception.Error("Found duplicate extension: %s" % alias)
self.extensions[alias] = ext
def get_resources(self):
"""Returns a list of ResourceExtension objects."""
resources = []
resources.append(ResourceExtension('extensions',
ExtensionsResource(self)))
for ext in self.extensions.values():
try:
resources.extend(ext.get_resources())
except AttributeError:
# NOTE(dprince): Extension aren't required to have resource
# extensions
pass
return resources
def get_controller_extensions(self):
"""Returns a list of ControllerExtension objects."""
controller_exts = []
for ext in self.extensions.values():
try:
get_ext_method = ext.get_controller_extensions
except AttributeError:
# NOTE(Vek): Extensions aren't required to have
# controller extensions
continue
controller_exts.extend(get_ext_method())
return controller_exts
def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
try:
LOG.debug('Ext name: %s', extension.name)
LOG.debug('Ext alias: %s', extension.alias)
LOG.debug('Ext description: %s',
' '.join(extension.__doc__.strip().split()))
LOG.debug('Ext updated: %s', extension.updated)
except AttributeError:
LOG.exception("Exception loading extension.")
return False
return True
def load_extension(self, ext_factory):
"""Execute an extension factory.
Loads an extension. The 'ext_factory' is the name of a
callable that will be imported and called with one
argument--the extension manager. The factory callable is
expected to call the register() method at least once.
"""
LOG.debug("Loading extension %s", ext_factory)
# Load the factory
factory = importutils.import_class(ext_factory)
# Call it
LOG.debug("Calling extension factory %s", ext_factory)
factory(self)
def _load_extensions(self):
"""Load extensions specified on the command line."""
extensions = list(self.cls_list)
# NOTE(thingee): Backwards compat for the old extension loader path.
# We can drop this post-grizzly in the H release.
old_contrib_path = ('manila.api.openstack.share.contrib.'
'standard_extensions')
new_contrib_path = 'manila.api.contrib.standard_extensions'
if old_contrib_path in extensions:
LOG.warning('osapi_share_extension is set to deprecated path: '
'%s.',
old_contrib_path)
LOG.warning('Please set your flag or manila.conf settings for '
'osapi_share_extension to: %s.', new_contrib_path)
extensions = [e.replace(old_contrib_path, new_contrib_path)
for e in extensions]
for ext_factory in extensions:
try:
self.load_extension(ext_factory)
except Exception as exc:
LOG.warning('Failed to load extension %(ext_factory)s: '
'%(exc)s.',
{"ext_factory": ext_factory, "exc": exc})
class ControllerExtension(object):
"""Extend core controllers of manila OpenStack API.
Provide a way to extend existing manila OpenStack API core
controllers.
"""
def __init__(self, extension, collection, controller):
self.extension = extension
self.collection = collection
self.controller = controller
class ResourceExtension(object):
"""Add top level resources to the OpenStack API in manila."""
def __init__(self, collection, controller, parent=None,
collection_actions=None, member_actions=None,
custom_routes_fn=None):
if not collection_actions:
collection_actions = {}
if not member_actions:
member_actions = {}
self.collection = collection
self.controller = controller
self.parent = parent
self.collection_actions = collection_actions
self.member_actions = member_actions
self.custom_routes_fn = custom_routes_fn
def load_standard_extensions(ext_mgr, logger, path, package, ext_list=None):
"""Registers all standard API extensions."""
# Walk through all the modules in our directory...
our_dir = path[0]
for dirpath, dirnames, filenames in os.walk(our_dir):
# Compute the relative package name from the dirpath
relpath = os.path.relpath(dirpath, our_dir)
if relpath == '.':
relpkg = ''
else:
relpkg = '.%s' % '.'.join(relpath.split(os.sep))
# Now, consider each file in turn, only considering .py and .pyc files
for fname in filenames:
root, ext = os.path.splitext(fname)
# Skip __init__ and anything that's not .py and .pyc
if (ext not in ('.py', '.pyc')) or root == '__init__':
continue
# If .pyc and .py both exist, skip .pyc
if ext == '.pyc' and ((root + '.py') in filenames):
continue
# Try loading it
classname = "%s%s" % (root[0].upper(), root[1:])
classpath = ("%s%s.%s.%s" %
(package, relpkg, root, classname))
if ext_list is not None and classname not in ext_list:
logger.debug("Skipping extension: %s" % classpath)
continue
try:
ext_mgr.load_extension(classpath)
except Exception as exc:
logger.warning('Failed to load extension %(classpath)s: '
'%(exc)s.',
{"classpath": classpath, "exc": exc})
# Now, let's consider any subdirectories we may have...
subdirs = []
for dname in dirnames:
# Skip it if it does not have __init__.py
if not os.path.exists(os.path.join(dirpath, dname,
'__init__.py')):
continue
# If it has extension(), delegate...
ext_name = ("%s%s.%s.extension" %
(package, relpkg, dname))
try:
ext = importutils.import_class(ext_name)
except ImportError:
# extension() doesn't exist on it, so we'll explore
# the directory for ourselves
subdirs.append(dname)
else:
try:
ext(ext_mgr)
except Exception as exc:
logger.warning('Failed to load extension '
'%(ext_name)s: %(exc)s.',
{"ext_name": ext_name, "exc": exc})
# Update the list of directories we'll explore...
dirnames[:] = subdirs
def extension_authorizer(api_name, extension_name):
def authorize(context, target=None, action=None):
target = target or policy.default_target(context)
if action is None:
act = '%s_extension:%s' % (api_name, extension_name)
else:
act = '%s_extension:%s:%s' % (api_name, extension_name, action)
policy.enforce(context, act, target)
return authorize