Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.8 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
        d['user_id'] = image['owner']
108
        d['tenant_id'] = image['owner']
109
        if image["properties"]:
110
            d['metadata'] = image['properties']
111
        else:
112
            d['metadata'] = {}
113
    return d
114

    
115

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

    
125
    log.debug('list_images detail=%s', detail)
126
    since = utils.isoparse(request.GET.get('changes-since'))
127
    with image_backend(request.user_uniq) as backend:
128
        images = backend.list_images()
129
        if since:
130
            updated_since = lambda img: date_parse(img["updated_at"]) >= since
131
            images = ifilter(updated_since, images)
132
            if not images:
133
                return HttpResponse(status=304)
134

    
135
    images = sorted(images, key=lambda x: x['id'])
136
    reply = [image_to_dict(image, detail) for image in images]
137

    
138
    if request.serialization == 'xml':
139
        data = render_to_string('list_images.xml',
140
                                dict(images=reply, detail=detail))
141
    else:
142
        data = json.dumps(dict(images=reply))
143

    
144
    return HttpResponse(data, status=200)
145

    
146

    
147
@api.api_method('POST', user_required=True, logger=log)
148
def create_image(request):
149
    # Normal Response Code: 202
150
    # Error Response Codes: computeFault (400, 500),
151
    #                       serviceUnavailable (503),
152
    #                       unauthorized (401),
153
    #                       badMediaType(415),
154
    #                       itemNotFound (404),
155
    #                       badRequest (400),
156
    #                       serverCapacityUnavailable (503),
157
    #                       buildInProgress (409),
158
    #                       resizeNotAllowed (403),
159
    #                       backupOrResizeInProgress (409),
160
    #                       overLimit (413)
161

    
162
    raise faults.NotImplemented('Not supported.')
163

    
164

    
165
@api.api_method('GET', user_required=True, logger=log)
166
def get_image_details(request, image_id):
167
    # Normal Response Codes: 200, 203
168
    # Error Response Codes: computeFault (400, 500),
169
    #                       serviceUnavailable (503),
170
    #                       unauthorized (401),
171
    #                       badRequest (400),
172
    #                       itemNotFound (404),
173
    #                       overLimit (413)
174

    
175
    log.debug('get_image_details %s', image_id)
176
    with image_backend(request.user_uniq) as backend:
177
        image = backend.get_image(image_id)
178
    reply = image_to_dict(image)
179

    
180
    if request.serialization == 'xml':
181
        data = render_to_string('image.xml', dict(image=reply))
182
    else:
183
        data = json.dumps(dict(image=reply))
184

    
185
    return HttpResponse(data, status=200)
186

    
187

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

    
197
    log.info('delete_image %s', image_id)
198
    with image_backend(request.user_uniq) as backend:
199
        backend.unregister(image_id)
200
    log.info('User %s deleted image %s', request.user_uniq, image_id)
201
    return HttpResponse(status=204)
202

    
203

    
204
@api.api_method('GET', user_required=True, logger=log)
205
def list_metadata(request, image_id):
206
    # Normal Response Codes: 200, 203
207
    # Error Response Codes: computeFault (400, 500),
208
    #                       serviceUnavailable (503),
209
    #                       unauthorized (401),
210
    #                       badRequest (400),
211
    #                       overLimit (413)
212

    
213
    log.debug('list_image_metadata %s', image_id)
214
    with image_backend(request.user_uniq) as backend:
215
        image = backend.get_image(image_id)
216
    metadata = image['properties']
217
    return util.render_metadata(request, metadata, use_values=False,
218
                                status=200)
219

    
220

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

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

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

    
245
        backend.update_metadata(image_id, dict(properties=properties))
246

    
247
    return util.render_metadata(request, properties, status=201)
248

    
249

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

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

    
268

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

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

    
291
    val = metadict[key]
292
    with image_backend(request.user_uniq) as backend:
293
        image = backend.get_image(image_id)
294
        properties = image['properties']
295
        properties[key] = val
296

    
297
        backend.update_metadata(image_id, dict(properties=properties))
298

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

    
301

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

    
314
    log.info('delete_image_metadata_item %s %s', image_id, key)
315
    with image_backend(request.user_uniq) as backend:
316
        image = backend.get_image(image_id)
317
        properties = image['properties']
318
        properties.pop(key, None)
319

    
320
        backend.update_metadata(image_id, dict(properties=properties))
321

    
322
    return HttpResponse(status=204)