servers.py 21.2 KB
Newer Older
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
Giorgos Verigakis's avatar
Giorgos Verigakis committed
3
4
5
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
6
#
Giorgos Verigakis's avatar
Giorgos Verigakis committed
7
8
9
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
10
#
Giorgos Verigakis's avatar
Giorgos Verigakis committed
11
12
13
14
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
15
#
Giorgos Verigakis's avatar
Giorgos Verigakis committed
16
17
18
19
20
21
22
23
24
25
26
27
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
28
#
Giorgos Verigakis's avatar
Giorgos Verigakis committed
29
30
31
32
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
33

34
35
from base64 import b64decode

36
from django.conf import settings
Giorgos Verigakis's avatar
Giorgos Verigakis committed
37
from django.conf.urls.defaults import patterns
38
from django.db import transaction
39
40
from django.http import HttpResponse
from django.template.loader import render_to_string
41
from django.utils import simplejson as json
42

43
from synnefo.api import faults, util
44
from synnefo.api.actions import server_actions
Giorgos Verigakis's avatar
Giorgos Verigakis committed
45
from synnefo.api.common import method_not_allowed
Christos Stavrakakis's avatar
Christos Stavrakakis committed
46
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
Giorgos Verigakis's avatar
Giorgos Verigakis committed
47
from synnefo.logic.backend import create_instance, delete_instance
Giorgos Verigakis's avatar
Giorgos Verigakis committed
48
from synnefo.logic.utils import get_rsapi_state
49
from synnefo.logic.rapi import GanetiApiError
50
from synnefo.logic.backend_allocator import BackendAllocator
51
from random import choice
52

53

Christos Stavrakakis's avatar
Christos Stavrakakis committed
54
from logging import getLogger
55
log = getLogger('synnefo.api')
56
57
58
59
60

