~zulcss/ubuntu/precise/quantum/trunk

« back to all changes in this revision

Viewing changes to quantum/extensions/extensions.py

  • Committer: Chuck Short
  • Date: 2012-11-26 19:51:11 UTC
  • mfrom: (26.1.1 raring-proposed)
  • Revision ID: zulcss@ubuntu.com-20121126195111-jnz2cr4xi6whemw2
* New upstream release for the Ubuntu Cloud Archive.
* debian/patches/*: Refreshed for opening of Grizzly.
* New upstream release.
* debian/rules: FTFBS if there is missing binaries.
* debian/quantum-server.install: Add quantum-debug.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
 
 
4
 
# Copyright 2011 OpenStack LLC.
5
 
# Copyright 2011 Justin Santa Barbara
6
 
# All Rights Reserved.
7
 
#
8
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
9
 
#    not use this file except in compliance with the License. You may obtain
10
 
#    a copy of the License at
11
 
#
12
 
#         http://www.apache.org/licenses/LICENSE-2.0
13
 
#
14
 
#    Unless required by applicable law or agreed to in writing, software
15
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
 
#    License for the specific language governing permissions and limitations
18
 
#    under the License.
19
 
 
20
 
from abc import ABCMeta
21
 
import imp
22
 
import logging
23
 
import os
24
 
 
25
 
import routes
26
 
import webob.dec
27
 
import webob.exc
28
 
 
29
 
from quantum.common import exceptions
30
 
import quantum.extensions
31
 
from quantum.manager import QuantumManager
32
 
from quantum.openstack.common import cfg
33
 
from quantum.openstack.common import importutils
34
 
from quantum import wsgi
35
 
 
36
 
 
37
 
LOG = logging.getLogger('quantum.api.extensions')
38
 
 
39
 
# Besides the supported_extension_aliases in plugin class,
40
 
# we also support register enabled extensions here so that we
41
 
# can load some mandatory files (such as db models) before initialize plugin
42
 
ENABLED_EXTS = {
43
 
    'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2':
44
 
    {
45
 
        'ext_alias': ["quotas"],
46
 
        'ext_db_models': ['quantum.extensions._quotav2_model.Quota'],
47
 
    },
48
 
    'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2':
49
 
    {
50
 
        'ext_alias': ["quotas"],
51
 
        'ext_db_models': ['quantum.extensions._quotav2_model.Quota'],
52
 
    },
53
 
}
54
 
 
55
 
 
56
 
class PluginInterface(object):
57
 
    __metaclass__ = ABCMeta
58
 
 
59
 
    @classmethod
60
 
    def __subclasshook__(cls, klass):
61
 
        """
62
 
        The __subclasshook__ method is a class method
63
 
        that will be called everytime a class is tested
64
 
        using issubclass(klass, PluginInterface).
65
 
        In that case, it will check that every method
66
 
        marked with the abstractmethod decorator is
67
 
        provided by the plugin class.
68
 
        """
69
 
        for method in cls.__abstractmethods__:
70
 
            if any(method in base.__dict__ for base in klass.__mro__):
71
 
                continue
72
 
            return NotImplemented
73
 
        return True
74
 
 
75
 
 
76
 
class ExtensionDescriptor(object):
77
 
    """Base class that defines the contract for extensions.
78
 
 
79
 
    Note that you don't have to derive from this class to have a valid
80
 
    extension; it is purely a convenience.
81
 
 
82
 
    """
83
 
 
84
 
    def get_name(self):
85
 
        """The name of the extension.
86
 
 
87
 
        e.g. 'Fox In Socks'
88
 
 
89
 
        """
90
 
        raise NotImplementedError()
91
 
 
92
 
    def get_alias(self):
93
 
        """The alias for the extension.
94
 
 
95
 
        e.g. 'FOXNSOX'
96
 
 
97
 
        """
98
 
        raise NotImplementedError()
99
 
 
100
 
    def get_description(self):
101
 
        """Friendly description for the extension.
102
 
 
103
 
        e.g. 'The Fox In Socks Extension'
104
 
 
105
 
        """
106
 
        raise NotImplementedError()
107
 
 
108
 
    def get_namespace(self):
109
 
        """The XML namespace for the extension.
110
 
 
111
 
        e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'
112
 
 
113
 
        """
114
 
        raise NotImplementedError()
115
 
 
116
 
    def get_updated(self):
117
 
        """The timestamp when the extension was last updated.
118
 
 
119
 
        e.g. '2011-01-22T13:25:27-06:00'
120
 
 
121
 
        """
122
 
        # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS
123
 
        raise NotImplementedError()
124
 
 
125
 
    def get_resources(self):
126
 
        """List of extensions.ResourceExtension extension objects.
127
 
 
128
 
        Resources define new nouns, and are accessible through URLs.
129
 
 
130
 
        """
131
 
        resources = []
132
 
        return resources
133
 
 
134
 
    def get_actions(self):
135
 
        """List of extensions.ActionExtension extension objects.
136
 
 
137
 
        Actions are verbs callable from the API.
138
 
 
139
 
        """
140
 
        actions = []
141
 
        return actions
142
 
 
143
 
    def get_request_extensions(self):
144
 
        """List of extensions.RequestException extension objects.
145
 
 
146
 
        Request extensions are used to handle custom request data.
147
 
 
148
 
        """
149
 
        request_exts = []
150
 
        return request_exts
151
 
 
152
 
    def get_extended_resources(self, version):
153
 
        """retrieve extended resources or attributes for core resources.
154
 
 
155
 
        Extended attributes are implemented by a core plugin similarly
156
 
        to the attributes defined in the core, and can appear in
157
 
        request and response messages. Their names are scoped with the
158
 
        extension's prefix. The core API version is passed to this
159
 
        function, which must return a
160
 
        map[<resource_name>][<attribute_name>][<attribute_property>]
161
 
        specifying the extended resource attribute properties required
162
 
        by that API version.
163
 
 
164
 
        Extension can add resources and their attr definitions too.
165
 
        The returned map can be integrated into RESOURCE_ATTRIBUTE_MAP.
166
 
        """
167
 
        return {}
168
 
 
169
 
    def get_plugin_interface(self):
170
 
        """
171
 
        Returns an abstract class which defines contract for the plugin.
172
 
        The abstract class should inherit from extesnions.PluginInterface,
173
 
        Methods in this abstract class  should be decorated as abstractmethod
174
 
        """
175
 
        return None
176
 
 
177
 
 
178
 
class ActionExtensionController(wsgi.Controller):
179
 
 
180
 
    def __init__(self, application):
181
 
 
182
 
        self.application = application
183
 
        self.action_handlers = {}
184
 
 
185
 
    def add_action(self, action_name, handler):
186
 
        self.action_handlers[action_name] = handler
187
 
 
188
 
    def action(self, request, id):
189
 
 
190
 
        input_dict = self._deserialize(request.body,
191
 
                                       request.get_content_type())
192
 
        for action_name, handler in self.action_handlers.iteritems():
193
 
            if action_name in input_dict:
194
 
                return handler(input_dict, request, id)
195
 
        # no action handler found (bump to downstream application)
196
 
        response = self.application
197
 
        return response
198
 
 
199
 
 
200
 
class RequestExtensionController(wsgi.Controller):
201
 
 
202
 
    def __init__(self, application):
203
 
        self.application = application
204
 
        self.handlers = []
205
 
 
206
 
    def add_handler(self, handler):
207
 
        self.handlers.append(handler)
208
 
 
209
 
    def process(self, request, *args, **kwargs):
210
 
        res = request.get_response(self.application)
211
 
        # currently request handlers are un-ordered
212
 
        for handler in self.handlers:
213
 
            response = handler(request, res)
214
 
        return response
215
 
 
216
 
 
217
 
class ExtensionController(wsgi.Controller):
218
 
 
219
 
    def __init__(self, extension_manager):
220
 
        self.extension_manager = extension_manager
221
 
 
222
 
    def _translate(self, ext):
223
 
        ext_data = {}
224
 
        ext_data['name'] = ext.get_name()
225
 
        ext_data['alias'] = ext.get_alias()
226
 
        ext_data['description'] = ext.get_description()
227
 
        ext_data['namespace'] = ext.get_namespace()
228
 
        ext_data['updated'] = ext.get_updated()
229
 
        ext_data['links'] = []  # TODO(dprince): implement extension links
230
 
        return ext_data
231
 
 
232
 
    def index(self, request):
233
 
        extensions = []
234
 
        for _alias, ext in self.extension_manager.extensions.iteritems():
235
 
            extensions.append(self._translate(ext))
236
 
        return dict(extensions=extensions)
237
 
 
238
 
    def show(self, request, id):
239
 
        # NOTE(dprince): the extensions alias is used as the 'id' for show
240
 
        ext = self.extension_manager.extensions.get(id, None)
241
 
        if not ext:
242
 
            raise webob.exc.HTTPNotFound(
243
 
                _("Extension with alias %s does not exist") % id)
244
 
        return dict(extension=self._translate(ext))
245
 
 
246
 
    def delete(self, request, id):
247
 
        raise webob.exc.HTTPNotFound()
248
 
 
249
 
    def create(self, request):
250
 
        raise webob.exc.HTTPNotFound()
251
 
 
252
 
 
253
 
class ExtensionMiddleware(wsgi.Middleware):
254
 
    """Extensions middleware for WSGI."""
255
 
    def __init__(self, application,
256
 
                 ext_mgr=None):
257
 
 
258
 
        self.ext_mgr = (ext_mgr
259
 
                        or ExtensionManager(
260
 
                        get_extensions_path()))
261
 
        mapper = routes.Mapper()
262
 
 
263
 
        # extended resources
264
 
        for resource in self.ext_mgr.get_resources():
265
 
            LOG.debug(_('Extended resource: %s'),
266
 
                      resource.collection)
267
 
            for action, method in resource.collection_actions.iteritems():
268
 
                path_prefix = ""
269
 
                parent = resource.parent
270
 
                conditions = dict(method=[method])
271
 
                path = "/%s/%s" % (resource.collection, action)
272
 
                if parent:
273
 
                    path_prefix = "/%s/{%s_id}" % (parent["collection_name"],
274
 
                                                   parent["member_name"])
275
 
                with mapper.submapper(controller=resource.controller,
276
 
                                      action=action,
277
 
                                      path_prefix=path_prefix,
278
 
                                      conditions=conditions) as submap:
279
 
                    submap.connect(path)
280
 
                    submap.connect("%s.:(format)" % path)
281
 
            mapper.resource(resource.collection, resource.collection,
282
 
                            controller=resource.controller,
283
 
                            member=resource.member_actions,
284
 
                            parent_resource=resource.parent)
285
 
 
286
 
        # extended actions
287
 
        action_controllers = self._action_ext_controllers(application,
288
 
                                                          self.ext_mgr, mapper)
289
 
        for action in self.ext_mgr.get_actions():
290
 
            LOG.debug(_('Extended action: %s'), action.action_name)
291
 
            controller = action_controllers[action.collection]
292
 
            controller.add_action(action.action_name, action.handler)
293
 
 
294
 
        # extended requests
295
 
        req_controllers = self._request_ext_controllers(application,
296
 
                                                        self.ext_mgr, mapper)
297
 
        for request_ext in self.ext_mgr.get_request_extensions():
298
 
            LOG.debug(_('Extended request: %s'), request_ext.key)
299
 
            controller = req_controllers[request_ext.key]
300
 
            controller.add_handler(request_ext.handler)
301
 
 
302
 
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
303
 
                                                          mapper)
304
 
        super(ExtensionMiddleware, self).__init__(application)
305
 
 
306
 
    @classmethod
307
 
    def factory(cls, global_config, **local_config):
308
 
        """Paste factory."""
309
 
        def _factory(app):
310
 
            return cls(app, global_config, **local_config)
311
 
        return _factory
312
 
 
313
 
    def _action_ext_controllers(self, application, ext_mgr, mapper):
314
 
        """Return a dict of ActionExtensionController-s by collection."""
315
 
        action_controllers = {}
316
 
        for action in ext_mgr.get_actions():
317
 
            if not action.collection in action_controllers.keys():
318
 
                controller = ActionExtensionController(application)
319
 
                mapper.connect("/%s/:(id)/action.:(format)" %
320
 
                               action.collection,
321
 
                               action='action',
322
 
                               controller=controller,
323
 
                               conditions=dict(method=['POST']))
324
 
                mapper.connect("/%s/:(id)/action" % action.collection,
325
 
                               action='action',
326
 
                               controller=controller,
327
 
                               conditions=dict(method=['POST']))
328
 
                action_controllers[action.collection] = controller
329
 
 
330
 
        return action_controllers
331
 
 
332
 
    def _request_ext_controllers(self, application, ext_mgr, mapper):
333
 
        """Returns a dict of RequestExtensionController-s by collection."""
334
 
        request_ext_controllers = {}
335
 
        for req_ext in ext_mgr.get_request_extensions():
336
 
            if not req_ext.key in request_ext_controllers.keys():
337
 
                controller = RequestExtensionController(application)
338
 
                mapper.connect(req_ext.url_route + '.:(format)',
339
 
                               action='process',
340
 
                               controller=controller,
341
 
                               conditions=req_ext.conditions)
342
 
 
343
 
                mapper.connect(req_ext.url_route,
344
 
                               action='process',
345
 
                               controller=controller,
346
 
                               conditions=req_ext.conditions)
347
 
                request_ext_controllers[req_ext.key] = controller
348
 
 
349
 
        return request_ext_controllers
350
 
 
351
 
    @webob.dec.wsgify(RequestClass=wsgi.Request)
352
 
    def __call__(self, req):
353
 
        """Route the incoming request with router."""
354
 
        req.environ['extended.app'] = self.application
355
 
        return self._router
356
 
 
357
 
    @staticmethod
358
 
    @webob.dec.wsgify(RequestClass=wsgi.Request)
359
 
    def _dispatch(req):
360
 
        """Dispatch the request.
361
 
 
362
 
        Returns the routed WSGI app's response or defers to the extended
363
 
        application.
364
 
 
365
 
        """
366
 
        match = req.environ['wsgiorg.routing_args'][1]
367
 
        if not match:
368
 
            return req.environ['extended.app']
369
 
        app = match['controller']
370
 
        return app
371
 
 
372
 
 
373
 
def plugin_aware_extension_middleware_factory(global_config, **local_config):
374
 
    """Paste factory."""
375
 
    def _factory(app):
376
 
        ext_mgr = PluginAwareExtensionManager.get_instance()
377
 
        return ExtensionMiddleware(app, ext_mgr=ext_mgr)
378
 
    return _factory
379
 
 
380
 
 
381
 
class ExtensionManager(object):
382
 
    """Load extensions from the configured extension path.
383
 
 
384
 
    See tests/unit/extensions/foxinsocks.py for an
385
 
    example extension implementation.
386
 
 
387
 
    """
388
 
    def __init__(self, path):
389
 
        LOG.info(_('Initializing extension manager.'))
390
 
        self.path = path
391
 
        self.extensions = {}
392
 
        self._load_all_extensions()
393
 
 
394
 
    def get_resources(self):
395
 
        """Returns a list of ResourceExtension objects."""
396
 
        resources = []
397
 
        resources.append(ResourceExtension('extensions',
398
 
                                           ExtensionController(self)))
399
 
        for ext in self.extensions.itervalues():
400
 
            try:
401
 
                resources.extend(ext.get_resources())
402
 
            except AttributeError:
403
 
                # NOTE(dprince): Extension aren't required to have resource
404
 
                # extensions
405
 
                pass
406
 
        return resources
407
 
 
408
 
    def get_actions(self):
409
 
        """Returns a list of ActionExtension objects."""
410
 
        actions = []
411
 
        for ext in self.extensions.itervalues():
412
 
            try:
413
 
                actions.extend(ext.get_actions())
414
 
            except AttributeError:
415
 
                # NOTE(dprince): Extension aren't required to have action
416
 
                # extensions
417
 
                pass
418
 
        return actions
419
 
 
420
 
    def get_request_extensions(self):
421
 
        """Returns a list of RequestExtension objects."""
422
 
        request_exts = []
423
 
        for ext in self.extensions.itervalues():
424
 
            try:
425
 
                request_exts.extend(ext.get_request_extensions())
426
 
            except AttributeError:
427
 
                # NOTE(dprince): Extension aren't required to have request
428
 
                # extensions
429
 
                pass
430
 
        return request_exts
431
 
 
432
 
    def extend_resources(self, version, attr_map):
433
 
        """Extend resources with additional resources or attributes.
434
 
 
435
 
        :param: attr_map, the existing mapping from resource name to
436
 
        attrs definition.
437
 
 
438
 
        After this function, we will extend the attr_map if an extension
439
 
        wants to extend this map.
440
 
        """
441
 
        for ext in self.extensions.itervalues():
442
 
            if not hasattr(ext, 'get_extended_resources'):
443
 
                continue
444
 
            try:
445
 
                extended_attrs = ext.get_extended_resources(version)
446
 
                for resource, resource_attrs in extended_attrs.iteritems():
447
 
                    if attr_map.get(resource, None):
448
 
                        attr_map[resource].update(resource_attrs)
449
 
                    else:
450
 
                        attr_map[resource] = resource_attrs
451
 
            except AttributeError:
452
 
                LOG.exception("Error fetching extended attributes for "
453
 
                              "extension '%s'" % ext.get_name())
454
 
 
455
 
    def _check_extension(self, extension):
456
 
        """Checks for required methods in extension objects."""
457
 
        try:
458
 
            LOG.debug(_('Ext name: %s'), extension.get_name())
459
 
            LOG.debug(_('Ext alias: %s'), extension.get_alias())
460
 
            LOG.debug(_('Ext description: %s'), extension.get_description())
461
 
            LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
462
 
            LOG.debug(_('Ext updated: %s'), extension.get_updated())
463
 
        except AttributeError as ex:
464
 
            LOG.exception(_("Exception loading extension: %s"), unicode(ex))
465
 
            return False
466
 
        if hasattr(extension, 'check_env'):
467
 
            try:
468
 
                extension.check_env()
469
 
            except exceptions.InvalidExtenstionEnv as ex:
470
 
                LOG.warn(_("Exception loading extension: %s"), unicode(ex))
471
 
                return False
472
 
        return True
473
 
 
474
 
    def _load_all_extensions(self):
475
 
        """Load extensions from the configured path.
476
 
 
477
 
        Load extensions from the configured path. The extension name is
478
 
        constructed from the module_name. If your extension module was named
479
 
        widgets.py the extension class within that module should be
480
 
        'Widgets'.
481
 
 
482
 
        See tests/unit/extensions/foxinsocks.py for an example
483
 
        extension implementation.
484
 
 
485
 
        """
486
 
        for path in self.path.split(':'):
487
 
            if os.path.exists(path):
488
 
                self._load_all_extensions_from_path(path)
489
 
            else:
490
 
                LOG.error("Extension path \"%s\" doesn't exist!" % path)
491
 
 
492
 
    def _load_all_extensions_from_path(self, path):
493
 
        for f in os.listdir(path):
494
 
            try:
495
 
                LOG.info(_('Loading extension file: %s'), f)
496
 
                mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
497
 
                ext_path = os.path.join(path, f)
498
 
                if file_ext.lower() == '.py' and not mod_name.startswith('_'):
499
 
                    mod = imp.load_source(mod_name, ext_path)
500
 
                    ext_name = mod_name[0].upper() + mod_name[1:]
501
 
                    new_ext_class = getattr(mod, ext_name, None)
502
 
                    if not new_ext_class:
503
 
                        LOG.warn(_('Did not find expected name '
504
 
                                   '"%(ext_name)s" in %(file)s'),
505
 
                                 {'ext_name': ext_name,
506
 
                                  'file': ext_path})
507
 
                        continue
508
 
                    new_ext = new_ext_class()
509
 
                    self.add_extension(new_ext)
510
 
            except Exception as exception:
511
 
                LOG.warn("extension file %s wasnt loaded due to %s",
512
 
                         f, exception)
513
 
 
514
 
    def add_extension(self, ext):
515
 
        # Do nothing if the extension doesn't check out
516
 
        if not self._check_extension(ext):
517
 
            return
518
 
 
519
 
        alias = ext.get_alias()
520
 
        LOG.warn(_('Loaded extension: %s'), alias)
521
 
 
522
 
        if alias in self.extensions:
523
 
            raise exceptions.Error("Found duplicate extension: %s" %
524
 
                                   alias)
525
 
        self.extensions[alias] = ext
526
 
 
527
 
 
528
 
class PluginAwareExtensionManager(ExtensionManager):
529
 
 
530
 
    _instance = None
531
 
 
532
 
    def __init__(self, path, plugin):
533
 
        self.plugin = plugin
534
 
        super(PluginAwareExtensionManager, self).__init__(path)
535
 
 
536
 
    def _check_extension(self, extension):
537
 
        """Checks if plugin supports extension and implements the
538
 
        extension contract."""
539
 
        extension_is_valid = super(PluginAwareExtensionManager,
540
 
                                   self)._check_extension(extension)
541
 
        return (extension_is_valid and
542
 
                self._plugin_supports(extension) and
543
 
                self._plugin_implements_interface(extension))
544
 
 
545
 
    def _plugin_supports(self, extension):
546
 
        alias = extension.get_alias()
547
 
        supports_extension = (hasattr(self.plugin,
548
 
                                      "supported_extension_aliases") and
549
 
                              alias in self.plugin.supported_extension_aliases)
550
 
        plugin_provider = cfg.CONF.core_plugin
551
 
        if not supports_extension and plugin_provider in ENABLED_EXTS:
552
 
            supports_extension = (alias in
553
 
                                  ENABLED_EXTS[plugin_provider]['ext_alias'])
554
 
        if not supports_extension:
555
 
            LOG.warn("extension %s not supported by plugin %s",
556
 
                     alias, self.plugin)
557
 
        return supports_extension
558
 
 
559
 
    def _plugin_implements_interface(self, extension):
560
 
        if(not hasattr(extension, "get_plugin_interface") or
561
 
           extension.get_plugin_interface() is None):
562
 
            return True
563
 
        plugin_has_interface = isinstance(self.plugin,
564
 
                                          extension.get_plugin_interface())
565
 
        if not plugin_has_interface:
566
 
            LOG.warn("plugin %s does not implement extension's"
567
 
                     "plugin interface %s" % (self.plugin,
568
 
                                              extension.get_alias()))
569
 
        return plugin_has_interface
570
 
 
571
 
    @classmethod
572
 
    def get_instance(cls):
573
 
        if cls._instance is None:
574
 
            plugin_provider = cfg.CONF.core_plugin
575
 
            if plugin_provider in ENABLED_EXTS:
576
 
                for model in ENABLED_EXTS[plugin_provider]['ext_db_models']:
577
 
                    LOG.debug('loading model %s', model)
578
 
                    model_class = importutils.import_class(model)
579
 
            cls._instance = cls(get_extensions_path(),
580
 
                                QuantumManager.get_plugin())
581
 
        return cls._instance
582
 
 
583
 
 
584
 
class RequestExtension(object):
585
 
    """Extend requests and responses of core Quantum OpenStack API controllers.
586
 
 
587
 
    Provide a way to add data to responses and handle custom request data
588
 
    that is sent to core Quantum OpenStack API controllers.
589
 
 
590
 
    """
591
 
    def __init__(self, method, url_route, handler):
592
 
        self.url_route = url_route
593
 
        self.handler = handler
594
 
        self.conditions = dict(method=[method])
595
 
        self.key = "%s-%s" % (method, url_route)
596
 
 
597
 
 
598
 
class ActionExtension(object):
599
 
    """Add custom actions to core Quantum OpenStack API controllers."""
600
 
 
601
 
    def __init__(self, collection, action_name, handler):
602
 
        self.collection = collection
603
 
        self.action_name = action_name
604
 
        self.handler = handler
605
 
 
606
 
 
607
 
class ResourceExtension(object):
608
 
    """Add top level resources to the OpenStack API in Quantum."""
609
 
 
610
 
    def __init__(self, collection, controller, parent=None,
611
 
                 collection_actions={}, member_actions={}):
612
 
        self.collection = collection
613
 
        self.controller = controller
614
 
        self.parent = parent
615
 
        self.collection_actions = collection_actions
616
 
        self.member_actions = member_actions
617
 
 
618
 
 
619
 
# Returns the extention paths from a config entry and the __path__
620
 
# of quantum.extensions
621
 
def get_extensions_path():
622
 
    paths = ':'.join(quantum.extensions.__path__)
623
 
    if cfg.CONF.api_extensions_path:
624
 
        paths = ':'.join([cfg.CONF.api_extensions_path, paths])
625
 
 
626
 
    return paths