Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12.2 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 import 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
PlanktonBackend = backend.get_backend()
63

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

    
73

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

    
83

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

    
93

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

    
107

    
108
def image_to_dict(image, detail=True):
109
    d = dict(id=image['id'], name=image['name'])
110
    if detail:
111
        d['updated'] = utils.isoformat(date_parse(image['updated_at']))
112
        d['created'] = utils.isoformat(date_parse(image['created_at']))
113
        d['status'] = 'DELETED' if image['deleted_at'] else 'ACTIVE'
114
        d['progress'] = 100 if image['status'] == 'available' else 0
115
        d['user_id'] = image['owner']
116
        d['tenant_id'] = image['owner']
117
        d['links'] = util.image_to_links(image["id"])
118
        if image["properties"]:
119
            d['metadata'] = image['properties']
120
        else:
121
            d['metadata'] = {}
122
    return d
123

    
124

    
125
@api.api_method("GET", user_required=True, logger=log)
126
def list_images(request, detail=False):
127
    # Normal Response Codes: 200, 203
128
    # Error Response Codes: computeFault (400, 500),
129
    #                       serviceUnavailable (503),
130
    #                       unauthorized (401),
131
    #                       badRequest (400),
132
    #                       overLimit (413)
133

    
134
    log.debug('list_images detail=%s', detail)
135
    since = utils.isoparse(request.GET.get('changes-since'))
136
    with PlanktonBackend(request.user_uniq) as b:
137
        images = b.list_images()
138
        if since:
139
            updated_since = lambda img: date_parse(img["updated_at"]) >= since
140
            images = ifilter(updated_since, images)
141
            if not images:
142
                return HttpResponse(status=304)
143

    
144
    images = sorted(images, key=lambda x: x['id'])
145
    reply = [image_to_dict(image, detail) for image in images]
146

    
147
    if request.serialization == 'xml':
148
        data = render_to_string('list_images.xml',
149
                                dict(images=reply, detail=detail))
150
    else:
151
        data = json.dumps(dict(images=reply))
152

    
153
    return HttpResponse(data, status=200)
154

    
155

    
156
@api.api_method('POST', user_required=True, logger=log)
157
def create_image(request):
158
    # Normal Response Code: 202
159
    # Error Response Codes: computeFault (400, 500),
160
    #                       serviceUnavailable (503),
161
    #                       unauthorized (401),
162
    #                       badMediaType(415),
163
    #                       itemNotFound (404),
164
    #                       badRequest (400),
165
    #                       serverCapacityUnavailable (503),
166
    #                       buildInProgress (409),
167
    #                       resizeNotAllowed (403),
168
    #                       backupOrResizeInProgress (409),
169
    #                       overLimit (413)
170

    
171
    raise faults.NotImplemented('Not supported.')
172

    
173

    
174
@api.api_method('GET', user_required=True, logger=log)
175
def get_image_details(request, image_id):
176
    # Normal Response Codes: 200, 203
177
    # Error Response Codes: computeFault (400, 500),
178
    #                       serviceUnavailable (503),
179
    #                       unauthorized (401),
180
    #                       badRequest (400),
181
    #                       itemNotFound (404),
182
    #                       overLimit (413)
183

    
184
    log.debug('get_image_details %s', image_id)
185
    with PlanktonBackend(request.user_uniq) as b:
186
        image = b.get_image(image_id)
187
    reply = image_to_dict(image)
188

    
189
    if request.serialization == 'xml':
190
        data = render_to_string('image.xml', dict(image=reply))
191
    else:
192
        data = json.dumps(dict(image=reply))
193

    
194
    return HttpResponse(data, status=200)
195

    
196

    
197
@api.api_method('DELETE', user_required=True, logger=log)
198
def delete_image(request, image_id):
199
    # Normal Response Code: 204
200
    # Error Response Codes: computeFault (400, 500),
201
    #                       serviceUnavailable (503),
202
    #                       unauthorized (401),
203
    #                       itemNotFound (404),
204
    #                       overLimit (413)
205

    
206
    log.info('delete_image %s', image_id)
207
    with PlanktonBackend(request.user_uniq) as b:
208
        b.unregister(image_id)
209
    log.info('User %s deleted image %s', request.user_uniq, image_id)
210
    return HttpResponse(status=204)
211

    
212

    
213
@api.api_method('GET', user_required=True, logger=log)
214
def list_metadata(request, image_id):
215
    # Normal Response Codes: 200, 203
