Statistics
| Branch: | Tag: | Revision:

root / api / images.py @ adee02b8

History | View | Annotate | Download (11.2 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.http import HttpResponse
36
from django.template.loader import render_to_string
37
from django.utils import simplejson as json
38

    
39
from synnefo.api import util
40
from synnefo.api.common import method_not_allowed
41
from synnefo.api.faults import BadRequest
42
from synnefo.db.models import Image, ImageMetadata
43

    
44

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

    
53
def demux(request):
54
    if request.method == 'GET':
55
        return list_images(request)
56
    elif request.method == 'POST':
57
        return create_image(request)
58
    else:
59
        return method_not_allowed(request)
60

    
61
def image_demux(request, image_id):
62
    if request.method == 'GET':
63
        return get_image_details(request, image_id)
64
    elif request.method == 'DELETE':
65
        return delete_image(request, image_id)
66
    else:
67
        return method_not_allowed(request)
68

    
69
def metadata_demux(request, image_id):
70
    if request.method == 'GET':
71
        return list_metadata(request, image_id)
72
    elif request.method == 'POST':
73
        return update_metadata(request, image_id)
74
    else:
75
        return method_not_allowed(request)
76

    
77
def metadata_item_demux(request, image_id, key):
78
    if request.method == 'GET':
79
        return get_metadata_item(request, image_id, key)
80
    elif request.method == 'PUT':
81
        return create_metadata_item(request, image_id, key)
82
    elif request.method == 'DELETE':
83
        return delete_metadata_item(request, image_id, key)
84
    else:
85
        return method_not_allowed(request)
86

    
87

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

    
98
        metadata = {}
99
        for meta in ImageMetadata.objects.filter(image=image):
100
            metadata[meta.meta_key] = meta.meta_value
101

    
102
        if metadata:
103
            d['metadata'] = {'values': metadata}
104

    
105
    return d
106

    
107
def metadata_to_dict(image):
108
    image_meta = image.imagemetadata_set.all()
109
    return dict((meta.meta_key, meta.meta_value) for meta in image_meta)
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
    since = util.isoparse(request.GET.get('changes-since'))
122

    
123
    if since:
124
        avail_images = Image.objects.filter(owner=request.user,
125
                                            updated__gte=since)
126
        if not avail_images:
127
            return HttpResponse(status=304)
128
    else:
129
        avail_images = Image.objects.filter(owner=request.user)
130

    
131
    images = [image_to_dict(image, detail) for image in avail_images]
132

    
133
    if request.serialization == 'xml':
134
        data = render_to_string('list_images.xml', {
135
            'images': images,
136
            'detail': detail})
137
    else:
138
        data = json.dumps({'images': {'values': images}})
139

    
140
    return HttpResponse(data, status=200)
141

    
142
@util.api_method('POST')
143
def create_image(request):
144
    # Normal Response Code: 202
145
    # Error Response Codes: computeFault (400, 500),
146
    #                       serviceUnavailable (503),
147
    #                       unauthorized (401),
148
    #                       badMediaType(415),
149
    #                       itemNotFound (404),
150
    #                       badRequest (400),
151
    #                       serverCapacityUnavailable (503),
152
    #                       buildInProgress (409),
153
    #                       resizeNotAllowed (403),
154
    #                       backupOrResizeInProgress (409),
155
    #                       overLimit (413)
156

    
157
    req = util.get_request_dict(request)
158

    
159
    try:
160
        d = req['image']
161
        server_id = d['serverRef']
162
        name = d['name']
163
    except (KeyError, ValueError):
164
        raise BadRequest('Malformed request.')
165

    
166
    owner = request.user
167
    vm = util.get_vm(server_id, owner)
168
    image = Image.objects.create(name=name, owner=owner, sourcevm=vm)
169

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

    
176
    return HttpResponse(data, status=202)
177

    
178
@util.api_method('GET')
179
def get_image_details(request, image_id):
180
    # Normal Response Codes: 200, 203
181
    # Error Response Codes: computeFault (400, 500),
182
    #                       serviceUnavailable (503),
183
    #                       unauthorized (401),
184
    #                       badRequest (400),
185
    #                       itemNotFound (404),
186
    #                       overLimit (413)
187

    
188
    image = util.get_image(image_id, request.user)
189
    imagedict = image_to_dict(image)
190

    
191
    if request.serialization == 'xml':
192
        data = render_to_string('image.xml', {'image': imagedict})
193
    else:
194
        data = json.dumps({'image': imagedict})
195

    
196
    return HttpResponse(data, status=200)
197

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

    
207
    image = util.get_image(image_id, request.user)
208
    image.delete()
209
    return HttpResponse(status=204)
210

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

    
220
    image = util.get_image(image_id, request.user)
221
    metadata = metadata_to_dict(image)
222
    return util.render_metadata(request, metadata, use_values=True, status=200)
223

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

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

    
243
    updated = {}
244

    
245
    for key, val in metadata.items():
246
        try:
247
            meta = ImageMetadata.objects.get(meta_key=key, image=image)
248
            meta.meta_value = val
249
            meta.save()
250
            updated[key] = val
251
        except ImageMetadata.DoesNotExist:
252
            pass    # Ignore non-existent metadata
253
    
254
    if updated:
255
        image.save()
256
    
257
    return util.render_metadata(request, updated, status=201)
258

    
259
@util.api_method('GET')
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
    image = util.get_image(image_id, request.user)
270
    meta = util.get_image_meta(image, key)
271
    return util.render_meta(request, meta, status=200)
272

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

    
285
    image = util.get_image(image_id, request.user)
286
    req = util.get_request_dict(request)
287
    try:
288
        metadict = req['meta']
289
        assert isinstance(metadict, dict)
290
        assert len(metadict) == 1
291
        assert key in metadict
292
    except (KeyError, AssertionError):
293
        raise BadRequest('Malformed request.')
294
    
295
    meta, created = ImageMetadata.objects.get_or_create(
296
        meta_key=key,
297
        image=image)
298
    
299
    meta.meta_value = metadict[key]
300
    meta.save()
301
    image.save()
302
    return util.render_meta(request, meta, status=201)
303

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

    
316
    image = util.get_image(image_id, request.user)
317
    meta = util.get_image_meta(image, key)
318
    meta.delete()
319
    image.save()
320
    return HttpResponse(status=204)