Revision 432fc8c3

b/api/images.py
3 3
#
4 4

  
5 5
from synnefo.api.common import method_not_allowed
6
from synnefo.api.util import isoformat, isoparse, get_user, get_image, get_request_dict, api_method
6
from synnefo.api.util import *
7 7
from synnefo.db.models import Image, ImageMetadata, VirtualMachine
8 8

  
9 9
from django.conf.urls.defaults import patterns
......
16 16
    (r'^(?:/|.json|.xml)?$', 'demux'),
17 17
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
18 18
    (r'^/(\d+)(?:.json|.xml)?$', 'image_demux'),
19
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
20
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
19 21
)
20 22

  
21 23
def demux(request):
......
34 36
    else:
35 37
        return method_not_allowed(request)
36 38

  
39
def metadata_demux(request, image_id):
40
    if request.method == 'GET':
41
        return list_metadata(request, image_id)
42
    elif request.method == 'POST':
43
        return update_metadata(request, image_id)
44
    else:
45
        return method_not_allowed(request)
46

  
47
def metadata_item_demux(request, image_id, key):
48
    if request.method == 'GET':
49
        return get_metadata_item(request, image_id, key)
50
    elif request.method == 'PUT':
51
        return create_metadata_item(request, image_id, key)
52
    elif request.method == 'DELETE':
53
        return delete_metadata_item(request, image_id, key)
54
    else:
55
        return method_not_allowed(request)
56

  
37 57

  
38 58
def image_to_dict(image, detail=True):
39 59
    d = {'id': image.id, 'name': image.name}
......
55 75
    
56 76
    return d
57 77

  
78
def metadata_to_dict(image):
79
    image_meta = image.imagemetadata_set.all()
80
    return dict((meta.meta_key, meta.meta_value) for meta in image_meta)
81

  
58 82

  
59 83
@api_method('GET')
60 84
def list_images(request, detail=False):
......
99 123
    #                       overLimit (413)
100 124
    
101 125
    req = get_request_dict(request)
102
    owner = get_user()
103 126
    
104 127
    try:
105 128
        d = req['image']
106
        server_id = int(d['serverRef'])
107
        vm = VirtualMachine.objects.get(id=server_id)
108
        image = Image.objects.create(name=d['name'], size=0, owner=owner, sourcevm=vm)
109
        image.save()
110
    except KeyError:
111
        raise BadRequest
112
    except ValueError:
113
        raise BadRequest
114
    except VirtualMachine.DoesNotExist:
115
        raise ItemNotFound
129
        server_id = d['serverRef']
130
        name = d['name']
131
    except (KeyError, ValueError):
132
        raise BadRequest('Malformed request.')
133
    
134
    owner = get_user()
135
    vm = get_vm(server_id)
136
    image = Image.objects.create(name=name, size=0, owner=owner, sourcevm=vm)
137
    image.save()
116 138
    
117 139
    imagedict = image_to_dict(image)
118 140
    if request.serialization == 'xml':
......
156 178
        raise Unauthorized()
157 179
    image.delete()
158 180
    return HttpResponse(status=204)
181

  
182
@api_method('GET')
183
def list_metadata(request, image_id):
184
    # Normal Response Codes: 200, 203
185
    # Error Response Codes: computeFault (400, 500),
186
    #                       serviceUnavailable (503),
187
    #                       unauthorized (401),
188
    #                       badRequest (400),
189
    #                       overLimit (413)
190

  
191
    image = get_image(image_id)
192
    metadata = metadata_to_dict(image)
193
    return render_metadata(request, metadata, use_values=True, status=200)
194

  
195
@api_method('POST')
196
def update_metadata(request, image_id):
197
    # Normal Response Code: 201
198
    # Error Response Codes: computeFault (400, 500),
199
    #                       serviceUnavailable (503),
200
    #                       unauthorized (401),
201
    #                       badRequest (400),
202
    #                       buildInProgress (409),
203
    #                       badMediaType(415),
204
    #                       overLimit (413)
205

  
206
    image = get_image(image_id)
207
    req = get_request_dict(request)
208
    try:
209
        metadata = req['metadata']
210
        assert isinstance(metadata, dict)
211
    except (KeyError, AssertionError):
212
        raise BadRequest('Malformed request.')
213

  
214
    updated = {}
215

  
216
    for key, val in metadata.items():
217
        try:
218
            meta = ImageMetadata.objects.get(meta_key=key, image=image)
219
            meta.meta_value = val
220
            meta.save()
221
            updated[key] = val
222
        except ImageMetadata.DoesNotExist:
223
            pass    # Ignore non-existent metadata
224

  
225
    return render_metadata(request, metadata, status=201)
226

  
227
@api_method('GET')
228
def get_metadata_item(request, image_id, key):
229
    # Normal Response Codes: 200, 203
