Statistics
| Branch: | Tag: | Revision:

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

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
from logging import getLogger
35

    
36
import dateutil.parser
37

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

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

    
49

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

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

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

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

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

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

    
94

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

    
106

    
107
@api_method('GET')
108
def list_images(request, detail=False):
109
    # Normal Response Codes: 200, 203
110
    # Error Response Codes: computeFault (400, 500),
111
    #                       serviceUnavailable (503),
112
    #                       unauthorized (401),
113
    #                       badRequest (400),
114
    #                       overLimit (413)
115

    
116
    log.debug('list_images detail=%s', detail)
117
    backend = ImageBackend(request.user_uniq)
118

    
119
    since = isoparse(request.GET.get('changes-since'))
120
    if since:
121
        images = []
122
        for image in backend.iter():
123
            updated = dateutil.parser.parse(image['updated_at'])
124
            if updated >= since:
125
                images.append(image)
126
        if not images:
127
            return HttpResponse(status=304)
128
    else:
129
        images = backend.list()
130

    
131
    images = sorted(images, key=lambda x: x['id'])
132
    reply = [image_to_dict(image, detail) for image in images]
133

    
134
    if request.serialization == 'xml':
135
        data = render_to_string('list_images.xml',
136
                                dict(images=reply, detail=detail))
137
    else:
138
        data = json.dumps(dict(images={'values': reply}))
139

    
140
    return HttpResponse(data, status=200)
141

    
142

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

    
158
    raise ServiceUnavailable('Not supported.')
159

    
160

    
161
@api_method('GET')
162
def get_image_details(request, image_id):
163
    # Normal Response Codes: 200, 203
164
    # Error Response Codes: computeFault (400, 500),
165
    #                       serviceUnavailable (503),
166
    #                       unauthorized (401),
167
    #                       badRequest (400),
168
    #                       itemNotFound (404),
169
    #                       overLimit (413)
170

    
171
    log.debug('get_image_details %s', image_id)
172
    image = util.get_image(image_id, request.user_uniq)
173
    reply = image_to_dict(image)
174

    
175
    if request.serialization == 'xml':
176
        data = render_to_string('image.xml', dict(image=reply))
177
    else:
178
        data = json.dumps(dict(image=reply))
179

    
180
    return HttpResponse(data, status=200)
181

    
182

    
183
@api_method('DELETE')
184
def delete_image(request, image_id):
185
    # Normal Response Code: 204
186
    # Error Response Codes: computeFault (400, 500),
187
    #                       serviceUnavailable (503),
188
    #                       unauthorized (401),
189
    #                       itemNotFound (404),
190
    #                       overLimit (413)
191

    
192
    log.info('delete_image %s', image_id)
193
    backend = ImageBackend(request.user_uniq)
194
    backend.delete(image_id)
195
    backend.close()
196
    log.info('User %s deleted image %s', request.user_uniq, image_id)
197
    return HttpResponse(status=204)
198

    
199

    
200
@api_method('GET')
201
def list_metadata(request, image_id):
202
    # Normal Response Codes: 200, 203
203
    # Error Response Codes: computeFault (400, 500),
204
    #                       serviceUnavailable (503),
205
    #                       unauthorized (401),
206
    #                       badRequest (400),
207
    #                       overLimit (413)
208

    
209
    log.debug('list_image_metadata %s', image_id)
210
    image = util.get_image(image_id, request.user_uniq)
211
    metadata = image['properties']
212
    return util.render_metadata(request, metadata, use_values=True, status=200)
213

    
214

    
215
@api_method('POST')
216
def update_metadata(request, image_id):
217
    # Normal Response Code: 201
218
    # Error Response Codes: computeFault (400, 500),
219
    #                       serviceUnavailable (503),
220
    #                       unauthorized (401),
221
    #                       badRequest (400),
222
    #                       buildInProgress (409),
223
    #                       badMediaType(415),
224
    #                       overLimit (413)
225

    
226
    req = util.get_request_dict(request)
227
    log.info('update_image_metadata %s %s', image_id, req)
228
    image = util.get_image(image_id, request.user_uniq)
229
    try:
230
        metadata = req['metadata']
231
        assert isinstance(metadata, dict)
232
    except (KeyError, AssertionError):
233
        raise BadRequest('Malformed request.')
234

    
235
    properties = image['properties']
236
    properties.update(metadata)
237

    
238
    backend = ImageBackend(request.user_uniq)
239
    backend.update(image_id, dict(properties=properties))
240
    backend.close()
241

    
242
    return util.render_metadata(request, properties, status=201)
243

    
244

    
245
@api_method('GET')
246
def get_metadata_item(request, image_id, key):
247
    # Normal Response Codes: 200, 203
248
    # Error Response Codes: computeFault (400, 500),
249
    #                       serviceUnavailable (503),
250
    #                       unauthorized (401),
251
    #                       itemNotFound (404),
252
    #                       badRequest (400),
253
    #                       overLimit (413)
254

    
255
    log.debug('get_image_metadata_item %s %s', image_id, key)
256
    image = util.get_image(image_id, request.user_uniq)
257
    val = image['properties'].get(key)
258
    if val is None:
259
        raise ItemNotFound('Metadata key not found.')
260
    return util.render_meta(request, {key: val}, status=200)
261

    
262

    
263
@api_method('PUT')
264
def create_metadata_item(request, image_id, key):
265
    # Normal Response Code: 201
266
    # Error Response Codes: computeFault (400, 500),
267
    #                       serviceUnavailable (503),
268
    #                       unauthorized (401),
269
    #                       itemNotFound (404),
270
    #                       badRequest (400),
271
    #                       buildInProgress (409),
272
    #                       badMediaType(415),
273
    #                       overLimit (413)
274

    
275
    req = util.get_request_dict(request)
276
    log.info('create_image_metadata_item %s %s %s', image_id, key, req)
277
    try:
278
        metadict = req['meta']
279
        assert isinstance(metadict, dict)
280
        assert len(metadict) == 1
281
        assert key in metadict
282
    except (KeyError, AssertionError):
283
        raise BadRequest('Malformed request.')
284

    
285
    val = metadict[key]
286
    image = util.get_image(image_id, request.user_uniq)
287
    properties = image['properties']
288
    properties[key] = val
289

    
290
    backend = ImageBackend(request.user_uniq)
291
    backend.update(image_id, dict(properties=properties))
292
    backend.close()
293

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

    
296

    
297
@api_method('DELETE')
298
def delete_metadata_item(request, image_id, key):
299
    # Normal Response Code: 204
300
    # Error Response Codes: computeFault (400, 500),
301
    #                       serviceUnavailable (503),
302
    #                       unauthorized (401),
303
    #                       itemNotFound (404),
304
    #                       badRequest (400),
305
    #                       buildInProgress (409),
306
    #                       badMediaType(415),
307
    #                       overLimit (413),
308

    
309
    log.info('delete_image_metadata_item %s %s', image_id, key)
310
    image = util.get_image(image_id, request.user_uniq)
311
    properties = image['properties']
312
    properties.pop(key, None)
313

    
314
    backend = ImageBackend(request.user_uniq)
315
    backend.update(image_id, dict(properties=properties))
316
    backend.close()
317

    
318
    return HttpResponse(status=204)