Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.5 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 contextlib import contextmanager
44

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

    
51

    
52
log = getLogger('synnefo.api')
53

    
54
urlpatterns = patterns('synnefo.api.images',
55
    (r'^(?:/|.json|.xml)?$', 'demux'),
56
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
57
    (r'^/([\w-]+)(?:.json|.xml)?$', 'image_demux'),
58
    (r'^/([\w-]+)/meta(?:.json|.xml)?$', 'metadata_demux'),
59
    (r'^/([\w-]+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux')
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 method_not_allowed(request)
69

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

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

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

    
96

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

    
108

    
109
@contextmanager
110
def image_backend(userid):
111
    backend = ImageBackend(userid)
112
    try:
113
        yield backend
114
    finally:
115
        backend.close()
116

    
117
@api_method('GET')
118
def list_images(request, detail=False):
119
    # Normal Response Codes: 200, 203
120
    # Error Response Codes: computeFault (400, 500),
121
    #                       serviceUnavailable (503),
122
    #                       unauthorized (401),
123
    #                       badRequest (400),
124
    #                       overLimit (413)
125

    
126
    log.debug('list_images detail=%s', detail)
127
    with image_backend(request.user_uniq) as backend:
128
        since = isoparse(request.GET.get('changes-since'))
129
        if since:
130
            images = []
131
            for image in backend.iter():
132
                updated = dateutil.parser.parse(image['updated_at'])
133
                if updated >= since:
134
                    images.append(image)
135
            if not images:
136
                return HttpResponse(status=304)
137
        else:
138
            images = backend.list()
139

    
140
    images = sorted(images, key=lambda x: x['id'])
141
    reply = [image_to_dict(image, detail) for image in images]
142

    
143
    if request.serialization == 'xml':
144
        data = render_to_string('list_images.xml',
145
                                dict(images=reply, detail=detail))
146
    else:
147
        data = json.dumps(dict(images={'values': reply}))
148

    
149
    return HttpResponse(data, status=200)
150

    
151

    
152
@api_method('POST')
153
def create_image(request):
154
    # Normal Response Code: 202
155
    # Error Response Codes: computeFault (400, 500),
156
    #                       serviceUnavailable (503),
157
    #                       unauthorized (401),
158
    #                       badMediaType(415),
159
    #                       itemNotFound (404),
160
    #                       badRequest (400),
161
    #                       serverCapacityUnavailable (503),
162
    #                       buildInProgress (409),
163
    #                       resizeNotAllowed (403),
164
    #                       backupOrResizeInProgress (409),
165
    #                       overLimit (413)
166

    
167
    raise ServiceUnavailable('Not supported.')
168

    
169

    
170
@api_method('GET')
171
def get_image_details(request, image_id):
172
    # Normal Response Codes: 200, 203
173
    # Error Response Codes: computeFault (400, 500),
174
    #                       serviceUnavailable (503),
175
    #                       unauthorized (401),
176
    #                       badRequest (400),
177
    #                       itemNotFound (404),
178
    #                       overLimit (413)
179

    
180
    log.debug('get_image_details %s', image_id)
181
    image = util.get_image(image_id, request.user_uniq)
182
    reply = image_to_dict(image)
183

    
184
    if request.serialization == 'xml':
185
        data = render_to_string('image.xml', dict(image=reply))
186
    else:
187
        data = json.dumps(dict(image=reply))
188

    
189
    return HttpResponse(data, status=200)
190

    
191

    
192
@api_method('DELETE')
193
def delete_image(request, image_id):
194
    # Normal Response Code: 204
195
    # Error Response Codes: computeFault (400, 500),
196
    #                       serviceUnavailable (503),
197
    #                       unauthorized (401),
198
    #                       itemNotFound (404),
199
    #                       overLimit (413)
200

    
201
    log.info('delete_image %s', image_id)
202
    with image_backend(request.user_uniq) as backend:
203
        backend.delete(image_id)
204
    log.info('User %s deleted image %s', request.user_uniq, image_id)
205
    return HttpResponse(status=204)
206

    
207

    
208
@api_method('GET')
209
def list_metadata(request, image_id):
210
    # Normal Response Codes: 200, 203
211
    # Error Response Codes: computeFault (400, 500),
212
    #                       serviceUnavailable (503),
213
    #                       unauthorized (401),
214
    #                       badRequest (400),
215
    #                       overLimit (413)
216

    
217
    log.debug('list_image_metadata %s', image_id)
218
    image = util.get_image(image_id, request.user_uniq)
219
    metadata = image['properties']
220
    return util.render_metadata(request, metadata, use_values=True, status=200)
221

    
222

    
223
@api_method('POST')
224
def update_metadata(request, image_id):
225
    # Normal Response Code: 201
226
    # Error Response Codes: computeFault (400, 500),
227
    #                       serviceUnavailable (503),
228
    #                       unauthorized (401),
229
    #                       badRequest (400),
230
    #                       buildInProgress (409),
231
    #                       badMediaType(415),
232
    #                       overLimit (413)
233

    
234
    req = util.get_request_dict(request)
235
    log.info('update_image_metadata %s %s', image_id, req)
236
    image = util.get_image(image_id, request.user_uniq)
237
    try:
238
        metadata = req['metadata']
239
        assert isinstance(metadata, dict)
240
    except (KeyError, AssertionError):
241
        raise BadRequest('Malformed request.')
242

    
243
    properties = image['properties']
244
    properties.update(metadata)
245

    
246
    with image_backend(request.user_uniq) as backend:
247
        backend.update(image_id, dict(properties=properties))
248

    
249
    return util.render_metadata(request, properties, status=201)
250

    
251

    
252
@api_method('GET')
253
def get_metadata_item(request, image_id, key):
254
    # Normal Response Codes: 200, 203
255
    # Error Response Codes: computeFault (400, 500),
256
    #                       serviceUnavailable (503),
257
    #                       unauthorized (401),
258
    #                       itemNotFound (404),
259
    #                       badRequest (400),
260
    #                       overLimit (413)
261

    
262
    log.debug('get_image_metadata_item %s %s', image_id, key)
263
    image = util.get_image(image_id, request.user_uniq)
264
    val = image['properties'].get(key)
265
    if val is None:
266
        raise ItemNotFound('Metadata key not found.')
267
    return util.render_meta(request, {key: val}, status=200)
268

    
269

    
270
@api_method('PUT')
271
def create_metadata_item(request, image_id, key):
272
    # Normal Response Code: 201
273
    # Error Response Codes: computeFault (400, 500),
274
    #                       serviceUnavailable (503),
275
    #                       unauthorized (401),
276
    #                       itemNotFound (404),
277
    #                       badRequest (400),
278
    #                       buildInProgress (409),
279
    #                       badMediaType(415),
280
    #                       overLimit (413)
281

    
282
    req = util.get_request_dict(request)
283
    log.info('create_image_metadata_item %s %s %s', image_id, key, req)
284
    try:
285
        metadict = req['meta']
286
        assert isinstance(metadict, dict)
287
        assert len(metadict) == 1
288
        assert key in metadict
289
    except (KeyError, AssertionError):
290
        raise BadRequest('Malformed request.')
291

    
292
    val = metadict[key]
293
    image = util.get_image(image_id, request.user_uniq)
294
    properties = image['properties']
295
    properties[key] = val
296

    
297
    with image_backend(request.user_uniq) as backend:
298
        backend.update(image_id, dict(properties=properties))
299

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

    
302

    
303
@api_method('DELETE')
304
def delete_metadata_item(request, image_id, key):
305
    # Normal Response Code: 204
306
    # Error Response Codes: computeFault (400, 500),
307
    #                       serviceUnavailable (503),
308
    #                       unauthorized (401),
309
    #                       itemNotFound (404),
310
    #                       badRequest (400),
311
    #                       buildInProgress (409),
312
    #                       badMediaType(415),
313
    #                       overLimit (413),
314

    
315
    log.info('delete_image_metadata_item %s %s', image_id, key)
316
    image = util.get_image(image_id, request.user_uniq)
317
    properties = image['properties']
318
    properties.pop(key, None)
319

    
320
    with image_backend(request.user_uniq) as backend:
321
        backend.update(image_id, dict(properties=properties))
322

    
323
    return HttpResponse(status=204)