230
    # Error Response Codes: computeFault (400, 500),
231
    #                       serviceUnavailable (503),
232
    #                       unauthorized (401),
233
    #                       itemNotFound (404),
234
    #                       badRequest (400),
235
    #                       overLimit (413)
236

  
237
    meta = get_image_meta(image_id, key)
238
    return render_meta(request, meta, status=200)
239

  
240
@api_method('PUT')
241
def create_metadata_item(request, image_id, key):
242
    # Normal Response Code: 201
243
    # Error Response Codes: computeFault (400, 500),
244
    #                       serviceUnavailable (503),
245
    #                       unauthorized (401),
246
    #                       itemNotFound (404),
247
    #                       badRequest (400),
248
    #                       buildInProgress (409),
249
    #                       badMediaType(415),
250
    #                       overLimit (413)
251

  
252
    image = get_image(image_id)
253
    req = get_request_dict(request)
254
    try:
255
        metadict = req['meta']
256
        assert isinstance(metadict, dict)
257
        assert len(metadict) == 1
258
        assert key in metadict
259
    except (KeyError, AssertionError):
260
        raise BadRequest('Malformed request.')
261

  
262
    meta, created = ImageMetadata.objects.get_or_create(meta_key=key, image=image)
263
    meta.meta_value = metadict[key]
264
    meta.save()
265
    return render_meta(request, meta, status=201)
266

  
267
@api_method('DELETE')
268
def delete_metadata_item(request, image_id, key):
269
    # Normal Response Code: 204
270
    # Error Response Codes: computeFault (400, 500),
271
    #                       serviceUnavailable (503),
272
    #                       unauthorized (401),
273
    #                       itemNotFound (404),
274
    #                       badRequest (400),
275
    #                       buildInProgress (409),
276
    #                       badMediaType(415),
277
    #                       overLimit (413),
278

  
279
    meta = get_image_meta(image_id, key)
280
    meta.delete()
281
    return HttpResponse(status=204)
b/api/servers.py
323 323

  
324 324
    vm = get_vm(server_id)
325 325
    metadata = metadata_to_dict(vm)
326
    
327
    if request.serialization == 'xml':
328
        data = render_to_string('metadata.xml', {'metadata': metadata})
329
    else:
330
        data = json.dumps({'metadata': {'values': metadata}})
331
    
332
    return HttpResponse(data, status=200)
333
    
326
    return render_metadata(request, metadata, use_values=True, status=200)
334 327

  
335 328
@api_method('POST')
336 329
def update_metadata(request, server_id):
......
362 355
        except VirtualMachineMetadata.DoesNotExist:
363 356
            pass    # Ignore non-existent metadata
364 357
    
365
    if request.serialization == 'xml':
366
        data = render_to_string('servers/metadata.xml', {'metadata': updated})
367
    else:
368
        data = json.dumps({'metadata': updated})
369
    return HttpResponse(data, status=201)
358
    return render_metadata(request, metadata, status=201)
370 359

  
371 360
@api_method('GET')
372 361
def get_metadata_item(request, server_id, key):
......
379 368
    #                       overLimit (413)
380 369

  
381 370
    meta = get_vm_meta(server_id, key)
382
    if request.serialization == 'xml':
383
        data = render_to_string('meta.xml', {'meta': meta})
384
    else:
385
        data = json.dumps({'meta': {key: meta.meta_value}})
386
    return HttpResponse(data, status=200)
371
    return render_meta(request, meta, status=200)
387 372

  
388 373
@api_method('PUT')
389 374
def create_metadata_item(request, server_id, key):
......
410 395
    meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm)
411 396
    meta.meta_value = metadict[key]
412 397
    meta.save()
413
    
414
    if request.serialization == 'xml':
415
        data = render_to_string('servers/meta.xml', {'meta': meta})
416
    else:
417
        data = json.dumps({'meta': {key: meta.meta_value}})
418
    return HttpResponse(data, status=201)
398
    return render_meta(request, meta, status=201)
419 399

  
420 400
@api_method('DELETE')
421 401
def delete_metadata_item(request, server_id, key):
b/api/util.py
13 13
from django.template.loader import render_to_string
14 14
from django.utils import simplejson as json
15 15

  
16
from synnefo.api.faults import *
17
from synnefo.db.models import *
16
from synnefo.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable
17
from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
18 18

  
19 19
import datetime
20 20
import dateutil.parser
......
98 98
    except Image.DoesNotExist:
99 99
        raise ItemNotFound('Image not found.')
100 100

  
101
def get_image_meta(image_id, key):
102
    """Return a ImageMetadata instance or raise ItemNotFound."""
103

  
104
    try:
105
        image_id = int(image_id)
106
        return ImageMetadata.objects.get(meta_key=key, image=image_id)
