1# Copyright (c) 2013 eBay Inc.
2# Copyright (c) 2013 OpenStack Foundation
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16"""The QoS specs extension"""
17
18from oslo_log import log as logging
19from oslo_utils import timeutils
20import six
21from six.moves import http_client
22import webob
23
24from cinder.api import common
25from cinder.api import extensions
26from cinder.api.openstack import wsgi
27from cinder.api.views import qos_specs as view_qos_specs
28from cinder import exception
29from cinder.i18n import _
30from cinder.policies import qos_specs as policy
31from cinder import rpc
32from cinder import utils
33from cinder.volume import qos_specs
34
35
36LOG = logging.getLogger(__name__)
37
38
39def _check_specs(context, specs_id):
40    # Not found exception will be handled at the wsgi level
41    qos_specs.get_qos_specs(context, specs_id)
42
43
44class QoSSpecsController(wsgi.Controller):
45    """The volume type extra specs API controller for the OpenStack API."""
46
47    _view_builder_class = view_qos_specs.ViewBuilder
48
49    @staticmethod
50    @utils.if_notifications_enabled
51    def _notify_qos_specs_error(context, method, payload):
52        rpc.get_notifier('QoSSpecs').error(context,
53                                           method,
54                                           payload)
55
56    def index(self, req):
57        """Returns the list of qos_specs."""
58        context = req.environ['cinder.context']
59        context.authorize(policy.GET_ALL_POLICY)
60
61        params = req.params.copy()
62
63        marker, limit, offset = common.get_pagination_params(params)
64        sort_keys, sort_dirs = common.get_sort_params(params)
65        filters = params
66        allowed_search_options = ('id', 'name', 'consumer')
67        utils.remove_invalid_filter_options(context, filters,
68                                            allowed_search_options)
69
70        specs = qos_specs.get_all_specs(context, filters=filters,
71                                        marker=marker, limit=limit,
72                                        offset=offset, sort_keys=sort_keys,
73                                        sort_dirs=sort_dirs)
74        return self._view_builder.summary_list(req, specs)
75
76    def create(self, req, body=None):
77        context = req.environ['cinder.context']
78        context.authorize(policy.CREATE_POLICY)
79
80        self.assert_valid_body(body, 'qos_specs')
81
82        specs = body['qos_specs']
83        name = specs.pop('name', None)
84        if name is None:
85            msg = _("Please specify a name for QoS specs.")
86            raise webob.exc.HTTPBadRequest(explanation=msg)
87
88        self.validate_string_length(name, 'name', min_length=1,
89                                    max_length=255, remove_whitespaces=True)
90        name = name.strip()
91
92        try:
93            spec = qos_specs.create(context, name, specs)
94            notifier_info = dict(name=name,
95                                 created_at=spec.created_at,
96                                 specs=specs)
97            rpc.get_notifier('QoSSpecs').info(context,
98                                              'qos_specs.create',
99                                              notifier_info)
100        except exception.InvalidQoSSpecs as err:
101            notifier_err = dict(name=name, error_message=err)
102            self._notify_qos_specs_error(context,
103                                         'qos_specs.create',
104                                         notifier_err)
105            raise webob.exc.HTTPBadRequest(explanation=six.text_type(err))
106        except exception.QoSSpecsExists as err:
107            notifier_err = dict(name=name, error_message=err)
108            self._notify_qos_specs_error(context,
109                                         'qos_specs.create',
110                                         notifier_err)
111            raise webob.exc.HTTPConflict(explanation=six.text_type(err))
112        except exception.QoSSpecsCreateFailed as err:
113            notifier_err = dict(name=name, error_message=err)
114            self._notify_qos_specs_error(context,
115                                         'qos_specs.create',
116                                         notifier_err)
117            raise webob.exc.HTTPInternalServerError(
118                explanation=six.text_type(err))
119
120        return self._view_builder.detail(req, spec)
121
122    def update(self, req, id, body=None):
123        context = req.environ['cinder.context']
124        context.authorize(policy.UPDATE_POLICY)
125        self.assert_valid_body(body, 'qos_specs')
126        specs = body['qos_specs']
127        try:
128            spec = qos_specs.get_qos_specs(context, id)
129
130            qos_specs.update(context, id, specs)
131            notifier_info = dict(id=id,
132                                 created_at=spec.created_at,
133                                 updated_at=timeutils.utcnow(),
134                                 specs=specs)
135            rpc.get_notifier('QoSSpecs').info(context,
136                                              'qos_specs.update',
137                                              notifier_info)
138        except (exception.QoSSpecsNotFound, exception.InvalidQoSSpecs) as err:
139            notifier_err = dict(id=id, error_message=err)
140            self._notify_qos_specs_error(context,
141                                         'qos_specs.update',
142                                         notifier_err)
143            # Not found exception will be handled at the wsgi level
144            raise
145        except exception.QoSSpecsUpdateFailed as err:
146            notifier_err = dict(id=id, error_message=err)
147            self._notify_qos_specs_error(context,
148                                         'qos_specs.update',
149                                         notifier_err)
150            raise webob.exc.HTTPInternalServerError(
151                explanation=six.text_type(err))
152
153        return body
154
155    def show(self, req, id):
156        """Return a single qos spec item."""
157        context = req.environ['cinder.context']
158        context.authorize(policy.GET_POLICY)
159
160        # Not found exception will be handled at the wsgi level
161        spec = qos_specs.get_qos_specs(context, id)
162
163        return self._view_builder.detail(req, spec)
164
165    def delete(self, req, id):
166        """Deletes an existing qos specs."""
167        context = req.environ['cinder.context']
168        context.authorize(policy.DELETE_POLICY)
169
170        # Convert string to bool type in strict manner
171        force = utils.get_bool_param('force', req.params)
172        LOG.debug("Delete qos_spec: %(id)s, force: %(force)s",
173                  {'id': id, 'force': force})
174        try:
175            spec = qos_specs.get_qos_specs(context, id)
176
177            qos_specs.delete(context, id, force)
178            notifier_info = dict(id=id,
179                                 created_at=spec.created_at,
180                                 deleted_at=timeutils.utcnow())
181            rpc.get_notifier('QoSSpecs').info(context,
182                                              'qos_specs.delete',
183                                              notifier_info)
184        except exception.QoSSpecsNotFound as err:
185            notifier_err = dict(id=id, error_message=err)
186            self._notify_qos_specs_error(context,
187                                         'qos_specs.delete',
188                                         notifier_err)
189            # Not found exception will be handled at the wsgi level
190            raise
191        except exception.QoSSpecsInUse as err:
192            notifier_err = dict(id=id, error_message=err)
193            self._notify_qos_specs_error(context,
194                                         'qos_specs.delete',
195                                         notifier_err)
196            if force:
197                msg = _('Failed to disassociate qos specs.')
198                raise webob.exc.HTTPInternalServerError(explanation=msg)
199            msg = _('Qos specs still in use.')
200            raise webob.exc.HTTPBadRequest(explanation=msg)
201
202        return webob.Response(status_int=http_client.ACCEPTED)
203
204    def delete_keys(self, req, id, body):
205        """Deletes specified keys in qos specs."""
206        context = req.environ['cinder.context']
207        context.authorize(policy.DELETE_POLICY)
208
209        if not (body and 'keys' in body
210                and isinstance(body.get('keys'), list)):
211            raise webob.exc.HTTPBadRequest()
212
213        keys = body['keys']
214        LOG.debug("Delete_key spec: %(id)s, keys: %(keys)s",
215                  {'id': id, 'keys': keys})
216
217        try:
218            qos_specs.delete_keys(context, id, keys)
219            spec = qos_specs.get_qos_specs(context, id)
220            notifier_info = dict(id=id,
221                                 created_at=spec.created_at,
222                                 updated_at=spec.updated_at)
223            rpc.get_notifier('QoSSpecs').info(context, 'qos_specs.delete_keys',
224                                              notifier_info)
225        except exception.NotFound as err:
226            notifier_err = dict(id=id, error_message=err)
227            self._notify_qos_specs_error(context,
228                                         'qos_specs.delete_keys',
229                                         notifier_err)
230            # Not found exception will be handled at the wsgi level
231            raise
232
233        return webob.Response(status_int=http_client.ACCEPTED)
234
235    def associations(self, req, id):
236        """List all associations of given qos specs."""
237        context = req.environ['cinder.context']
238        context.authorize(policy.GET_ALL_POLICY)
239
240        LOG.debug("Get associations for qos_spec id: %s", id)
241
242        try:
243            spec = qos_specs.get_qos_specs(context, id)
244
245            associates = qos_specs.get_associations(context, id)
246            notifier_info = dict(id=id,
247                                 created_at=spec.created_at)
248            rpc.get_notifier('QoSSpecs').info(context,
249                                              'qos_specs.associations',
250                                              notifier_info)
251        except exception.QoSSpecsNotFound as err:
252            notifier_err = dict(id=id, error_message=err)
253            self._notify_qos_specs_error(context,
254                                         'qos_specs.associations',
255                                         notifier_err)
256            # Not found exception will be handled at the wsgi level
257            raise
258        except exception.CinderException as err:
259            notifier_err = dict(id=id, error_message=err)
260            self._notify_qos_specs_error(context,
261                                         'qos_specs.associations',
262                                         notifier_err)
263            raise webob.exc.HTTPInternalServerError(
264                explanation=six.text_type(err))
265
266        return self._view_builder.associations(req, associates)
267
268    def associate(self, req, id):
269        """Associate a qos specs with a volume type."""
270        context = req.environ['cinder.context']
271        context.authorize(policy.UPDATE_POLICY)
272
273        type_id = req.params.get('vol_type_id', None)
274
275        if not type_id:
276            msg = _('Volume Type id must not be None.')
277            notifier_err = dict(id=id, error_message=msg)
278            self._notify_qos_specs_error(context,
279                                         'qos_specs.delete',
280                                         notifier_err)
281            raise webob.exc.HTTPBadRequest(explanation=msg)
282        LOG.debug("Associate qos_spec: %(id)s with type: %(type_id)s",
283                  {'id': id, 'type_id': type_id})
284
285        try:
286            spec = qos_specs.get_qos_specs(context, id)
287
288            qos_specs.associate_qos_with_type(context, id, type_id)
289            notifier_info = dict(id=id, type_id=type_id,
290                                 created_at=spec.created_at)
291            rpc.get_notifier('QoSSpecs').info(context,
292                                              'qos_specs.associate',
293                                              notifier_info)
294        except exception.NotFound as err:
295            notifier_err = dict(id=id, error_message=err)
296            self._notify_qos_specs_error(context,
297                                         'qos_specs.associate',
298                                         notifier_err)
299            # Not found exception will be handled at the wsgi level
300            raise
301        except exception.InvalidVolumeType as err:
302            notifier_err = dict(id=id, error_message=err)
303            self._notify_qos_specs_error(context,
304                                         'qos_specs.associate',
305                                         notifier_err)
306            self._notify_qos_specs_error(context,
307                                         'qos_specs.associate',
308                                         notifier_err)
309            raise webob.exc.HTTPBadRequest(explanation=six.text_type(err))
310        except exception.QoSSpecsAssociateFailed as err:
311            notifier_err = dict(id=id, error_message=err)
312            self._notify_qos_specs_error(context,
313                                         'qos_specs.associate',
314                                         notifier_err)
315            raise webob.exc.HTTPInternalServerError(
316                explanation=six.text_type(err))
317
318        return webob.Response(status_int=http_client.ACCEPTED)
319
320    def disassociate(self, req, id):
321        """Disassociate a qos specs from a volume type."""
322        context = req.environ['cinder.context']
323        context.authorize(policy.UPDATE_POLICY)
324
325        type_id = req.params.get('vol_type_id', None)
326
327        if not type_id:
328            msg = _('Volume Type id must not be None.')
329            notifier_err = dict(id=id, error_message=msg)
330            self._notify_qos_specs_error(context,
331                                         'qos_specs.delete',
332                                         notifier_err)
333            raise webob.exc.HTTPBadRequest(explanation=msg)
334        LOG.debug("Disassociate qos_spec: %(id)s from type: %(type_id)s",
335                  {'id': id, 'type_id': type_id})
336
337        try:
338            spec = qos_specs.get_qos_specs(context, id)
339
340            qos_specs.disassociate_qos_specs(context, id, type_id)
341            notifier_info = dict(id=id, type_id=type_id,
342                                 created_at=spec.created_at)
343            rpc.get_notifier('QoSSpecs').info(context,
344                                              'qos_specs.disassociate',
345                                              notifier_info)
346        except exception.NotFound as err:
347            notifier_err = dict(id=id, error_message=err)
348            self._notify_qos_specs_error(context,
349                                         'qos_specs.disassociate',
350                                         notifier_err)
351            # Not found exception will be handled at the wsgi level
352            raise
353        except exception.QoSSpecsDisassociateFailed as err:
354            notifier_err = dict(id=id, error_message=err)
355            self._notify_qos_specs_error(context,
356                                         'qos_specs.disassociate',
357                                         notifier_err)
358            raise webob.exc.HTTPInternalServerError(
359                explanation=six.text_type(err))
360
361        return webob.Response(status_int=http_client.ACCEPTED)
362
363    def disassociate_all(self, req, id):
364        """Disassociate a qos specs from all volume types."""
365        context = req.environ['cinder.context']
366        context.authorize(policy.UPDATE_POLICY)
367
368        LOG.debug("Disassociate qos_spec: %s from all.", id)
369
370        try:
371            spec = qos_specs.get_qos_specs(context, id)
372
373            qos_specs.disassociate_all(context, id)
374            notifier_info = dict(id=id,
375                                 created_at=spec.created_at)
376            rpc.get_notifier('QoSSpecs').info(context,
377                                              'qos_specs.disassociate_all',
378                                              notifier_info)
379        except exception.QoSSpecsNotFound as err:
380            notifier_err = dict(id=id, error_message=err)
381            self._notify_qos_specs_error(context,
382                                         'qos_specs.disassociate_all',
383                                         notifier_err)
384            # Not found exception will be handled at the wsgi level
385            raise
386        except exception.QoSSpecsDisassociateFailed as err:
387            notifier_err = dict(id=id, error_message=err)
388            self._notify_qos_specs_error(context,
389                                         'qos_specs.disassociate_all',
390                                         notifier_err)
391            raise webob.exc.HTTPInternalServerError(
392                explanation=six.text_type(err))
393
394        return webob.Response(status_int=http_client.ACCEPTED)
395
396
397class Qos_specs_manage(extensions.ExtensionDescriptor):
398    """QoS specs support."""
399
400    name = "Qos_specs_manage"
401    alias = "qos-specs"
402    updated = "2013-08-02T00:00:00+00:00"
403
404    def get_resources(self):
405        resources = []
406        res = extensions.ResourceExtension(
407            Qos_specs_manage.alias,
408            QoSSpecsController(),
409            member_actions={"associations": "GET",
410                            "associate": "GET",
411                            "disassociate": "GET",
412                            "disassociate_all": "GET",
413                            "delete_keys": "PUT"})
414
415        resources.append(res)
416
417        return resources
418