Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / api / images.py @ 483c9197

History | View | Annotate | Download (11.8 kB)

1
# Copyright 2011 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 django.conf.urls.defaults import patterns
35
from django.db.models import Q
36
from django.http import HttpResponse
37
from django.template.loader import render_to_string
38
from django.utils import simplejson as json
39

    
40
from synnefo.api import util
41
from synnefo.api.common import method_not_allowed
42
from synnefo.api.faults import BadRequest
43
from synnefo.db.models import Image, ImageMetadata
44
from synnefo.util.log import getLogger
45

    
46

    
47
log = getLogger('synnefo.api')
48

    
49
urlpatterns = patterns('synnefo.api.images',
50
    (r'^(?:/|.json|.xml)?$', 'demux'),
51
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
52
    (r'^/(\d+)(?:.json|.xml)?$', 'image_demux'),
53
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
54
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
55
)
56

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

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

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

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

    
91

    
92
def image_to_dict(image, detail=True):
93
    d = {'id': image.id, 'name': image.name}
94
    if detail:
95
        d['updated'] = util.isoformat(image.updated)
96
        d['created'] = util.isoformat(image.created)
97
        d['status'] = image.state
98
        d['progress'] = 100 if image.state == 'ACTIVE' else 0
99
        if image.sourcevm:
100
            d['serverRef'] = image.sourcevm.id
101

    
102
        metadata = {}
103
        for meta in ImageMetadata.objects.filter(image=image):
104
            metadata[meta.meta_key] = meta.meta_value
105

    
106
        if metadata:
107
            d['metadata'] = {'values': metadata}
108

    
109
    return d
110

    
111

    
112
@util.api_method('GET')
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
    user_images = Image.objects.filter(Q(owner=request.user) | Q(public=True))
123
    since = util.isoparse(request.GET.get('changes-since'))
124
    
125
    if since:
126
        user_images = user_images.filter(updated__gte=since)
127
        if not user_images:
128
            return HttpResponse(status=304)
129
    else:
130
        user_images = user_images.exclude(state='DELETED')
131
    
132
    images = [image_to_dict(image, detail) for image in user_images]
133
    
134
    if request.serialization == 'xml':
135
        data = render_to_string('list_images.xml', {
136
            'images': images,
137
            'detail': detail})
138
    else:
139
        data = json.dumps({'images': {'values': images}})
140

    
141
    return HttpResponse(data, status=200)
142

    
143
@util.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
    req = util.get_request_dict(request)
159
    log.debug('create_image %s', req)
160
    
161
    try:
162
        d = req['image']
163
        server_id = d['serverRef']
164
        name = d['name']
165
    except (KeyError, ValueError):
166
        raise BadRequest('Malformed request.')
167

    
168
    owner = request.user
169
    vm = util.get_vm(server_id, owner)
170
    image = Image.objects.create(name=name, owner=owner, sourcevm=vm)
171
    log.info('User %d created image %d', owner.id, image.id)
172

    
173
    imagedict = image_to_dict(image)
174
    if request.serialization == 'xml':
175
        data = render_to_string('image.xml', {'image': imagedict})
176
    else:
177
        data = json.dumps({'image': imagedict})
178

    
179
    return HttpResponse(data, status=202)
180

    
181
@util.api_method('GET')
182
def get_image_details(request, image_id):
183
    # Normal Response Codes: 200, 203
184
    # Error Response Codes: computeFault (400, 500),
185
    #                       serviceUnavailable (503),
186
    #                       unauthorized (401),
187
    #                       badRequest (400),
188
    #                       itemNotFound (404),
189
    #                       overLimit (413)
190
    
191
    log.debug('get_image_details %s', image_id)
192
    image = util.get_image(image_id, request.user)
193
    imagedict = image_to_dict(image)
194

    
195
    if request.serialization == 'xml':
196
        data = render_to_string('image.xml', {'image': imagedict})
197
    else:
198
        data = json.dumps({'image': imagedict})
199

    
200
    return HttpResponse(data, status=200)
201

    
202
@util.api_method('DELETE')
203
def delete_image(request, image_id):
204
    # Normal Response Code: 204
205
    # Error Response Codes: computeFault (400, 500),
206
    #                       serviceUnavailable (503),
207
    #                       unauthorized (401),
208
    #                       itemNotFound (404),
209
    #                       overLimit (413)
210
    
211
    log.debug('delete_image %s', image_id)
212
    image = util.get_image(image_id, request.user)
