Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.7 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.defaults 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.method_not_allowed(request)
69

    
70

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

    
79

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

    
88

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

    
99

    
100
def image_to_dict(image, detail=True):
101
    d = dict(id=image['id'], name=image['name'])
102
    if detail:
103
        d['updated'] = utils.isoformat(date_parse(image['updated_at']))
104
        d['created'] = utils.isoformat(date_parse(image['created_at']))
105
        d['status'] = 'DELETED' if image['deleted_at'] else 'ACTIVE'
106
        d['progress'] = 100 if image['status'] == 'available' else 0
107
        if image['properties']:
108
            d['metadata'] = image['properties']
109
    return d
110

    
111

    
112
@api.api_method("GET", user_required=True, logger=log)
113
def list_images(request, detail=False):
114
    # Normal Response Codes: 200, 203
115
    # Error Response Codes: computeFault (400, 500),
116
    #                       serviceUnavailable (503),
117
    #                       unauthorized (401),
118
    #                       badRequest (400),
119
    #                       overLimit (413)
120

    
121
    log.debug('list_images detail=%s', detail)
122
    since = utils.isoparse(request.GET.get('changes-since'))
123
    with image_backend(request.user_uniq) as backend:
124
        images = backend.list_images()
125
        if since:
126
            updated_since = lambda img: date_parse(img["updated_at"]) >= since
127
            images = ifilter(updated_since, images)
128
            if not images:
129
                return HttpResponse(status=304)
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=reply))
139

    
140
    return HttpResponse(data, status=200)
141

    
142

    
143
@api.api_method('POST', user_required=True, logger=log)
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 faults.NotImplemented('Not supported.')
159

    
160

    
161
@api.api_method('GET', user_required=True, logger=log)
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
    with image_backend(request.user_uniq) as backend:
173
        image = backend.get_image(image_id)
174
    reply = image_to_dict(image)
175

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

    
181
    return HttpResponse(data, status=200)
182

    
183

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

    
193
    log.info('delete_image %s', image_id)
194
    with image_backend(request.user_uniq) as backend:
195
        backend.unregister(image_id)
196
    log.info('User %s deleted image %s', request.user_uniq, image_id)
197
    return HttpResponse(status=204)
198

    
199

    
200
@api.api_method('GET', user_required=True, logger=log)
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
    with image_backend(request.user_uniq) as backend:
211
        image = backend.get_image(image_id)
212
    metadata = image['properties']
213
    return util.render_metadata(request, metadata, use_values=False,
214
                                status=200)
215

    
216

    
217
@api.api_method('POST', user_required=True, logger=log)
218
def update_metadata(request, image_id):
219
    # Normal Response Code: 201
220
    # Error Response Codes: computeFault (400, 500),
221
    #                       serviceUnavailable (503),
222
    #                       unauthorized (401),
223
    #                       badRequest (400),
224
    #                       buildInProgress (409),
225
    #                       badMediaType(415),
226
    #                       overLimit (413)
227

    
228
    req = utils.get_request_dict(request)
229
    log.info('update_image_metadata %s %s', image_id, req)
230
    with image_backend(request.user_uniq) as backend:
231
        image = backend.get_image(image_id)
232
        try:
233
            metadata = req['metadata']
234
            assert isinstance(metadata, dict)
235
        except (KeyError, AssertionError):
236
            raise faults.BadRequest('Malformed request.')
237

    
238
        properties = image['properties']
239
        properties.update(metadata)
240

    
241
        backend.update_metadata(image_id, dict(properties=properties))
242

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

    
245

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

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

    
264

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

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

    
287
    val = metadict[key]
288
    with image_backend(request.user_uniq) as backend:
289
        image = backend.get_image(image_id)
290
        properties = image['properties']
291
        properties[key] = val
292

    
293
        backend.update_metadata(image_id, dict(properties=properties))
294

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

    
297

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

    
310
    log.info('delete_image_metadata_item %s %s', image_id, key)
311
    with image_backend(request.user_uniq) as backend:
312
        image = backend.get_image(image_id)
313
        properties = image['properties']
314
        properties.pop(key, None)
315

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

    
318
    return HttpResponse(status=204)