~raxnetworking/nova/bare_bones_melange

« back to all changes in this revision

Viewing changes to melange/common/extensions.py

  • Committer: Rajaram Mallya
  • Date: 2011-09-09 06:33:09 UTC
  • Revision ID: rajarammallya@gmail.com-20110909063309-gbzsll614t0q9vau
Vinkesh/Rajaram|Removed extensions code and started using openstack common's extension code

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
#    License for the specific language governing permissions and limitations
17
17
#    under the License.
18
18
 
19
 
import imp
20
 
import logging
21
 
import os
22
 
import routes
23
 
import webob.dec
24
 
import webob.exc
25
 
 
26
 
from melange.common import exception
27
 
from melange.common import wsgi
28
 
 
29
 
 
30
 
LOG = logging.getLogger('melange.common.extensions')
31
 
 
32
 
 
33
 
class ExtensionDescriptor(object):
34
 
    """Base class that defines the contract for extensions.
35
 
 
36
 
    Note that you don't have to derive from this class to have a valid
37
 
    extension; it is purely a convenience.
38
 
 
39
 
    """
40
 
 
41
 
    def get_name(self):
42
 
        """The name of the extension.
43
 
 
44
 
        e.g. 'Fox In Socks'
45
 
 
46
 
        """
47
 
        raise NotImplementedError()
48
 
 
49
 
    def get_alias(self):
50
 
        """The alias for the extension.
51
 
 
52
 
        e.g. 'FOXNSOX'
53
 
 
54
 
        """
55
 
        raise NotImplementedError()
56
 
 
57
 
    def get_description(self):
58
 
        """Friendly description for the extension.
59
 
 
60
 
        e.g. 'The Fox In Socks Extension'
61
 
 
62
 
        """
63
 
        raise NotImplementedError()
64
 
 
65
 
    def get_namespace(self):
66
 
        """The XML namespace for the extension.
67
 
 
68
 
        e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'
69
 
 
70
 
        """
71
 
        raise NotImplementedError()
72
 
 
73
 
    def get_updated(self):
74
 
        """The timestamp when the extension was last updated.
75
 
 
76
 
        e.g. '2011-01-22T13:25:27-06:00'
77
 
 
78
 
        """
79
 
        # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS
80
 
        raise NotImplementedError()
81
 
 
82
 
    def get_resources(self):
83
 
        """List of extensions.ResourceExtension extension objects.
84
 
 
85
 
        Resources define new nouns, and are accessible through URLs.
86
 
 
87
 
        """
88
 
        resources = []
89
 
        return resources
90
 
 
91
 
    def get_actions(self):
92
 
        """List of extensions.ActionExtension extension objects.
93
 
 
94
 
        Actions are verbs callable from the API.
95
 
 
96
 
        """
97
 
        actions = []
98
 
        return actions
99
 
 
100
 
    def get_request_extensions(self):
101
 
        """List of extensions.RequestException extension objects.
102
 
 
103
 
        Request extensions are used to handle custom request data.
104
 
 
105
 
        """
106
 
        request_exts = []
107
 
        return request_exts
108
 
 
109
 
 
110
 
class ActionExtensionController(wsgi.Controller):
111
 
 
112
 
    def __init__(self, application):
113
 
 
114
 
        self.application = application
115
 
        self.action_handlers = {}
116
 
        super(ActionExtensionController, self).__init__()
117
 
 
118
 
    def add_action(self, action_name, handler):
119
 
        self.action_handlers[action_name] = handler
120
 
 
121
 
    def action(self, request, id, body=None):
122
 
        for action_name, handler in self.action_handlers.iteritems():
123
 
            if action_name in body:
124
 
                return handler(body, request, id)
125
 
        # no action handler found (bump to downstream application)
126
 
        response = self.application
127
 
        return response
128
 
 
129
 
 
130
 
class RequestExtensionController(wsgi.Controller):
131
 
 
132
 
    def __init__(self, application):
133
 
        self.application = application
134
 
        self.handlers = []
135
 
        super(RequestExtensionController, self).__init__()
136
 
 
137
 
    def add_handler(self, handler):
138
 
        self.handlers.append(handler)
139
 
 
140
 
    def process(self, request, *args, **kwargs):
141
 
        res = request.get_response(self.application)
142
 
        # currently request handlers are un-ordered
143
 
        for handler in self.handlers:
144
 
            res = handler(request, res)
145
 
        return res
146
 
 
147
 
 
148
 
class ExtensionController(wsgi.Controller):
149
 
 
150
 
    def __init__(self, extension_manager):
151
 
        self.extension_manager = extension_manager
152
 
        super(ExtensionController, self).__init__()
153
 
 
154
 
    def _translate(self, ext):
155
 
        ext_data = {}
156
 
        ext_data['name'] = ext.get_name()
157
 
        ext_data['alias'] = ext.get_alias()
158
 
        ext_data['description'] = ext.get_description()
159
 
        ext_data['namespace'] = ext.get_namespace()
160
 
        ext_data['updated'] = ext.get_updated()
161
 
        ext_data['links'] = []  # TODO(dprince): implement extension links
162
 
        return ext_data
163
 
 
164
 
    def index(self, request):
165
 
        extensions = []
166
 
        for _alias, ext in self.extension_manager.extensions.iteritems():
167
 
            extensions.append(self._translate(ext))
168
 
        return dict(extensions=extensions)
169
 
 
170
 
    def show(self, request, id):
171
 
        # NOTE(dprince): the extensions alias is used as the 'id' for show
172
 
        ext = self.extension_manager.extensions[id]
173
 
        return dict(extension=self._translate(ext))
174
 
 
175
 
    def delete(self, request, id):
176
 
        raise webob.exc.HTTPNotFound()
177
 
 
178
 
    def create(self, request):
179
 
        raise webob.exc.HTTPNotFound()
180
 
 
181
 
 
182
 
class ExtensionMiddleware(wsgi.Middleware):
183
 
    """Extensions middleware for WSGI."""
184
 
 
185
 
    @classmethod
186
 
    def factory(cls, global_config, **local_config):
187
 
        """Paste factory."""
188
 
        def _factory(app):
189
 
            return cls(app, global_config, **local_config)
190
 
        return _factory
191
 
 
192
 
    def _action_ext_controllers(self, application, ext_mgr, mapper):
193
 
        """Return a dict of ActionExtensionController-s by collection."""
194
 
        action_controllers = {}
195
 
        for action in ext_mgr.get_actions():
196
 
            if not action.collection in action_controllers.keys():
197
 
                controller = ActionExtensionController(application)
198
 
                mapper.connect("/%s/:(id)/action.:(format)" %
199
 
                                action.collection,
200
 
                                action='action',
201
 
                                controller=controller.create_resource(),
202
 
                                conditions=dict(method=['POST']))
203
 
                mapper.connect("/%s/:(id)/action" % action.collection,
204
 
                                action='action',
205
 
                                controller=controller.create_resource(),
206
 
                                conditions=dict(method=['POST']))
207
 
                action_controllers[action.collection] = controller
208
 
 
209
 
        return action_controllers
210
 
 
211
 
    def _request_ext_controllers(self, application, ext_mgr, mapper):
212
 
        """Returns a dict of RequestExtensionController-s by collection."""
213
 
        request_ext_controllers = {}
214
 
        for req_ext in ext_mgr.get_request_extensions():
215
 
            if not req_ext.key in request_ext_controllers.keys():
216
 
                controller = RequestExtensionController(application)
217
 
                mapper.connect(req_ext.url_route + '.:(format)',
218
 
                                action='process',
219
 
                                controller=controller.create_resource(),
220
 
                                conditions=req_ext.conditions)
221
 
 
222
 
                mapper.connect(req_ext.url_route,
223
 
                                action='process',
224
 
                                controller=controller.create_resource(),
225
 
                                conditions=req_ext.conditions)
226
 
                request_ext_controllers[req_ext.key] = controller
227
 
 
228
 
        return request_ext_controllers
229
 
 
230
 
    def __init__(self, application, config_params,
231
 
                 ext_mgr=None):
232
 
        self.ext_mgr = (ext_mgr
233
 
                   or ExtensionManager(config_params.get('api_extensions_path',
234
 
                                                         '')))
235
 
 
236
 
        mapper = routes.Mapper()
237
 
 
238
 
        # extended resources
239
 
        for resource in self.ext_mgr.get_resources():
240
 
            LOG.debug(_('Extended resource: %s'),
241
 
                        resource.collection)