216
    # Error Response Codes: computeFault (400, 500),
217
    #                       serviceUnavailable (503),
218
    #                       unauthorized (401),
219
    #                       badRequest (400),
220
    #                       overLimit (413)
221

    
222
    log.debug('list_image_metadata %s', image_id)
223
    with PlanktonBackend(request.user_uniq) as b:
224
        image = b.get_image(image_id)
225
    metadata = image['properties']
226
    return util.render_metadata(request, metadata, use_values=False,
227
                                status=200)
228

    
229

    
230
@api.api_method('POST', user_required=True, logger=log)
231
def update_metadata(request, image_id):
232
    # Normal Response Code: 201
233
    # Error Response Codes: computeFault (400, 500),
234
    #                       serviceUnavailable (503),
235
    #                       unauthorized (401),
236
    #                       badRequest (400),
237
    #                       buildInProgress (409),
238
    #                       badMediaType(415),
239
    #                       overLimit (413)
240

    
241
    req = utils.get_request_dict(request)
242
    log.info('update_image_metadata %s %s', image_id, req)
243
    with PlanktonBackend(request.user_uniq) as b:
244
        image = b.get_image(image_id)
245
        try:
246
            metadata = req['metadata']
247
            assert isinstance(metadata, dict)
248
        except (KeyError, AssertionError):
249
            raise faults.BadRequest('Malformed request.')
250

    
251
        properties = image['properties']
252
        properties.update(metadata)
253

    
254
        b.update_metadata(image_id, dict(properties=properties))
255

    
256
    return util.render_metadata(request, properties, status=201)
257

    
258

    
259
@api.api_method('GET', user_required=True, logger=log)
260
def get_metadata_item(request, image_id, key):
261
    # Normal Response Codes: 200, 203
262
    # Error Response Codes: computeFault (400, 500),
263
    #                       serviceUnavailable (503),
264
    #                       unauthorized (401),
265
    #                       itemNotFound (404),
266
    #                       badRequest (400),
267
    #                       overLimit (413)
268

    
269
    log.debug('get_image_metadata_item %s %s', image_id, key)
270
    with PlanktonBackend(request.user_uniq) as b:
271
        image = b.get_image(image_id)
272
    val = image['properties'].get(key)
273
    if val is None:
274
        raise faults.ItemNotFound('Metadata key not found.')
275
    return util.render_meta(request, {key: val}, status=200)
276

    
277

    
278
@api.api_method('PUT', user_required=True, logger=log)
279
def create_metadata_item(request, image_id, key):
280
    # Normal Response Code: 201
281
    # Error Response Codes: computeFault (400, 500),
282
    #                       serviceUnavailable (503),
283
    #                       unauthorized (401),
284
    #                       itemNotFound (404),
285
    #                       badRequest (400),
286
    #                       buildInProgress (409),
287
    #                       badMediaType(415),
288
    #                       overLimit (413)
289

    
290
    req = utils.get_request_dict(request)
291
    log.info('create_image_metadata_item %s %s %s', image_id, key, req)
292
    try:
293
        metadict = req['meta']
294
        assert isinstance(metadict, dict)
295
        assert len(metadict) == 1
296
        assert key in metadict
297
    except (KeyError, AssertionError):
298
        raise faults.BadRequest('Malformed request.')
299

    
300
    val = metadict[key]
301
    with PlanktonBackend(request.user_uniq) as b:
302
        image = b.get_image(image_id)
303
        properties = image['properties']
304
        properties[key] = val
305

    
306
        b.update_metadata(image_id, dict(properties=properties))
307

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

    
310

    
311
@api.api_method('DELETE', user_required=True, logger=log)
312
def delete_metadata_item(request, image_id, key):
313
    # Normal Response Code: 204
314
    # Error Response Codes: computeFault (400, 500),
315
    #                       serviceUnavailable (503),
316
    #                       unauthorized (401),
317
    #                       itemNotFound (404),
318
    #                       badRequest (400),
319
    #                       buildInProgress (409),
320
    #                       badMediaType(415),
321
    #                       overLimit (413),
322

    
323
    log.info('delete_image_metadata_item %s %s', image_id, key)
324
    with PlanktonBackend(request.user_uniq) as b:
325
        image = b.get_image(image_id)
326
        properties = image['properties']
327
        properties.pop(key, None)
328

    
329
        b.update_metadata(image_id, dict(properties=properties))
330

    
331
    return HttpResponse(status=204)