213
    image.state = 'DELETED'
214
    image.save()
215
    log.info('User %d deleted image %d', request.user.id, image.id)
216
    return HttpResponse(status=204)
217

    
218
@util.api_method('GET')
219
def list_metadata(request, image_id):
220
    # Normal Response Codes: 200, 203
221
    # Error Response Codes: computeFault (400, 500),
222
    #                       serviceUnavailable (503),
223
    #                       unauthorized (401),
224
    #                       badRequest (400),
225
    #                       overLimit (413)
226
    
227
    log.debug('list_image_metadata %s', image_id)
228
    image = util.get_image(image_id, request.user)
229
    metadata = dict((m.meta_key, m.meta_value) for m in image.metadata.all())
230
    return util.render_metadata(request, metadata, use_values=True, status=200)
231

    
232
@util.api_method('POST')
233
def update_metadata(request, image_id):
234
    # Normal Response Code: 201
235
    # Error Response Codes: computeFault (400, 500),
236
    #                       serviceUnavailable (503),
237
    #                       unauthorized (401),
238
    #                       badRequest (400),
239
    #                       buildInProgress (409),
240
    #                       badMediaType(415),
241
    #                       overLimit (413)
242
    
243
    req = util.get_request_dict(request)
244
    log.debug('update_image_metadata %s %s', image_id, req)
245
    image = util.get_image(image_id, request.user)
246
    try:
247
        metadata = req['metadata']
248
        assert isinstance(metadata, dict)
249
    except (KeyError, AssertionError):
250
        raise BadRequest('Malformed request.')
251
    
252
    for key, val in metadata.items():
253
        meta, created = image.metadata.get_or_create(meta_key=key)
254
        meta.meta_value = val
255
        meta.save()
256
    
257
    image.save()
258
    image_meta = dict((m.meta_key, m.meta_value) for m in image.metadata.all())
259
    return util.render_metadata(request, image_meta, status=201)
260

    
261
@util.api_method('GET')
262
def get_metadata_item(request, image_id, key):
263
    # Normal Response Codes: 200, 203
264
    # Error Response Codes: computeFault (400, 500),
265
    #                       serviceUnavailable (503),
266
    #                       unauthorized (401),
267
    #                       itemNotFound (404),
268
    #                       badRequest (400),
269
    #                       overLimit (413)
270
    
271
    log.debug('get_image_metadata_item %s %s', image_id, key)
272
    image = util.get_image(image_id, request.user)
273
    meta = util.get_image_meta(image, key)
274
    return util.render_meta(request, meta, status=200)
275

    
276
@util.api_method('PUT')
277
def create_metadata_item(request, image_id, key):
278
    # Normal Response Code: 201
279
    # Error Response Codes: computeFault (400, 500),
280
    #                       serviceUnavailable (503),
281
    #                       unauthorized (401),
282
    #                       itemNotFound (404),
283
    #                       badRequest (400),
284
    #                       buildInProgress (409),
285
    #                       badMediaType(415),
286
    #                       overLimit (413)
287
    
288
    req = util.get_request_dict(request)
289
    log.debug('create_image_metadata_item %s %s %s', image_id, key, req)
290
    image = util.get_image(image_id, request.user)
291
    try:
292
        metadict = req['meta']
293
        assert isinstance(metadict, dict)
294
        assert len(metadict) == 1
295
        assert key in metadict
296
    except (KeyError, AssertionError):
297
        raise BadRequest('Malformed request.')
298
    
299
    meta, created = ImageMetadata.objects.get_or_create(
300
        meta_key=key,
301
        image=image)
302
    
303
    meta.meta_value = metadict[key]
304
    meta.save()
305
    image.save()
306
    return util.render_meta(request, meta, status=201)
307

    
308
@util.api_method('DELETE')
309
def delete_metadata_item(request, image_id, key):
310
    # Normal Response Code: 204
311
    # Error Response Codes: computeFault (400, 500),
312
    #                       serviceUnavailable (503),
313
    #                       unauthorized (401),
314
    #                       itemNotFound (404),
315
    #                       badRequest (400),
316
    #                       buildInProgress (409),
317
    #                       badMediaType(415),
318
    #                       overLimit (413),
319
    
320
    log.debug('delete_image_metadata_item %s %s', image_id, key)
321
    image = util.get_image(image_id, request.user)
322
    meta = util.get_image_meta(image, key)
323
    meta.delete()
324
    image.save()
325
    return HttpResponse(status=204)