107
    except ImageMetadata.DoesNotExist:
108
        raise ItemNotFound('Metadata key not found.')
109

  
101 110

  
102 111
def get_request_dict(request):
103 112
    """Returns data sent by the client as a python dict."""
......
111 120
    else:
112 121
        raise BadRequest('Unsupported Content-Type.')
113 122

  
123

  
124
def render_metadata(request, metadata, use_values=False, status=200):
125
    if request.serialization == 'xml':
126
        data = render_to_string('metadata.xml', {'metadata': metadata})
127
    else:
128
        d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
129
        data = json.dumps(d)
130
    return HttpResponse(data, status=status)
131

  
132
def render_meta(request, meta, status=200):
133
    if request.serialization == 'xml':
134
        data = render_to_string('meta.xml', {'meta': meta})
135
    else:
136
        data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
137
    return HttpResponse(data, status=status)
138

  
114 139
def render_fault(request, fault):
115 140
    if settings.DEBUG or request.META.get('SERVER_NAME') == 'testserver':
116 141
        fault.details = format_exc(fault)
......
132 157
    
133 158
    return resp
134 159

  
160

  
135 161
def request_serialization(request, atom_allowed=False):
136 162
    """Return the serialization format requested.
137 163
       
b/db/fixtures/images.json
7 7
            "created": "2011-02-06 00:00:00",
8 8
            "updated": "2011-02-06 00:00:00",
9 9
            "state": "ACTIVE",
10
            "description": "Debian Sid, full installation",
11
            "size": 4096
12 10
        }
13 11
    },
14 12
    {
......
19 17
            "created": "2011-02-06 00:00:00",
20 18
            "updated": "2011-02-06 00:00:00",
21 19
            "state": "ACTIVE",
22
            "description": "Red Hat Enterprise Linux, full installation",
23
            "size": 2048
24 20
        }
25 21
    },
26 22
    {
......
31 27
            "created": "2011-02-06 00:00:00",
32 28
            "updated": "2011-02-06 00:00:00",
33 29
            "state": "ACTIVE",
34
            "description": "Ubuntu 10.10, full installation",
35
            "size": 8192
36 30
        }
37 31
    },
38 32
    {
b/db/models.py
54 54

  
55 55
    name = models.CharField('Image name', max_length=255)
56 56
    state = models.CharField('Current Image State', choices=IMAGE_STATES, max_length=30)
57
    description = models.TextField('General description')
58
    size = models.PositiveIntegerField('Image size in MBs')
59 57
    owner = models.ForeignKey(SynnefoUser, blank=True, null=True)
60 58
    created = models.DateTimeField('Time of creation', auto_now_add=True)
61 59
    updated = models.DateTimeField('Time of last update', auto_now=True)
b/tools/cloud
406 406
        path = '/api/%s/servers/%d/meta/%s' % (self.api, int(server_id), key)
407 407
        reply = self.http_delete(path)
408 408

  
409
@command_name('lsimgmeta')
410
class ListImageMeta(Command):
411
    description = 'list image meta'
412
    syntax = '<image id> [key]'
413

  
414
    def execute(self, image_id, key=None):
415
        path = '/api/%s/images/%d/meta' % (self.api, int(image_id))
416
        if key:
417
            path += '/' + key
418
        reply = self.http_get(path)
419
        if key:
420
            print_dict(reply['meta'])
421
        else:
422
            print_dict(reply['metadata']['values'])
423

  
424
@command_name('setimgmeta')
425
class UpdateImageMeta(Command):
426
    description = 'update image meta'
427
    syntax = '<image id> <key> <val>'
428

  
429
    def execute(self, image_id, key, val):
430
        path = '/api/%s/images/%d/meta' % (self.api, int(image_id))
431
        metadata = {key: val}
432
        body = json.dumps({'metadata': metadata})
433
        reply = self.http_post(path, body, expected_status=201)
434
        print_dict(reply['metadata'])
435

  
436
@command_name('addimgmeta')
437
class CreateImageMeta(Command):
438
    description = 'add image meta'
439
    syntax = '<image id> <key> <val>'
440

  
441
    def execute(self, image_id, key, val):
442
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
443
        meta = {key: val}
444
        body = json.dumps({'meta': meta})
445
        reply = self.http_put(path, body, expected_status=201)
446
        print_dict(reply['meta'])
447

  
448
@command_name('delimgmeta')
449
class DeleteImageMeta(Command):
450
    description = 'delete image meta'
451
    syntax = '<image id> <key>'
452

  
453
    def execute(self, image_id, key):
454
        path = '/api/%s/images/%d/meta/%s' % (self.api, int(image_id), key)
455
        reply = self.http_delete(path)
456

  
409 457

  
410 458
def main():
411 459
    try:

Also available in: Unified diff