242
 
            mapper.resource(resource.collection, resource.collection,
243
 
                            controller=resource.controller.create_resource(),
244
 
                            collection=resource.collection_actions,
245
 
                            member=resource.member_actions,
246
 
                            parent_resource=resource.parent)
247
 
 
248
 
        # extended actions
249
 
        action_controllers = self._action_ext_controllers(application,
250
 
                                                          self.ext_mgr, mapper)
251
 
        for action in self.ext_mgr.get_actions():
252
 
            LOG.debug(_('Extended action: %s'), action.action_name)
253
 
            controller = action_controllers[action.collection]
254
 
            controller.add_action(action.action_name, action.handler)
255
 
 
256
 
        # extended requests
257
 
        req_controllers = self._request_ext_controllers(application,
258
 
                                                        self.ext_mgr, mapper)
259
 
        for request_ext in self.ext_mgr.get_request_extensions():
260
 
            LOG.debug(_('Extended request: %s'), request_ext.key)
261
 
            controller = req_controllers[request_ext.key]
262
 
            controller.add_handler(request_ext.handler)
263
 
 
264
 
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
265
 
                                                          mapper)
266
 
 
267
 
        super(ExtensionMiddleware, self).__init__(application)
268
 
 
269
 
    @webob.dec.wsgify(RequestClass=wsgi.Request)
270
 
    def __call__(self, request):
271
 
        """Route the incoming request with router."""
272
 
        request.environ['extended.app'] = self.application
273
 
        return self._router
274
 
 
275
 
    @staticmethod
276
 
    @webob.dec.wsgify(RequestClass=wsgi.Request)
277
 
    def _dispatch(request):
278
 
        """Dispatch the request.
279
 
 
280
 
        Returns the routed WSGI app's response or defers to the extended
281
 
        application.
282
 
 
283
 
        """
284
 
        match = request.environ['wsgiorg.routing_args'][1]
285
 
        if not match:
286
 
            return request.environ['extended.app']
287
 
        app = match['controller']
288
 
        return app
289
 
 
290
 
 
291
 
class ExtensionManager(object):
292
 
    """Load extensions from the configured extension path.
293
 
 
294
 
    See melange/tests/unit/extensions/foxinsocks.py for an
295
 
    example extension implementation.
296
 
 
297
 
    """
298
 
 
299
 
    def __init__(self, path):
300
 
        LOG.info(_('Initializing extension manager.'))
301
 
 
302
 
        self.path = path
303
 
        self.extensions = {}
304
 
        self._load_all_extensions()
305
 
 
306
 
    def get_resources(self):
307
 
        """Returns a list of ResourceExtension objects."""
308
 
        resources = []
309
 
        resources.append(ResourceExtension('extensions',
310
 
                                            ExtensionController(self)))
311
 
        for alias, ext in self.extensions.iteritems():
312
 
            try:
313
 
                resources.extend(ext.get_resources())
314
 
            except AttributeError:
315
 
                # NOTE(dprince): Extension aren't required to have resource
316
 
                # extensions
317
 
                pass
318
 
        return resources
319
 
 
320
 
    def get_actions(self):
321
 
        """Returns a list of ActionExtension objects."""
322
 
        actions = []
323
 
        for alias, ext in self.extensions.iteritems():
324
 
            try:
325
 
                actions.extend(ext.get_actions())
326
 
            except AttributeError:
327
 
                # NOTE(dprince): Extension aren't required to have action
328
 
                # extensions
329
 
                pass
330
 
        return actions
331
 
 
332
 
    def get_request_extensions(self):
333
 
        """Returns a list of RequestExtension objects."""
334
 
        request_exts = []
335
 
        for alias, ext in self.extensions.iteritems():
336
 
            try:
337
 
                request_exts.extend(ext.get_request_extensions())
338
 
            except AttributeError:
339
 
                # NOTE(dprince): Extension aren't required to have request
340
 
                # extensions
341
 
                pass
342
 
        return request_exts
343
 
 
344
 
    def _check_extension(self, extension):
345
 
        """Checks for required methods in extension objects."""
346
 
        try:
347
 
            LOG.debug(_('Ext name: %s'), extension.get_name())
348
 
            LOG.debug(_('Ext alias: %s'), extension.get_alias())
349
 
            LOG.debug(_('Ext description: %s'), extension.get_description())
350
 
            LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
351
 
            LOG.debug(_('Ext updated: %s'), extension.get_updated())
352
 
        except AttributeError as ex:
