1# Copyright 2013 OpenStack Foundation.
2# All Rights Reserved.
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
16from six.moves import http_client
17import webob
18
19from cinder.api import common
20from cinder.api.openstack import wsgi
21from cinder.api.schemas import volume_metadata as metadata
22from cinder.api import validation
23from cinder import exception
24from cinder.i18n import _
25from cinder import volume
26
27
28class Controller(wsgi.Controller):
29    """The volume metadata API controller for the OpenStack API."""
30
31    def __init__(self):
32        self.volume_api = volume.API()
33        super(Controller, self).__init__()
34
35    def _get_metadata(self, context, volume_id):
36        # The metadata is at the second position of the tuple returned
37        # from _get_volume_and_metadata
38        return self._get_volume_and_metadata(context, volume_id)[1]
39
40    def _get_volume_and_metadata(self, context, volume_id):
41        # Not found exception will be handled at the wsgi level
42        volume = self.volume_api.get(context, volume_id)
43        meta = self.volume_api.get_volume_metadata(context, volume)
44        return (volume, meta)
45
46    def index(self, req, volume_id):
47        """Returns the list of metadata for a given volume."""
48        context = req.environ['cinder.context']
49        return {'metadata': self._get_metadata(context, volume_id)}
50
51    @validation.schema(metadata.create)
52    def create(self, req, volume_id, body):
53        context = req.environ['cinder.context']
54        metadata = body['metadata']
55
56        new_metadata = self._update_volume_metadata(context, volume_id,
57                                                    metadata, delete=False,
58                                                    use_create=True)
59        return {'metadata': new_metadata}
60
61    @validation.schema(metadata.update)
62    def update(self, req, volume_id, id, body):
63        meta_item = body['meta']
64
65        if id not in meta_item:
66            expl = _('Request body and URI mismatch')
67            raise webob.exc.HTTPBadRequest(explanation=expl)
68
69        if len(meta_item) > 1:
70            expl = _('Request body contains too many items')
71            raise webob.exc.HTTPBadRequest(explanation=expl)
72
73        context = req.environ['cinder.context']
74        self._update_volume_metadata(context,
75                                     volume_id,
76                                     meta_item,
77                                     delete=False)
78
79        return {'meta': meta_item}
80
81    @validation.schema(metadata.create)
82    def update_all(self, req, volume_id, body):
83        metadata = body['metadata']
84        context = req.environ['cinder.context']
85
86        new_metadata = self._update_volume_metadata(context,
87                                                    volume_id,
88                                                    metadata,
89                                                    delete=True)
90
91        return {'metadata': new_metadata}
92
93    def _update_volume_metadata(self, context, volume_id, metadata,
94                                delete=False, use_create=False):
95        try:
96            volume = self.volume_api.get(context, volume_id)
97            if use_create:
98                return self.volume_api.create_volume_metadata(context, volume,
99                                                              metadata)
100            else:
101                return self.volume_api.update_volume_metadata(
102                    context, volume, metadata, delete,
103                    meta_type=common.METADATA_TYPES.user)
104        # Not found exception will be handled at the wsgi level
105        except (ValueError, AttributeError):
106            msg = _("Malformed request body")
107            raise webob.exc.HTTPBadRequest(explanation=msg)
108
109        except exception.InvalidVolumeMetadata as error:
110            raise webob.exc.HTTPBadRequest(explanation=error.msg)
111
112        except exception.InvalidVolumeMetadataSize as error:
113            raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
114
115    def show(self, req, volume_id, id):
116        """Return a single metadata item."""
117        context = req.environ['cinder.context']
118        data = self._get_metadata(context, volume_id)
119
120        try:
121            return {'meta': {id: data[id]}}
122        except KeyError:
123            raise exception.VolumeMetadataNotFound(volume_id=volume_id,
124                                                   metadata_key=id)
125
126    def delete(self, req, volume_id, id):
127        """Deletes an existing metadata."""
128        context = req.environ['cinder.context']
129
130        volume, metadata = self._get_volume_and_metadata(context, volume_id)
131
132        if id not in metadata:
133            raise exception.VolumeMetadataNotFound(volume_id=volume_id,
134                                                   metadata_key=id)
135
136        # Not found exception will be handled at the wsgi level
137        self.volume_api.delete_volume_metadata(
138            context,
139            volume,
140            id,
141            meta_type=common.METADATA_TYPES.user)
142        return webob.Response(status_int=http_client.OK)
143
144
145def create_resource():
146    return wsgi.Resource(Controller())
147