Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / images.py @ d984eedc

History | View | Annotate | Download (12.6 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from logging import getLogger
35
from itertools import ifilter
36

    
37
from dateutil.parser import parse as date_parse
38

    
39
from django.conf.urls import patterns
40
from django.http import HttpResponse
41
from django.template.loader import render_to_string
42
from django.utils import simplejson as json
43

    
44
from snf_django.lib import api
45
from snf_django.lib.api import faults, utils
46
from synnefo.api import util
47
from synnefo.plankton.utils import image_backend
48

    
49

    
50
log = getLogger(__name__)
51

    
52
urlpatterns = patterns(
53
    'synnefo.api.images',
54
    (r'^(?:/|.json|.xml)?$', 'demux'),
55
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
56
    (r'^/([\w-]+)(?:.json|.xml)?$', 'image_demux'),
57
    (r'^/([\w-]+)/metadata(?:.json|.xml)?$', 'metadata_demux'),
58
    (r'^/([\w-]+)/metadata/(.+?)(?:.json|.xml)?$', 'metadata_item_demux')
59
)
60

    
61

    
62
def demux(request):
63
    if request.method == 'GET':
64
        return list_images(request)
65
    elif request.method == 'POST':
66
        return create_image(request)
67
    else:
68
        return api.api_method_not_allowed(request,
69
                                          allowed_methods=['GET', 'POST'])
70

    
71

    
72
def image_demux(request, image_id):
73
    if request.method == 'GET':
74
        return get_image_details(request, image_id)
75
    elif request.method == 'DELETE':
76
        return delete_image(request, image_id)
77
    else:
78
        return api.api_method_not_allowed(request,
79
                                          allowed_methods=['GET', 'DELETE'])
80

    
81

    
82
def metadata_demux(request, image_id):
83
    if request.method == 'GET':
84
        return list_metadata(request, image_id)
85
    elif request.method == 'POST':
86
        return update_metadata(request, image_id)
87
    else:
88
        return api.api_method_not_allowed(request,
89
                                          allowed_methods=['GET', 'POST'])
90

    
91

    
92
def metadata_item_demux(request, image_id, key):
93
    if request.method == 'GET':
94
        return get_metadata_item(request, image_id, key)
95
    elif request.method == 'PUT':
96
        return create_metadata_item(request, image_id, key)
97
    elif request.method == 'DELETE':
98
        return delete_metadata_item(request, image_id, key)
99
    else:
100
        return api.api_method_not_allowed(request,
101
                                          allowed_methods=['GET',
102
                                                           'PUT',
103
                                                           'DELETE'])
104

    
105

    
106
API_STATUS_FROM_IMAGE_STATUS = {
107
    "CREATING": "SAVING",
108
    "AVAILABLE": "ACTIVE",
109
    "ERROR": "ERROR",
110
    "DELETED": "DELETED"}
111

    
112

    
113
def image_to_dict(image, detail=True):
114
    d = dict(id=image['id'], name=image['name'])
115
    if detail:
116
        d['updated'] = utils.isoformat(date_parse(image['updated_at']))
117
        d['created'] = utils.isoformat(date_parse(image['created_at']))
118
        img_status = image.get("status", "").upper()
119
        status = API_STATUS_FROM_IMAGE_STATUS.get(img_status, "UNKNOWN")
120
        d['status'] = status
121
        d['progress'] = 100 if status == 'ACTIVE' else 0
122
        d['user_id'] = image['owner']
123
        d['tenant_id'] = image['owner']
124
        d['public'] = image["is_public"]
125
        d['links'] = util.image_to_links(image["id"])
126
        if image["properties"]:
127
            d['metadata'] = image['properties']
128
        else:
129
            d['metadata'] = {}
130
        d["is_snapshot"] = image["is_snapshot"]
131
    return d
132

    
133

    
134
@api.api_method("GET", user_required=True, logger=log)
135
def list_images(request, detail=False):
136
    # Normal Response Codes: 200, 203
137
    # Error Response Codes: computeFault (400, 500),
138
    #                       serviceUnavailable (503),
139
    #                       unauthorized (401),
140
    #                       badRequest (400),
141
    #                       overLimit (413)
142

    
143
    log.debug('list_images detail=%s', detail)
144
    since = utils.isoparse(request.GET.get('changes-since'))
145
    with image_backend(request.user_uniq) as backend:
146
        images = backend.list_images()
147
        if since:
148
            updated_since = lambda img: date_parse(img["updated_at"]) >= since
149
            images = ifilter(updated_since, images)
150
            if not images:
151
                return HttpResponse(status=304)
152

    
153
    images = sorted(images, key=lambda x: x['id'])
154
    reply = [image_to_dict(image, detail) for image in images]
155

    
156
    if request.serialization == 'xml':
157
        data = render_to_string('list_images.xml',
158
                                dict(images=reply, detail=detail))
159
    else:
160
        data = json.dumps(dict(images=reply))
161

    
162
    return HttpResponse(data, status=200)
163

    
164

    
165
@api.api_method('POST', user_required=True, logger=log)
166
def create_image(request):
167
    # Normal Response Code: 202
168
    # Error Response Codes: computeFault (400, 500),
169
    #                       serviceUnavailable (503),
170
    #                       unauthorized (401),
171
    #                       badMediaType(415),
172
    #                       itemNotFound (404),
173
    #                       badRequest (400),
174
    #                       serverCapacityUnavailable (503),
175
    #                       buildInProgress (409),
176
    #                       resizeNotAllowed (403),
177
    #                       backupOrResizeInProgress (409),
178
    #                       overLimit (413)
179

    
180
    raise faults.NotImplemented('Not supported.')
181

    
182

    
183
@api.api_method('GET', user_required=True, logger=log)
184
def get_image_details(request, image_id):
185
    # Normal Response Codes: 200, 203
186
    # Error Response Codes: computeFault (400, 500),
187
    #                       serviceUnavailable (503),
188
    #                       unauthorized (401),
189
    #                       badRequest (400),
190
    #                       itemNotFound (404),
191
    #                       overLimit (413)
192

    
193
    log.debug('get_image_details %s', image_id)
194
    with image_backend(request.user_uniq) as backend:
195
        image = backend.get_image(image_id)
196
    reply = image_to_dict(image)
197

    
198
    if request.serialization == 'xml':
199
        data = render_to_string('image.xml', dict(image=reply))
200
    else:
201
        data = json.dumps(dict(image=reply))
202

    
203
    return HttpResponse(data, status=200)
204

    
205

    
206
@api.api_method('DELETE', user_required=True, logger=log)
207
def delete_image(request, image_id):
208
    # Normal Response Code: 204
209
    # Error Response Codes: computeFault (400, 500),
210
    #                       serviceUnavailable (503),
211
    #                       unauthorized (401),
212
    #                       itemNotFound (404),
213
    #                       overLimit (413)
214

    
215
    log.info('delete_image %s', image_id)
216
    with image_backend(request.user_uniq) as backend:
217
        backend.unregister(image_id)
218
    log.info('User %s deleted image %s', request.user_uniq, image_id)
219
    return HttpResponse(status=204)
220

    
221

    
222
@api.api_method('GET', user_required=True, logger=log)
223
def list_metadata(request, image_id):
224
    # Normal Response Codes: 200, 203
225
    # Error Response Codes: computeFault (400, 500),
226
    #                       serviceUnavailable (503),
227
    #                       unauthorized (401),
228
    #                       badRequest (400),
229
    #                       overLimit (413)
230

    
231
    log.debug('list_image_metadata %s', image_id)
232
    with image_backend(request.user_uniq) as backend:
233
        image = backend.get_image(image_id)
234
    metadata = image['properties']
235
    return util.render_metadata(request, metadata, use_values=False,
236
                                status=200)
237

    
238

    
239
@api.api_method('POST', user_required=True, logger=log)
240
def update_metadata(request, image_id):
241
    # Normal Response Code: 201
242
    # Error Response Codes: computeFault (400, 500),
243
    #                       serviceUnavailable (503),
244
    #                       unauthorized (401),
245
    #                       badRequest (400),
246
    #                       buildInProgress (409),
247
    #                       badMediaType(415),
248
    #                       overLimit (413)
249

    
250
    req = utils.get_request_dict(request)
251
    log.info('update_image_metadata %s %s', image_id, req)
252
    with image_backend(request.user_uniq) as backend:
253
        image = backend.get_image(image_id)
254
        try:
255
            metadata = req['metadata']
256
            assert isinstance(metadata, dict)
257
        except (KeyError, AssertionError):
258
            raise faults.BadRequest('Malformed request.')
259

    
260
        properties = image['properties']
261
        properties.update(metadata)
262

    
263
        backend.update_metadata(image_id, dict(properties=properties))
264

    
265
    return util.render_metadata(request, properties, status=201)
266

    
267

    
268
@api.api_method('GET', user_required=True, logger=log)
269
def get_metadata_item(request, image_id, key):
270
    # Normal Response Codes: 200, 203
271
    # Error Response Codes: computeFault (400, 500),
272
    #                       serviceUnavailable (503),
273
    #                       unauthorized (401),
274
    #                       itemNotFound (404),
275
    #                       badRequest (400),
276
    #                       overLimit (413)
277

    
278
    log.debug('get_image_metadata_item %s %s', image_id, key)
279
    with image_backend(request.user_uniq) as backend:
280
        image = backend.get_image(image_id)
281
    val = image['properties'].get(key)
282
    if val is None:
283
        raise faults.ItemNotFound('Metadata key not found.')
284
    return util.render_meta(request, {key: val}, status=200)
285

    
286

    
287
@api.api_method('PUT', user_required=True, logger=log)
288
def create_metadata_item(request, image_id, key):
289
    # Normal Response Code: 201
290
    # Error Response Codes: computeFault (400, 500),
291
    #                       serviceUnavailable (503),
292
    #                       unauthorized (401),
293
    #                       itemNotFound (404),
294
    #                       badRequest (400),
295
    #                       buildInProgress (409),
296
    #                       badMediaType(415),
297
    #                       overLimit (413)
298

    
299
    req = utils.get_request_dict(request)
300
    log.info('create_image_metadata_item %s %s %s', image_id, key, req)
301
    try:
302
        metadict = req['meta']
303
        assert isinstance(metadict, dict)
304
        assert len(metadict) == 1
305
        assert key in metadict
306
    except (KeyError, AssertionError):
307
        raise faults.BadRequest('Malformed request.')
308

    
309
    val = metadict[key]
310
    with image_backend(request.user_uniq) as backend:
311
        image = backend.get_image(image_id)
312
        properties = image['properties']
313
        properties[key] = val
314

    
315
        backend.update_metadata(image_id, dict(properties=properties))
316

    
317
    return util.render_meta(request, {key: val}, status=201)
318

    
319

    
320
@api.api_method('DELETE', user_required=True, logger=log)
321
def delete_metadata_item(request, image_id, key):
322
    # Normal Response Code: 204
323
    # Error Response Codes: computeFault (400, 500),
324
    #                       serviceUnavailable (503),
325
    #                       unauthorized (401),
326
    #                       itemNotFound (404),
327
    #                       badRequest (400),
328
    #                       buildInProgress (409),
329
    #                       badMediaType(415),
330
    #                       overLimit (413),
331

    
332
    log.info('delete_image_metadata_item %s %s', image_id, key)
333
    with image_backend(request.user_uniq) as backend:
334
        image = backend.get_image(image_id)
335
        properties = image['properties']
336
        properties.pop(key, None)
337

    
338
        backend.update_metadata(image_id, dict(properties=properties))
339

    
340
    return HttpResponse(status=204)