353
 
            LOG.exception(_("Exception loading extension: %s"), unicode(ex))
354
 
 
355
 
    def _load_all_extensions(self):
356
 
        """Load extensions from the configured path.
357
 
 
358
 
        Load extensions from the configured path. The extension name is
359
 
        constructed from the module_name. If your extension module was named
360
 
        widgets.py the extension class within that module should be
361
 
        'Widgets'.
362
 
 
363
 
        In addition, extensions are loaded from the 'contrib' directory.
364
 
 
365
 
        See melange/tests/unit/extensions/foxinsocks.py for an example
366
 
        extension implementation.
367
 
 
368
 
        """
369
 
        if os.path.exists(self.path):
370
 
            self._load_all_extensions_from_path(self.path)
371
 
 
372
 
        contrib_path = os.path.join(os.path.dirname(__file__), "contrib")
373
 
        if os.path.exists(contrib_path):
374
 
            self._load_all_extensions_from_path(contrib_path)
375
 
 
376
 
    def _load_all_extensions_from_path(self, path):
377
 
        for f in os.listdir(path):
378
 
            LOG.info(_('Loading extension file: %s'), f)
379
 
            mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
380
 
            ext_path = os.path.join(path, f)
381
 
            if file_ext.lower() == '.py' and not mod_name.startswith('_'):
382
 
                mod = imp.load_source(mod_name, ext_path)
383
 
                ext_name = mod_name[0].upper() + mod_name[1:]
384
 
                new_ext_class = getattr(mod, ext_name, None)
385
 
                if not new_ext_class:
386
 
                    LOG.warn(_('Did not find expected name '
387
 
                               '"%(ext_name)s" in %(file)s'),
388
 
                             {'ext_name': ext_name,
389
 
                              'file': ext_path})
390
 
                    continue
391
 
                new_ext = new_ext_class()
392
 
                self._check_extension(new_ext)
393
 
                self._add_extension(new_ext)
394
 
 
395
 
    def _add_extension(self, ext):
396
 
        alias = ext.get_alias()
397
 
        LOG.info(_('Loaded extension: %s'), alias)
398
 
 
399
 
        self._check_extension(ext)
400
 
 
401
 
        if alias in self.extensions:
402
 
            raise exception.MelangeError(_("Found duplicate extension: %s")
403
 
                                         % alias)
404
 
        self.extensions[alias] = ext
405
 
 
406
 
 
407
 
class RequestExtension(object):
408
 
    """Extend requests and responses of core melange OpenStack API controllers.
409
 
 
410
 
    Provide a way to add data to responses and handle custom request data
411
 
    that is sent to core melange OpenStack API controllers.
412
 
 
413
 
    """
414
 
    def __init__(self, method, url_route, handler):
415
 
        self.url_route = url_route
416
 
        self.handler = handler
417
 
        self.conditions = dict(method=[method])
418
 
        self.key = "%s-%s" % (method, url_route)
419
 
 
420
 
 
421
 
class ActionExtension(object):
422
 
    """Add custom actions to core melange OpenStack API controllers."""
423
 
 
424
 
    def __init__(self, collection, action_name, handler):
425
 
        self.collection = collection
426
 
        self.action_name = action_name
427
 
        self.handler = handler
428
 
 
429
 
 
430
 
class ResourceExtension(object):
431
 
    """Add top level resources to the OpenStack API in melange."""
432
 
 
433
 
    def __init__(self, collection, controller, parent=None,
434
 
                 collection_actions=None, member_actions=None,
435
 
                 deserializer=None, serializer=None):
436
 
        self.collection = collection
437
 
        self.controller = controller
438
 
        self.parent = parent
439
 
        self.collection_actions = collection_actions or {}
440
 
        self.member_actions = member_actions or {}
441
 
        self.deserializer = deserializer
442
 
        self.serializer = serializer
 
19
from openstack.common import extensions
 
20
 
 
21
 
 
22
def factory(global_config, **local_config):
 
23
    """Paste factory."""
 
24
    def _factory(app):
 
25
        extensions.DEFAULT_XMLNS = "http://docs.openstack.org/melange"
 
26
        ext_mgr = extensions.ExtensionManager(
 
27
            global_config.get('api_extensions_path', ''))
 
28
        return extensions.ExtensionMiddleware(app, global_config, ext_mgr)
 
29
    return _factory