416d60674c3ae6892536ddee9a84c5803baeb691
[openstack-build/neutron-build.git] / neutron / pecan_wsgi / controllers / root.py
1 # Copyright (c) 2015 Mirantis, Inc.
2 # Copyright (c) 2015 Rackspace, Inc.
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.
16
17 from oslo_log import log
18 import pecan
19 from pecan import request
20
21 from neutron._i18n import _, _LW
22 from neutron.api import extensions
23 from neutron.api.views import versions as versions_view
24 from neutron import manager
25 from neutron.pecan_wsgi.controllers import utils
26
27 LOG = log.getLogger(__name__)
28 _VERSION_INFO = {}
29
30
31 def _load_version_info(version_info):
32     assert version_info['id'] not in _VERSION_INFO
33     _VERSION_INFO[version_info['id']] = version_info
34
35
36 def _get_version_info():
37     return _VERSION_INFO.values()
38
39
40 class RootController(object):
41
42     @utils.expose(generic=True)
43     def index(self):
44         builder = versions_view.get_view_builder(pecan.request)
45         versions = [builder.build(version) for version in _get_version_info()]
46         return dict(versions=versions)
47
48     @utils.when(index, method='HEAD')
49     @utils.when(index, method='POST')
50     @utils.when(index, method='PATCH')
51     @utils.when(index, method='PUT')
52     @utils.when(index, method='DELETE')
53     def not_supported(self):
54         pecan.abort(405)
55
56
57 class ExtensionsController(object):
58
59     @utils.expose()
60     def _lookup(self, alias, *remainder):
61         return ExtensionController(alias), remainder
62
63     @utils.expose()
64     def index(self):
65         ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
66         exts = [extensions.ExtensionController._translate(ext)
67                 for ext in ext_mgr.extensions.values()]
68         return {'extensions': exts}
69
70
71 class V2Controller(object):
72
73     # Same data structure as neutron.api.versions.Versions for API backward
74     # compatibility
75     version_info = {
76         'id': 'v2.0',
77         'status': 'CURRENT'
78     }
79     _load_version_info(version_info)
80
81     extensions = ExtensionsController()
82
83     @utils.expose(generic=True)
84     def index(self):
85         builder = versions_view.get_view_builder(pecan.request)
86         return dict(version=builder.build(self.version_info))
87
88     @utils.when(index, method='HEAD')
89     @utils.when(index, method='POST')
90     @utils.when(index, method='PATCH')
91     @utils.when(index, method='PUT')
92     @utils.when(index, method='DELETE')
93     def not_supported(self):
94         pecan.abort(405)
95
96     @utils.expose()
97     def _lookup(self, collection, *remainder):
98         # if collection exists in the extension to service plugins map then
99         # we are assuming that collection is the service plugin and
100         # needs to be remapped.
101         # Example: https://neutron.endpoint/v2.0/lbaas/loadbalancers
102         if (remainder and
103                 manager.NeutronManager.get_service_plugin_by_path_prefix(
104                     collection)):
105             collection = remainder[0]
106             remainder = remainder[1:]
107         controller = manager.NeutronManager.get_controller_for_resource(
108             collection)
109         if not controller:
110             LOG.warn(_LW("No controller found for: %s - returning response "
111                          "code 404"), collection)
112             pecan.abort(404)
113         # Store resource and collection names in pecan request context so that
114         # hooks can leverage them if necessary
115         request.context['resource'] = controller.resource
116         request.context['collection'] = collection
117         return controller, remainder
118
119
120 # This controller cannot be specified directly as a member of RootController
121 # as its path is not a valid python identifier
122 pecan.route(RootController, 'v2.0', V2Controller())
123
124
125 class ExtensionController(object):
126
127     def __init__(self, alias):
128         self.alias = alias
129
130     @utils.expose()
131     def index(self):
132         ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
133         ext = ext_mgr.extensions.get(self.alias, None)
134         if not ext:
135             pecan.abort(
136                 404, detail=_("Extension with alias %s "
137                               "does not exist") % self.alias)
138         return {'extension': extensions.ExtensionController._translate(ext)}
139
140
141 class CollectionsController(utils.NeutronPecanController):
142
143     @utils.expose()
144     def _lookup(self, item, *remainder):
145         # Store resource identifier in request context
146         request.context['resource_id'] = item
147         return ItemController(self.resource, item), remainder
148
149     @utils.expose(generic=True)
150     def index(self, *args, **kwargs):
151         return self.get(*args, **kwargs)
152
153     def get(self, *args, **kwargs):
154         # list request
155         # TODO(kevinbenton): use user-provided fields in call to plugin
156         # after making sure policy enforced fields remain
157         kwargs.pop('fields', None)
158         _listify = lambda x: x if isinstance(x, list) else [x]
159         filters = {k: _listify(v) for k, v in kwargs.items()}
160         # TODO(kevinbenton): convert these using api_common.get_filters
161         lister = getattr(self.plugin, 'get_%s' % self.collection)
162         neutron_context = request.context['neutron_context']
163         return {self.collection: lister(neutron_context, filters=filters)}
164
165     @utils.when(index, method='HEAD')
166     @utils.when(index, method='PATCH')
167     @utils.when(index, method='PUT')
168     @utils.when(index, method='DELETE')
169     def not_supported(self):
170         pecan.abort(405)
171
172     @utils.when(index, method='POST')
173     def post(self, *args, **kwargs):
174         # TODO(kevinbenton): emulated bulk!
175         resources = request.context['resources']
176         pecan.response.status = 201
177         return self.create(resources)
178
179     def create(self, resources):
180         if len(resources) > 1:
181             # Bulk!
182             method = 'create_%s_bulk' % self.resource
183             key = self.collection
184             data = {key: [{self.resource: res} for res in resources]}
185         else:
186             method = 'create_%s' % self.resource
187             key = self.resource
188             data = {key: resources[0]}
189         creator = getattr(self.plugin, method)
190         neutron_context = request.context['neutron_context']
191         return {key: creator(neutron_context, data)}
192
193
194 class ItemController(utils.NeutronPecanController):
195
196     def __init__(self, resource, item):
197         super(ItemController, self).__init__(None, resource)
198         self.item = item
199
200     @utils.expose(generic=True)
201     def index(self, *args, **kwargs):
202         return self.get()
203
204     def get(self, *args, **kwargs):
205         getter = getattr(self.plugin, 'get_%s' % self.resource)
206         neutron_context = request.context['neutron_context']
207         return {self.resource: getter(neutron_context, self.item)}
208
209     @utils.when(index, method='HEAD')
210     @utils.when(index, method='POST')
211     @utils.when(index, method='PATCH')
212     def not_supported(self):
213         pecan.abort(405)
214
215     @utils.when(index, method='PUT')
216     def put(self, *args, **kwargs):
217         neutron_context = request.context['neutron_context']
218         resources = request.context['resources']
219         if request.member_action:
220             member_action_method = getattr(self.plugin,
221                                            request.member_action)
222             return member_action_method(neutron_context, self.item,
223                                         resources[0])
224         # TODO(kevinbenton): bulk?
225         updater = getattr(self.plugin, 'update_%s' % self.resource)
226         # Bulk update is not supported, 'resources' always contains a single
227         # elemenet
228         data = {self.resource: resources[0]}
229         return updater(neutron_context, self.item, data)
230
231     @utils.when(index, method='DELETE')
232     def delete(self):
233         # TODO(kevinbenton): setting code could be in a decorator
234         pecan.response.status = 204
235         neutron_context = request.context['neutron_context']
236         deleter = getattr(self.plugin, 'delete_%s' % self.resource)
237         return deleter(neutron_context, self.item)