Coverage for manila/api/extensions.py: 72%
172 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2011 OpenStack LLC.
2# Copyright 2011 Justin Santa Barbara
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
17import os
19from oslo_config import cfg
20from oslo_log import log
21from oslo_utils import importutils
22import webob.dec
23import webob.exc
25from manila.api.openstack import wsgi
26from manila import exception
27from manila import policy
29CONF = cfg.CONF
30LOG = log.getLogger(__name__)
33class ExtensionDescriptor(object):
34 """Base class that defines the contract for extensions.
36 Note that you don't have to derive from this class to have a valid
37 extension; it is purely a convenience.
39 """
41 # The name of the extension, e.g., 'Fox In Socks'
42 name = None
44 # The alias for the extension, e.g., 'FOXNSOX'
45 alias = None
47 # Description comes from the docstring for the class
49 # The timestamp when the extension was last updated, e.g.,
50 # '2011-01-22T13:25:27-06:00'
51 updated = None
53 def __init__(self, ext_mgr):
54 """Register extension with the extension manager."""
56 ext_mgr.register(self)
57 self.ext_mgr = ext_mgr
59 def get_resources(self):
60 """List of extensions.ResourceExtension extension objects.
62 Resources define new nouns, and are accessible through URLs.
64 """
65 resources = []
66 return resources
68 def get_controller_extensions(self):
69 """List of extensions.ControllerExtension extension objects.
71 Controller extensions are used to extend existing controllers.
72 """
73 controller_exts = []
74 return controller_exts
77class ExtensionsResource(wsgi.Resource):
79 def __init__(self, extension_manager):
80 self.extension_manager = extension_manager
81 super(ExtensionsResource, self).__init__(None)
83 def _translate(self, ext):
84 ext_data = {}
85 ext_data['name'] = ext.name
86 ext_data['alias'] = ext.alias
87 ext_data['description'] = ext.__doc__
88 ext_data['updated'] = ext.updated
89 ext_data['links'] = [] # TODO(dprince): implement extension links
90 return ext_data
92 def index(self, req):
93 extensions = []
94 for _alias, ext in self.extension_manager.extensions.items():
95 extensions.append(self._translate(ext))
96 return dict(extensions=extensions)
98 def show(self, req, id):
99 try:
100 # NOTE(dprince): the extensions alias is used as the 'id' for show
101 ext = self.extension_manager.extensions[id]
102 except KeyError:
103 raise webob.exc.HTTPNotFound()
105 return dict(extension=self._translate(ext))
107 def delete(self, req, id):
108 raise webob.exc.HTTPNotFound()
110 def create(self, req):
111 raise webob.exc.HTTPNotFound()
114class ExtensionManager(object):
115 """Load extensions from the configured extension path.
117 See manila/tests/api/extensions/foxinsocks/extension.py for an
118 example extension implementation.
120 """
122 def __init__(self):
123 LOG.info('Initializing extension manager.')
125 self.cls_list = CONF.osapi_share_extension
127 self.extensions = {}
128 self._load_extensions()
130 def register(self, ext):
131 # Do nothing if the extension doesn't check out
132 if not self._check_extension(ext): 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 return
135 alias = ext.alias
136 LOG.info('Loaded extension: %s', alias)
138 if alias in self.extensions: 138 ↛ 139line 138 didn't jump to line 139 because the condition on line 138 was never true
139 raise exception.Error("Found duplicate extension: %s" % alias)
140 self.extensions[alias] = ext
142 def get_resources(self):
143 """Returns a list of ResourceExtension objects."""
145 resources = []
146 resources.append(ResourceExtension('extensions',
147 ExtensionsResource(self)))
149 for ext in self.extensions.values():
150 try:
151 resources.extend(ext.get_resources())
152 except AttributeError:
153 # NOTE(dprince): Extension aren't required to have resource
154 # extensions
155 pass
156 return resources
158 def get_controller_extensions(self):
159 """Returns a list of ControllerExtension objects."""
160 controller_exts = []
161 for ext in self.extensions.values():
162 try:
163 get_ext_method = ext.get_controller_extensions
164 except AttributeError:
165 # NOTE(Vek): Extensions aren't required to have
166 # controller extensions
167 continue
168 controller_exts.extend(get_ext_method())
169 return controller_exts
171 def _check_extension(self, extension):
172 """Checks for required methods in extension objects."""
173 try:
174 LOG.debug('Ext name: %s', extension.name)
175 LOG.debug('Ext alias: %s', extension.alias)
176 LOG.debug('Ext description: %s',
177 ' '.join(extension.__doc__.strip().split()))
178 LOG.debug('Ext updated: %s', extension.updated)
179 except AttributeError:
180 LOG.exception("Exception loading extension.")
181 return False
183 return True
185 def load_extension(self, ext_factory):
186 """Execute an extension factory.
188 Loads an extension. The 'ext_factory' is the name of a
189 callable that will be imported and called with one
190 argument--the extension manager. The factory callable is
191 expected to call the register() method at least once.
192 """
194 LOG.debug("Loading extension %s", ext_factory)
196 # Load the factory
197 factory = importutils.import_class(ext_factory)
199 # Call it
200 LOG.debug("Calling extension factory %s", ext_factory)
201 factory(self)
203 def _load_extensions(self):
204 """Load extensions specified on the command line."""
206 extensions = list(self.cls_list)
208 # NOTE(thingee): Backwards compat for the old extension loader path.
209 # We can drop this post-grizzly in the H release.
210 old_contrib_path = ('manila.api.openstack.share.contrib.'
211 'standard_extensions')
212 new_contrib_path = 'manila.api.contrib.standard_extensions'
213 if old_contrib_path in extensions: 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true
214 LOG.warning('osapi_share_extension is set to deprecated path: '
215 '%s.',
216 old_contrib_path)
217 LOG.warning('Please set your flag or manila.conf settings for '
218 'osapi_share_extension to: %s.', new_contrib_path)
219 extensions = [e.replace(old_contrib_path, new_contrib_path)
220 for e in extensions]
222 for ext_factory in extensions:
223 try:
224 self.load_extension(ext_factory)
225 except Exception as exc:
226 LOG.warning('Failed to load extension %(ext_factory)s: '
227 '%(exc)s.',
228 {"ext_factory": ext_factory, "exc": exc})
231class ControllerExtension(object):
232 """Extend core controllers of manila OpenStack API.
234 Provide a way to extend existing manila OpenStack API core
235 controllers.
236 """
238 def __init__(self, extension, collection, controller):
239 self.extension = extension
240 self.collection = collection
241 self.controller = controller
244class ResourceExtension(object):
245 """Add top level resources to the OpenStack API in manila."""
247 def __init__(self, collection, controller, parent=None,
248 collection_actions=None, member_actions=None,
249 custom_routes_fn=None):
250 if not collection_actions: 250 ↛ 252line 250 didn't jump to line 252 because the condition on line 250 was always true
251 collection_actions = {}
252 if not member_actions: 252 ↛ 254line 252 didn't jump to line 254 because the condition on line 252 was always true
253 member_actions = {}
254 self.collection = collection
255 self.controller = controller
256 self.parent = parent
257 self.collection_actions = collection_actions
258 self.member_actions = member_actions
259 self.custom_routes_fn = custom_routes_fn
262def load_standard_extensions(ext_mgr, logger, path, package, ext_list=None):
263 """Registers all standard API extensions."""
265 # Walk through all the modules in our directory...
266 our_dir = path[0]
267 for dirpath, dirnames, filenames in os.walk(our_dir):
268 # Compute the relative package name from the dirpath
269 relpath = os.path.relpath(dirpath, our_dir)
270 if relpath == '.': 270 ↛ 273line 270 didn't jump to line 273 because the condition on line 270 was always true
271 relpkg = ''
272 else:
273 relpkg = '.%s' % '.'.join(relpath.split(os.sep))
275 # Now, consider each file in turn, only considering .py and .pyc files
276 for fname in filenames:
277 root, ext = os.path.splitext(fname)
279 # Skip __init__ and anything that's not .py and .pyc
280 if (ext not in ('.py', '.pyc')) or root == '__init__': 280 ↛ 284line 280 didn't jump to line 284 because the condition on line 280 was always true
281 continue
283 # If .pyc and .py both exist, skip .pyc
284 if ext == '.pyc' and ((root + '.py') in filenames):
285 continue
287 # Try loading it
288 classname = "%s%s" % (root[0].upper(), root[1:])
289 classpath = ("%s%s.%s.%s" %
290 (package, relpkg, root, classname))
292 if ext_list is not None and classname not in ext_list:
293 logger.debug("Skipping extension: %s" % classpath)
294 continue
296 try:
297 ext_mgr.load_extension(classpath)
298 except Exception as exc:
299 logger.warning('Failed to load extension %(classpath)s: '
300 '%(exc)s.',
301 {"classpath": classpath, "exc": exc})
303 # Now, let's consider any subdirectories we may have...
304 subdirs = []
305 for dname in dirnames: 305 ↛ 307line 305 didn't jump to line 307 because the loop on line 305 never started
306 # Skip it if it does not have __init__.py
307 if not os.path.exists(os.path.join(dirpath, dname,
308 '__init__.py')):
309 continue
311 # If it has extension(), delegate...
312 ext_name = ("%s%s.%s.extension" %
313 (package, relpkg, dname))
314 try:
315 ext = importutils.import_class(ext_name)
316 except ImportError:
317 # extension() doesn't exist on it, so we'll explore
318 # the directory for ourselves
319 subdirs.append(dname)
320 else:
321 try:
322 ext(ext_mgr)
323 except Exception as exc:
324 logger.warning('Failed to load extension '
325 '%(ext_name)s: %(exc)s.',
326 {"ext_name": ext_name, "exc": exc})
328 # Update the list of directories we'll explore...
329 dirnames[:] = subdirs
332def extension_authorizer(api_name, extension_name):
333 def authorize(context, target=None, action=None):
334 target = target or policy.default_target(context)
335 if action is None:
336 act = '%s_extension:%s' % (api_name, extension_name)
337 else:
338 act = '%s_extension:%s:%s' % (api_name, extension_name, action)
339 policy.enforce(context, act, target)
340 return authorize