/
image_data.py
250 lines (214 loc) · 9.92 KB
/
image_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# Copyright 2012 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import glance_store
from oslo_log import log as logging
from oslo_utils import excutils
import webob.exc
import glance.api.policy
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
import glance.db
import glance.gateway
from glance import i18n
import glance.notifier
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
class ImageDataController(object):
def __init__(self, db_api=None, store_api=None,
policy_enforcer=None, notifier=None,
gateway=None):
if gateway is None:
db_api = db_api or glance.db.get_api()
store_api = store_api or glance_store
policy = policy_enforcer or glance.api.policy.Enforcer()
notifier = notifier or glance.notifier.Notifier()
gateway = glance.gateway.Gateway(db_api, store_api,
notifier, policy)
self.gateway = gateway
def _restore(self, image_repo, image):
"""
Restore the image to queued status.
:param image_repo: The instance of ImageRepo
:param image: The image will be restored
"""
try:
if image_repo and image:
image.status = 'queued'
image_repo.save(image)
except Exception as e:
msg = (_LE("Unable to restore image %(image_id)s: %(e)s") %
{'image_id': image.image_id,
'e': utils.exception_to_str(e)})
LOG.exception(msg)
@utils.mutating
def upload(self, req, image_id, data, size):
image_repo = self.gateway.get_repo(req.context)
image = None
try:
image = image_repo.get(image_id)
image.status = 'saving'
try:
image_repo.save(image)
image.set_data(data, size)
image_repo.save(image, from_state='saving')
except (exception.NotFound, exception.Conflict):
msg = (_("Image %s could not be found after upload. "
"The image may have been deleted during the "
"upload, cleaning up the chunks uploaded.") %
image_id)
LOG.warn(msg)
# NOTE(sridevi): Cleaning up the uploaded chunks.
try:
image.delete()
except exception.NotFound:
# NOTE(sridevi): Ignore this exception
pass
raise webob.exc.HTTPGone(explanation=msg,
request=req,
content_type='text/plain')
except ValueError as e:
LOG.debug("Cannot save data for image %(id)s: %(e)s",
{'id': image_id, 'e': utils.exception_to_str(e)})
self._restore(image_repo, image)
raise webob.exc.HTTPBadRequest(
explanation=utils.exception_to_str(e))
except glance_store.StoreAddDisabled:
msg = _("Error in store configuration. Adding images to store "
"is disabled.")
LOG.exception(msg)
self._restore(image_repo, image)
raise webob.exc.HTTPGone(explanation=msg, request=req,
content_type='text/plain')
except exception.InvalidImageStatusTransition as e:
msg = utils.exception_to_str(e)
LOG.exception(msg)
raise webob.exc.HTTPConflict(explanation=e.msg, request=req)
except exception.Forbidden as e:
msg = ("Not allowed to upload image data for image %s" %
image_id)
LOG.debug(msg)
raise webob.exc.HTTPForbidden(explanation=msg, request=req)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except glance_store.StorageFull as e:
msg = _("Image storage media "
"is full: %s") % utils.exception_to_str(e)
LOG.error(msg)
self._restore(image_repo, image)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req)
except exception.StorageQuotaFull as e:
msg = _("Image exceeds the storage "
"quota: %s") % utils.exception_to_str(e)
LOG.error(msg)
self._restore(image_repo, image)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req)
except exception.ImageSizeLimitExceeded as e:
msg = _("The incoming image is "
"too large: %s") % utils.exception_to_str(e)
LOG.error(msg)
self._restore(image_repo, image)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
request=req)
except glance_store.StorageWriteDenied as e:
msg = _("Insufficient permissions on image "
"storage media: %s") % utils.exception_to_str(e)
LOG.error(msg)
self._restore(image_repo, image)
raise webob.exc.HTTPServiceUnavailable(explanation=msg,
request=req)
except webob.exc.HTTPGone as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Failed to upload image data due to HTTP error"))
except webob.exc.HTTPError as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Failed to upload image data due to HTTP error"))
self._restore(image_repo, image)
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to upload image data due to "
"internal error"))
self._restore(image_repo, image)
def download(self, req, image_id):
image_repo = self.gateway.get_repo(req.context)
try:
image = image_repo.get(image_id)
if image.status == 'deactivated':
msg = _('The requested image has been deactivated. '
'Image data download is forbidden.')
raise exception.Forbidden(message=msg)
if not image.locations:
raise exception.ImageDataNotFound()
except exception.ImageDataNotFound as e:
raise webob.exc.HTTPNoContent(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
return image
class RequestDeserializer(wsgi.JSONRequestDeserializer):
def upload(self, request):
try:
request.get_content_type(('application/octet-stream',))
except exception.InvalidContentType as e:
raise webob.exc.HTTPUnsupportedMediaType(explanation=e.msg)
image_size = request.content_length or None
return {'size': image_size, 'data': request.body_file}
class ResponseSerializer(wsgi.JSONResponseSerializer):
def download(self, response, image):
offset, chunk_size = 0, None
range_val = response.request.get_content_range()
if range_val:
# NOTE(flaper87): if not present, both, start
# and stop, will be None.
if range_val.start is not None:
offset = range_val.start
if range_val.stop is not None:
chunk_size = range_val.stop - offset
response.headers['Content-Type'] = 'application/octet-stream'
try:
# NOTE(markwash): filesystem store (and maybe others?) cause a
# problem with the caching middleware if they are not wrapped in
# an iterator very strange
response.app_iter = iter(image.get_data(offset=offset,
chunk_size=chunk_size))
except glance_store.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except glance_store.RemoteServiceUnavailable as e:
raise webob.exc.HTTPServiceUnavailable(explanation=e.msg)
except (glance_store.StoreGetNotSupported,
glance_store.StoreRandomGetNotSupported) as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
# NOTE(saschpe): "response.app_iter = ..." currently resets Content-MD5
# (https://github.com/Pylons/webob/issues/86), so it should be set
# afterwards for the time being.
if image.checksum:
response.headers['Content-MD5'] = image.checksum
# NOTE(markwash): "response.app_iter = ..." also erroneously resets the
# content-length
response.headers['Content-Length'] = str(image.size)
def upload(self, response, result):
response.status_int = 204
def create_resource():
"""Image data resource factory method"""
deserializer = RequestDeserializer()
serializer = ResponseSerializer()
controller = ImageDataController()
return wsgi.Resource(controller, deserializer, serializer)