Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / api / images.py @ 6ef51e9f

History | View | Annotate | Download (11.3 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
import dateutil.parser
35

    
36
from django.conf.urls.defaults import patterns
37
from django.http import HttpResponse
38
from django.template.loader import render_to_string
39
from django.utils import simplejson as json
40

    
41
from synnefo.api import util
42
from synnefo.api.common import method_not_allowed
43
from synnefo.api.faults import BadRequest, ItemNotFound, ServiceUnavailable
44
from synnefo.api.util import api_method, isoformat, isoparse
45
from synnefo.plankton.backend import ImageBackend
46
from synnefo.util.log import getLogger
47

    
48

    
49
log = getLogger('synnefo.api')
50

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

    
59
def demux(request):
60
    if request.method == 'GET':
61
        return list_images(request)
62
    elif request.method == 'POST':
63
        return create_image(request)
64
    else:
65
        return method_not_allowed(request)
66

    
67
def image_demux(request, image_id):
68
    if request.method == 'GET':
69
        return get_image_details(request, image_id)
70
    elif request.method == 'DELETE':
71
        return delete_image(request, image_id)
72
    else:
73
        return method_not_allowed(request)
74

    
75
def metadata_demux(request, image_id):
76
    if request.method == 'GET':
77
        return list_metadata(request, image_id)
78
    elif request.method == 'POST':
79
        return update_metadata(request, image_id)
80
    else:
81
        return method_not_allowed(request)
82

    
83
def metadata_item_demux(request, image_id, key):
84
    if request.method == 'GET':
85
        return get_metadata_item(request, image_id, key)
86
    elif request.method == 'PUT':
87
        return create_metadata_item(request, image_id, key)
88
    elif request.method == 'DELETE':
89
        return delete_metadata_item(request, image_id, key)
90
    else:
91
        return method_not_allowed(request)
92

    
93

    
94
def image_to_dict(image, detail=True):
95
    d = dict(id=image['id'], name=image['name'])
96
    if detail:
97
        d['updated'] = isoformat(dateutil.parser.parse(image['updated_at']))
98
        d['created'] = isoformat(dateutil.parser.parse(image['created_at']))
99
        d['status'] = 'DELETED' if image['deleted_at'] else 'ACTIVE'
100
        d['progress'] = 100 if image['status'] == 'available' else 0
101
        if image['properties']:
102
            d['metadata'] = {'values': image['properties']}
103
    return d
104

    
105

    
106
@api_method('GET')
107
def list_images(request, detail=False):
108
    # Normal Response Codes: 200, 203
109
    # Error Response Codes: computeFault (400, 500),
110
    #                       serviceUnavailable (503),
111
    #                       unauthorized (401),
112
    #                       badRequest (400),
113
    #                       overLimit (413)
114
    
115
    log.debug('list_images detail=%s', detail)
116
    backend = ImageBackend(request.user)
117
    
118
    since = isoparse(request.GET.get('changes-since'))
119
    if since:
120
        images = []
121
        for image in backend.iter():
122
            updated = dateutil.parser.parse(image['updated_at'])
123
            if updated >= since:
124
                images.append(image)
125
        if not images:
126
            return HttpResponse(status=304)
127
    else:
128
        images = backend.list()
129
    
130
    reply = [image_to_dict(image, detail) for image in images]
131
    
132
    if request.serialization == 'xml':
133
        data = render_to_string('list_images.xml',
134
                                dict(images=reply, detail=detail))
135
    else:
136
        data = json.dumps(dict(images={'values': reply}))
137
    
138
    return HttpResponse(data, status=200)
139

    
140

    
141
@api_method('POST')
142
def create_image(request):
143
    # Normal Response Code: 202
144
    # Error Response Codes: computeFault (400, 500),
145
    #                       serviceUnavailable (503),
146
    #                       unauthorized (401),
147
    #                       badMediaType(415),
148
    #                       itemNotFound (404),
149
    #                       badRequest (400),
150
    #                       serverCapacityUnavailable (503),
151
    #                       buildInProgress (409),
152
    #                       resizeNotAllowed (403),
153
    #                       backupOrResizeInProgress (409),
154
    #                       overLimit (413)
155
    
156
    raise ServiceUnavailable('Not supported.')
157

    
158

    
159
@api_method('GET')
160
def get_image_details(request, image_id):
161
    # Normal Response Codes: 200, 203
162
    # Error Response Codes: computeFault (400, 500),
163
    #                       serviceUnavailable (503),
164
    #                       unauthorized (401),
165
    #                       badRequest (400),
166
    #                       itemNotFound (404),
167
    #                       overLimit (413)
168
    
169
    log.debug('get_image_details %s', image_id)
170
    image = util.get_image(image_id, request.user)
171
    reply = image_to_dict(image)
172
    
173
    if request.serialization == 'xml':
174
        data = render_to_string('image.xml', dict(image=reply))
175
    else:
176
        data = json.dumps(dict(image=reply))
177
    
178
    return HttpResponse(data, status=200)
179

    
180

    
181
@api_method('DELETE')
182
def delete_image(request, image_id):
183
    # Normal Response Code: 204
184
    # Error Response Codes: computeFault (400, 500),
185
    #                       serviceUnavailable (503),
186
    #                       unauthorized (401),
187
    #                       itemNotFound (404),
188
    #                       overLimit (413)
189
    
190
    log.debug('delete_image %s', image_id)
191
    backend = ImageBackend(request.user)
192
    backend.delete(image_id)
193
    backend.close()
194
    log.info('User %s deleted image %s', request.user, image_id)
195
    return HttpResponse(status=204)
196

    
197

    
198
@api_method('GET')
199
def list_metadata(request, image_id):
200
    # Normal Response Codes: 200, 203
201
    # Error Response Codes: computeFault (400, 500),
202
    #                       serviceUnavailable (503),
203
    #                       unauthorized (401),
204
    #                       badRequest (400),
205
    #                       overLimit (413)
206
    
207
    log.debug('list_image_metadata %s', image_id)
208
    image = util.get_image(image_id, request.user)
209
    metadata = image['properties']
210
    return util.render_metadata(request, metadata, use_values=True, status=200)
211

    
212

    
213
@api_method('POST')
214
def update_metadata(request, image_id):
215
    # Normal Response Code: 201
216
    # Error Response Codes: computeFault (400, 500),
217
    #                       serviceUnavailable (503),
218
    #                       unauthorized (401),
219
    #                       badRequest (400),
220
    #                       buildInProgress (409),
221
    #                       badMediaType(415),
222
    #                       overLimit (413)
223
    
224
    req = util.get_request_dict(request)
225
    log.debug('update_image_metadata %s %s', image_id, req)
226
    image = util.get_image(image_id, request.user)
227
    try:
228
        metadata = req['metadata']
229
        assert isinstance(metadata, dict)
230
    except (KeyError, AssertionError):
231
        raise BadRequest('Malformed request.')
232
    
233
    properties = image['properties']
234
    properties.update(metadata)
235
    
236
    backend = ImageBackend(request.user)
237
    backend.update(image_id, dict(properties=properties))
238
    backend.close()
239
    
240
    return util.render_metadata(request, properties, status=201)
241

    
242

    
243
@api_method('GET')
244
def get_metadata_item(request, image_id, key):
245
    # Normal Response Codes: 200, 203
246
    # Error Response Codes: computeFault (400, 500),
247
    #                       serviceUnavailable (503),
248
    #                       unauthorized (401),
249
    #                       itemNotFound (404),
250
    #                       badRequest (400),
251
    #                       overLimit (413)
252
    
253
    log.debug('get_image_metadata_item %s %s', image_id, key)
254
    image = util.get_image(image_id, request.user)
255
    val = image['properties'].get(key)
256
    if val is None:
257
        raise ItemNotFound('Metadata key not found.')
258
    return util.render_meta(request, {key: val}, status=200)
259

    
260

    
261
@api_method('PUT')
262
def create_metadata_item(request, image_id, key):
263
    # Normal Response Code: 201
264
    # Error Response Codes: computeFault (400, 500),
265
    #                       serviceUnavailable (503),
266
    #                       unauthorized (401),
267
    #                       itemNotFound (404),
268
    #                       badRequest (400),
269
    #                       buildInProgress (409),
270
    #                       badMediaType(415),
271
    #                       overLimit (413)
272
    
273
    req = util.get_request_dict(request)
274
    log.debug('create_image_metadata_item %s %s %s', image_id, key, req)
275
    try:
276
        metadict = req['meta']
277
        assert isinstance(metadict, dict)
278
        assert len(metadict) == 1
279
        assert key in metadict
280
    except (KeyError, AssertionError):
281
        raise BadRequest('Malformed request.')
282

    
283
    val = metadict[key]
284
    image = util.get_image(image_id, request.user)
285
    properties = image['properties']
286
    properties[key] = val
287
    
288
    backend = ImageBackend(request.user)
289
    backend.update(image_id, dict(properties=properties))
290
    backend.close()
291
    
292
    return util.render_meta(request, {key: val}, status=201)
293

    
294

    
295
@api_method('DELETE')
296
def delete_metadata_item(request, image_id, key):
297
    # Normal Response Code: 204
298
    # Error Response Codes: computeFault (400, 500),
299
    #                       serviceUnavailable (503),
300
    #                       unauthorized (401),
301
    #                       itemNotFound (404),
302
    #                       badRequest (400),
303
    #                       buildInProgress (409),
304
    #                       badMediaType(415),
305
    #                       overLimit (413),
306
    
307
    log.debug('delete_image_metadata_item %s %s', image_id, key)
308
    image = util.get_image(image_id, request.user)
309
    properties = image['properties']
310
    properties.pop(key, None)
311
    
312
    backend = ImageBackend(request.user)
313
    backend.update(image_id, dict(properties=properties))
314
    backend.close()
315
    
316
    return HttpResponse(status=204)