urlpatterns = patterns('synnefo.api.servers',
    (r'^(?:/|.json|.xml)?$', 'demux'),
    (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
    (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
Giorgos Verigakis's avatar
Giorgos Verigakis committed
61
62
63
    (r'^/(\d+)/action(?:.json|.xml)?$', 'server_action'),
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
Giorgos Verigakis's avatar
Giorgos Verigakis committed
64
65
    (r'^/(\d+)/meta(?:.json|.xml)?$', 'metadata_demux'),
    (r'^/(\d+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
66
    (r'^/(\d+)/stats(?:.json|.xml)?$', 'server_stats'),
67
68
69
70
71
72
73
74
75
)


def demux(request):
    if request.method == 'GET':
        return list_servers(request)
    elif request.method == 'POST':
        return create_server(request)
    else:
Giorgos Verigakis's avatar
Giorgos Verigakis committed
76
        return method_not_allowed(request)
77

78

79
80
81
82
83
84
85
86
def server_demux(request, server_id):
    if request.method == 'GET':
        return get_server_details(request, server_id)
    elif request.method == 'PUT':
        return update_server_name(request, server_id)
    elif request.method == 'DELETE':
        return delete_server(request, server_id)
    else:
Giorgos Verigakis's avatar
Giorgos Verigakis committed
87
88
        return method_not_allowed(request)

89

Giorgos Verigakis's avatar
Giorgos Verigakis committed
90
91
92
93
94
95
96
97
def metadata_demux(request, server_id):
    if request.method == 'GET':
        return list_metadata(request, server_id)
    elif request.method == 'POST':
        return update_metadata(request, server_id)
    else:
        return method_not_allowed(request)

98

Giorgos Verigakis's avatar
Giorgos Verigakis committed
99
100
101
102
103
104
105
106
107
def metadata_item_demux(request, server_id, key):
    if request.method == 'GET':
        return get_metadata_item(request, server_id, key)
    elif request.method == 'PUT':
        return create_metadata_item(request, server_id, key)
    elif request.method == 'DELETE':
        return delete_metadata_item(request, server_id, key)
    else:
        return method_not_allowed(request)
108

109

110
111
112
113
114
115
def nic_to_dict(nic):
    d = {'id': util.construct_nic_id(nic),
         'network_id': str(nic.network.id),
         'mac_address': nic.mac,
         'ipv4': nic.ipv4 if nic.ipv4 else None,
         'ipv6': nic.ipv6 if nic.ipv6 else None}
116

Giorgos Verigakis's avatar
Giorgos Verigakis committed
117
118
    if nic.firewall_profile:
        d['firewallProfile'] = nic.firewall_profile
Giorgos Verigakis's avatar
Giorgos Verigakis committed
119
    return d
Giorgos Verigakis's avatar
Giorgos Verigakis committed
120

121

Giorgos Verigakis's avatar
Giorgos Verigakis committed
122
123
def vm_to_dict(vm, detail=False):
    d = dict(id=vm.id, name=vm.name)
124
    if detail:
Giorgos Verigakis's avatar
Giorgos Verigakis committed
125
        d['status'] = get_rsapi_state(vm)
126
127
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' \
                        else vm.buildpercentage
Giorgos Verigakis's avatar
Giorgos Verigakis committed
128
        d['hostId'] = vm.hostid
129
130
        d['updated'] = util.isoformat(vm.updated)
        d['created'] = util.isoformat(vm.created)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
131
        d['flavorRef'] = vm.flavor.id
132
        d['imageRef'] = vm.imageid
133

134
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
135
        if metadata:
136
            d['metadata'] = {'values': metadata}
137

138
139
140
        attachments = [nic_to_dict(nic) for nic in vm.nics.all()]
        if attachments:
            d['attachments'] = {'values': attachments}
141
142
    return d

Giorgos Verigakis's avatar
Giorgos Verigakis committed
143
144
145

def render_server(request, server, status=200):
    if request.serialization == 'xml':
146
147
148
        data = render_to_string('server.xml', {
            'server': server,
            'is_root': True})
149
    else:
Giorgos Verigakis's avatar
Giorgos Verigakis committed
150
        data = json.dumps({'server': server})
151
    return HttpResponse(data, status=status)
152

153

154
@util.api_method('GET')
155
156
157
158
159
160
161
def list_servers(request, detail=False):
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       overLimit (413)
162

Christos Stavrakakis's avatar
Christos Stavrakakis committed
163
    log.debug('list_servers detail=%s', detail)
164
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
165

166
    since = util.isoparse(request.GET.get('changes-since'))
Giorgos Verigakis's avatar
Giorgos Verigakis committed
167
    if since:
Giorgos Verigakis's avatar
Giorgos Verigakis committed
168
        user_vms = user_vms.filter(updated__gte=since)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
169
170
        if not user_vms:
            return HttpResponse(status=304)
171
    else:
172
        user_vms = user_vms.filter(deleted=False)
173

Giorgos Verigakis's avatar
Giorgos Verigakis committed
174
    servers = [vm_to_dict(server, detail) for server in user_vms]
175

Giorgos Verigakis's avatar
Giorgos Verigakis committed
176
    if request.serialization == 'xml':
177
178
179
        data = render_to_string('list_servers.xml', {
            'servers': servers,
            'detail': detail})
180
    else:
181
        data = json.dumps({'servers': {'values': servers}})
182

183
    return HttpResponse(data, status=200)
184

185

186
@util.api_method('POST')
187
188
189
190
191
# Use manual transactions. Backend and IP pool allocations need exclusive
# access (SELECT..FOR UPDATE). Running create_server with commit_on_success
# would result in backends and public networks to be locked until the job is
# sent to the Ganeti backend.
@transaction.commit_manually
192
193
194
195
196
197
198
199
200
201
202
def create_server(request):
    # Normal Response Code: 202
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badMediaType(415),
    #                       itemNotFound (404),
    #                       badRequest (400),
    #                       serverCapacityUnavailable (503),
    #                       overLimit (413)
    try:
203
204
205
        req = util.get_request_dict(request)
        log.info('create_server %s', req)

206
        try:
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
            server = req['server']
            name = server['name']
            metadata = server.get('metadata', {})
            assert isinstance(metadata, dict)
            image_id = server['imageRef']
            flavor_id = server['flavorRef']
            personality = server.get('personality', [])
            assert isinstance(personality, list)
        except (KeyError, AssertionError):
            raise faults.BadRequest("Malformed request")

        if len(personality) > settings.MAX_PERSONALITY:
            raise faults.OverLimit("Maximum number of personalities exceeded")

        for p in personality:
            # Verify that personalities are well-formed
            try:
                assert isinstance(p, dict)
                keys = set(p.keys())
                allowed = set(['contents', 'group', 'mode', 'owner', 'path'])
                assert keys.issubset(allowed)
                contents = p['contents']
                if len(contents) > settings.MAX_PERSONALITY_SIZE:
                    # No need to decode if contents already exceed limit
                    raise faults.OverLimit("Maximum size of personality exceeded")
                if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE:
                    raise faults.OverLimit("Maximum size of personality exceeded")
            except AssertionError:
                raise faults.BadRequest("Malformed personality in request")

        image = {}
        img = util.get_image(image_id, request.user_uniq)
        properties = img.get('properties', {})
        image['backend_id'] = img['location']
        image['format'] = img['disk_format']
        image['metadata'] = dict((key.upper(), val) \
                                 for key, val in properties.items())

        flavor = util.get_flavor(flavor_id)
        password = util.random_password()

        count = VirtualMachine.objects.filter(userid=request.user_uniq,
                                              deleted=False).count()

        # get user limit
        vms_limit_for_user = \
            settings.VMS_USER_QUOTA.get(request.user_uniq,
                    settings.MAX_VMS_PER_USER)

        if count >= vms_limit_for_user:
            raise faults.OverLimit("Server count limit exceeded for your account.")

        backend_allocator = BackendAllocator()
260
        backend = backend_allocator.allocate(request.user_uniq, flavor)
261
262
263
264
265

        if backend is None:
            log.error("No available backends for VM with flavor %s", flavor)
            raise Exception("No available backends")
    except:
266
        transaction.rollback()
267
        raise
268
    else:
269
        transaction.commit()
270

271
    try:
272
        if settings.PUBLIC_USE_POOL:
273
274
275
276
277
278
279
280
281
282
283
            (network, address) = util.allocate_public_address(backend)
            if address is None:
                log.error("Public networks of backend %s are full", backend)
                raise faults.OverLimit("Can not allocate IP for new machine."
                                       " Public networks are full.")
            nic = {'ip': address, 'network': network.backend_id}
        else:
            network = choice(list(util.backend_public_networks(backend)))
            nic = {'ip': 'pool', 'network': network.backend_id}
    except:
        transaction.rollback()
284
        raise
285
286
    else:
        transaction.commit()
287

288
289
290
291
292
293
294
295
296
    try:
        # We must save the VM instance now, so that it gets a valid
        # vm.backend_vm_id.
        vm = VirtualMachine.objects.create(
            name=name,
            backend=backend,
            userid=request.user_uniq,
            imageid=image_id,
            flavor=flavor)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
297

298
299
300
301
302
        try:
            jobID = create_instance(vm, nic, flavor, image, password, personality)
        except GanetiApiError:
            vm.delete()
            raise
303

304
305
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
                request.user_uniq, vm, nic, backend, str(jobID))
306

307
308
309
310
311
312
313
314
315
316
317
318
        vm.backendjobid = jobID
        vm.save()

        for key, val in metadata.items():
            VirtualMachineMetadata.objects.create(
                meta_key=key,
                meta_value=val,
                vm=vm)

        server = vm_to_dict(vm, detail=True)
        server['status'] = 'BUILD'
        server['adminPass'] = password
319

320
321
322
323
324
325
        respsone = render_server(request, server, status=202)
    except:
        transaction.rollback()
        raise
    else:
        transaction.commit()
326
327

    return respsone
328

329

330
@util.api_method('GET')
331
def get_server_details(request, server_id):
332
333
334
335
336
337
338
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       itemNotFound (404),
    #                       overLimit (413)
339

Christos Stavrakakis's avatar
Christos Stavrakakis committed
340
    log.debug('get_server_details %s', server_id)
341
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
342
343
    server = vm_to_dict(vm, detail=True)
    return render_server(request, server)
344

345

346
@util.api_method('PUT')
347
348
349
350
351
352
353
354
355
356
def update_server_name(request, server_id):
    # Normal Response Code: 204
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       badMediaType(415),
    #                       itemNotFound (404),
    #                       buildInProgress (409),
    #                       overLimit (413)
357

358
    req = util.get_request_dict(request)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
359
    log.info('update_server_name %s %s', server_id, req)
360

361
362
    try:
        name = req['server']['name']
Giorgos Verigakis's avatar
Giorgos Verigakis committed
363
    except (TypeError, KeyError):
364
        raise faults.BadRequest("Malformed request")
365

366
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
367
368
    vm.name = name
    vm.save()
369

370
371
    return HttpResponse(status=204)

372

373
@util.api_method('DELETE')
374
@transaction.commit_on_success
375
def delete_server(request, server_id):
376
377
378
379
380
381
382
383
    # Normal Response Codes: 204
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       itemNotFound (404),
    #                       unauthorized (401),
    #                       buildInProgress (409),
    #                       overLimit (413)
384

Christos Stavrakakis's avatar
Christos Stavrakakis committed
385
    log.info('delete_server %s', server_id)
386
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
387
    delete_instance(vm)
388
    return HttpResponse(status=204)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
389

390

391
@util.api_method('POST')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
392
def server_action(request, server_id):
393
    req = util.get_request_dict(request)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
394
    log.debug('server_action %s %s', server_id, req)
395
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
396
    if len(req) != 1:
397
        raise faults.BadRequest("Malformed request")
398

Giorgos Verigakis's avatar
Giorgos Verigakis committed
399
    key = req.keys()[0]
Giorgos Verigakis's avatar
Giorgos Verigakis committed
400
    val = req[key]
401

Giorgos Verigakis's avatar
Giorgos Verigakis committed
402
403
    try:
        assert isinstance(val, dict)
404
        return server_actions[key](request, vm, req[key])
Giorgos Verigakis's avatar
Giorgos Verigakis committed
405
    except KeyError:
406
        raise faults.BadRequest("Unknown action")
Giorgos Verigakis's avatar
Giorgos Verigakis committed
407
    except AssertionError:
408
        raise faults.BadRequest("Invalid argument")
Giorgos Verigakis's avatar
Giorgos Verigakis committed
409

410

411
@util.api_method('GET')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
412
413
414
415
416
417
418
def list_addresses(request, server_id):
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       overLimit (413)
419

Christos Stavrakakis's avatar
Christos Stavrakakis committed
420
    log.debug('list_addresses %s', server_id)
421
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
422
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
423

Giorgos Verigakis's avatar
Giorgos Verigakis committed
424
    if request.serialization == 'xml':
Giorgos Verigakis's avatar
Giorgos Verigakis committed
425
426
427
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
    else:
        data = json.dumps({'addresses': {'values': addresses}})
428

Giorgos Verigakis's avatar
Giorgos Verigakis committed
429
430
    return HttpResponse(data, status=200)

431

432
@util.api_method('GET')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
433
434
435
436
437
438
439
440
def list_addresses_by_network(request, server_id, network_id):
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       itemNotFound (404),
    #                       overLimit (413)
441

Christos Stavrakakis's avatar
Christos Stavrakakis committed
442
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
443
444
    machine = util.get_vm(server_id, request.user_uniq)
    network = util.get_network(network_id, request.user_uniq)
445
    nic = util.get_nic(machine, network)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
446
    address = nic_to_dict(nic)
447

Giorgos Verigakis's avatar
Giorgos Verigakis committed
448
    if request.serialization == 'xml':
Giorgos Verigakis's avatar
Giorgos Verigakis committed
449
450
451
        data = render_to_string('address.xml', {'address': address})
    else:
        data = json.dumps({'network': address})
452

Giorgos Verigakis's avatar
Giorgos Verigakis committed
453
    return HttpResponse(data, status=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
454

455

456
@util.api_method('GET')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
457
458
459
460
461
462
463
def list_metadata(request, server_id):
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       overLimit (413)
464

Christos Stavrakakis's avatar
Christos Stavrakakis committed
465
    log.debug('list_server_metadata %s', server_id)
466
    vm = util.get_vm(server_id, request.user_uniq)
467
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
468
    return util.render_metadata(request, metadata, use_values=True, status=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
469

470

471
@util.api_method('POST')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
472
473
474
475
476
477
478
479
480
def update_metadata(request, server_id):
    # Normal Response Code: 201
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       buildInProgress (409),
    #                       badMediaType(415),
    #                       overLimit (413)
481

482
    req = util.get_request_dict(request)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
483
    log.info('update_server_metadata %s %s', server_id, req)
484
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
485
486
487
488
    try:
        metadata = req['metadata']
        assert isinstance(metadata, dict)
    except (KeyError, AssertionError):
489
        raise faults.BadRequest("Malformed request")
490

491
492
493
494
    for key, val in metadata.items():
        meta, created = vm.metadata.get_or_create(meta_key=key)
        meta.meta_value = val
        meta.save()
495

496
497
498
    vm.save()
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
    return util.render_metadata(request, vm_meta, status=201)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
499

500

501
@util.api_method('GET')
Giorgos Verigakis's avatar
Giorgos Verigakis committed
502
503
504
505
506
507
508
509
def get_metadata_item(request, server_id, key):
    # Normal Response Codes: 200, 203
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       itemNotFound (404),
    #                       badRequest (400),
    #                       overLimit (413)
510

Christos Stavrakakis's avatar
Christos Stavrakakis committed
511
    log.debug('get_server_metadata_item %s %s', server_id, key)
512
    vm = util.get_vm(server_id, request.user_uniq)
513
    meta = util.get_vm_meta(vm, key)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
514
515
    d = {meta.meta_key: meta.meta_value}
    return util.render_meta(request, d, status=200)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
516

517

518
@util.api_method('PUT')
519
@transaction.commit_on_success
Giorgos Verigakis's avatar
Giorgos Verigakis committed
520
521
522
523
524
525
526
527
528
529
def create_metadata_item(request, server_id, key):
    # Normal Response Code: 201
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       itemNotFound (404),
    #                       badRequest (400),
    #                       buildInProgress (409),
    #                       badMediaType(415),
    #                       overLimit (413)
530

531
    req = util.get_request_dict(request)
Christos Stavrakakis's avatar
Christos Stavrakakis committed
532
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
533
    vm = util.get_vm(server_id, request.user_uniq)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
534
535
536
537
538
539
    try:
        metadict = req['meta']
        assert isinstance(metadict, dict)
        assert len(metadict) == 1
        assert key in metadict
    except (KeyError, AssertionError):
540
        raise faults.BadRequest("Malformed request")
541

542
543
544
    meta, created = VirtualMachineMetadata.objects.get_or_create(
        meta_key=key,
        vm=vm)
545

Giorgos Verigakis's avatar
Giorgos Verigakis committed
546
547
    meta.meta_value = metadict[key]
    meta.save()
548
    vm.save()
Giorgos Verigakis's avatar
Giorgos Verigakis committed
549
550
    d = {meta.meta_key: meta.meta_value}
    return util.render_meta(request, d, status=201)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
551

552

553
@util.api_method('DELETE')
554
@transaction.commit_on_success
Giorgos Verigakis's avatar
Giorgos Verigakis committed
555
556
557
558
559
560
561
562
563
564
def delete_metadata_item(request, server_id, key):
    # Normal Response Code: 204
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       itemNotFound (404),
    #                       badRequest (400),
    #                       buildInProgress (409),
    #                       badMediaType(415),
    #                       overLimit (413),
565

Christos Stavrakakis's avatar
Christos Stavrakakis committed
566
    log.info('delete_server_metadata_item %s %s', server_id, key)
567
    vm = util.get_vm(server_id, request.user_uniq)
568
    meta = util.get_vm_meta(vm, key)
Giorgos Verigakis's avatar
Giorgos Verigakis committed
569
    meta.delete()
570
    vm.save()
Giorgos Verigakis's avatar
Giorgos Verigakis committed
571
    return HttpResponse(status=204)
572

573

574
575
576
577
578
579
580
581
582
@util.api_method('GET')
def server_stats(request, server_id):
    # Normal Response Codes: 200
    # Error Response Codes: computeFault (400, 500),
    #                       serviceUnavailable (503),
    #                       unauthorized (401),
    #                       badRequest (400),
    #                       itemNotFound (404),
    #                       overLimit (413)
583

Christos Stavrakakis's avatar
Christos Stavrakakis committed
584
    log.debug('server_stats %s', server_id)
585
    vm = util.get_vm(server_id, request.user_uniq)
586
587
    #secret = util.encrypt(vm.backend_vm_id)
    secret = vm.backend_vm_id      # XXX disable backend id encryption
588

589
590
591
    stats = {
        'serverRef': vm.id,
        'refresh': settings.STATS_REFRESH_PERIOD,
592
593
594
595
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
596

597
598
599
600
601
602
    if request.serialization == 'xml':
        data = render_to_string('server_stats.xml', stats)
    else:
        data = json.dumps({'stats': stats})

    return HttpResponse(data, status=200)