Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / servers.py @ b7f21824

History | View | Annotate | Download (23.6 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 base64 import b64decode
35

    
36
from django import dispatch
37
from django.conf import settings
38
from django.conf.urls.defaults import patterns
39
from django.db import transaction
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 synnefo.api import faults, util
45
from synnefo.api.actions import server_actions
46
from synnefo.api.common import method_not_allowed
47
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
48
from synnefo.logic.backend import create_instance, delete_instance
49
from synnefo.logic.utils import get_rsapi_state
50
from synnefo.logic.rapi import GanetiApiError
51
from synnefo.logic.backend_allocator import BackendAllocator
52
from synnefo import quotas
53

    
54
# server creation signal
55
server_created = dispatch.Signal(providing_args=["created_vm_params"])
56

    
57
from logging import getLogger
58
log = getLogger('synnefo.api')
59

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

    
73

    
74
def demux(request):
75
    if request.method == 'GET':
76
        return list_servers(request)
77
    elif request.method == 'POST':
78
        return create_server(request)
79
    else:
80
        return method_not_allowed(request)
81

    
82

    
83
def server_demux(request, server_id):
84
    if request.method == 'GET':
85
        return get_server_details(request, server_id)
86
    elif request.method == 'PUT':
87
        return update_server_name(request, server_id)
88
    elif request.method == 'DELETE':
89
        return delete_server(request, server_id)
90
    else:
91
        return method_not_allowed(request)
92

    
93

    
94
def metadata_demux(request, server_id):
95
    if request.method == 'GET':
96
        return list_metadata(request, server_id)
97
    elif request.method == 'POST':
98
        return update_metadata(request, server_id)
99
    else:
100
        return method_not_allowed(request)
101

    
102

    
103
def metadata_item_demux(request, server_id, key):
104
    if request.method == 'GET':
105
        return get_metadata_item(request, server_id, key)
106
    elif request.method == 'PUT':
107
        return create_metadata_item(request, server_id, key)
108
    elif request.method == 'DELETE':
109
        return delete_metadata_item(request, server_id, key)
110
    else:
111
        return method_not_allowed(request)
112

    
113

    
114
def nic_to_dict(nic):
115
    d = {'id': util.construct_nic_id(nic),
116
         'network_id': str(nic.network.id),
117
         'mac_address': nic.mac,
118
         'ipv4': nic.ipv4 if nic.ipv4 else None,
119
         'ipv6': nic.ipv6 if nic.ipv6 else None}
120

    
121
    if nic.firewall_profile:
122
        d['firewallProfile'] = nic.firewall_profile
123
    return d
124

    
125

    
126
def vm_to_dict(vm, detail=False):
127
    d = dict(id=vm.id, name=vm.name)
128
    if detail:
129
        d['status'] = get_rsapi_state(vm)
130
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' \
131
                        else vm.buildpercentage
132
        d['hostId'] = vm.hostid
133
        d['updated'] = util.isoformat(vm.updated)
134
        d['created'] = util.isoformat(vm.created)
135
        d['flavorRef'] = vm.flavor.id
136
        d['imageRef'] = vm.imageid
137
        d['suspended'] = vm.suspended
138

    
139
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
140
        if metadata:
141
            d['metadata'] = {'values': metadata}
142

    
143
        attachments = [nic_to_dict(nic) for nic in vm.nics.order_by('index')]
144
        if attachments:
145
            d['attachments'] = {'values': attachments}
146

    
147
        # include the latest vm diagnostic, if set
148
        diagnostic = vm.get_last_diagnostic()
149
        if diagnostic:
150
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
151

    
152
    return d
153

    
154

    
155
def diagnostics_to_dict(diagnostics):
156
    """
157
    Extract api data from diagnostics QuerySet.
158
    """
159
    entries = list()
160

    
161
    for diagnostic in diagnostics:
162
        # format source date if set
163
        formatted_source_date = None
164
        if diagnostic.source_date:
165
            formatted_source_date = util.isoformat(diagnostic.source_date)
166

    
167
        entry = {
168
            'source': diagnostic.source,
169
            'created': util.isoformat(diagnostic.created),
170
            'message': diagnostic.message,
171
            'details': diagnostic.details,
172
            'level': diagnostic.level,
173
        }
174

    
175
        if formatted_source_date:
176
            entry['source_date'] = formatted_source_date
177

    
178
        entries.append(entry)
179

    
180
    return entries
181

    
182

    
183
def render_server(request, server, status=200):
184
    if request.serialization == 'xml':
185
        data = render_to_string('server.xml', {
186
            'server': server,
187
            'is_root': True})
188
    else:
189
        data = json.dumps({'server': server})
190
    return HttpResponse(data, status=status)
191

    
192

    
193
def render_diagnostics(request, diagnostics_dict, status=200):
194
    """
195
    Render diagnostics dictionary to json response.
196
    """
197
    return HttpResponse(json.dumps(diagnostics_dict), status=status)
198

    
199

    
200
@util.api_method('GET')
201
def get_server_diagnostics(request, server_id):
202
    """
203
    Virtual machine diagnostics api view.
204
    """
205
    log.debug('server_diagnostics %s', server_id)
206
    vm = util.get_vm(server_id, request.user_uniq)
207
    diagnostics = diagnostics_to_dict(vm.diagnostics.all())
208
    return render_diagnostics(request, diagnostics)
209

    
210

    
211
@util.api_method('GET')
212
def list_servers(request, detail=False):
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
    log.debug('list_servers detail=%s', detail)
221
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
222

    
223
    since = util.isoparse(request.GET.get('changes-since'))
224

    
225
    if since:
226
        user_vms = user_vms.filter(updated__gte=since)
227
        if not user_vms:
228
            return HttpResponse(status=304)
229
    else:
230
        user_vms = user_vms.filter(deleted=False)
231

    
232
    servers = [vm_to_dict(server, detail)\
233
               for server in user_vms.order_by('id')]
234

    
235
    if request.serialization == 'xml':
236
        data = render_to_string('list_servers.xml', {
237
            'servers': servers,
238
            'detail': detail})
239
    else:
240
        data = json.dumps({'servers': {'values': servers}})
241

    
242
    return HttpResponse(data, status=200)
243

    
244

    
245
@util.api_method('POST')
246
# Use manual transactions. Backend and IP pool allocations need exclusive
247
# access (SELECT..FOR UPDATE). Running create_server with commit_on_success
248
# would result in backends and public networks to be locked until the job is
249
# sent to the Ganeti backend.
250
@quotas.uses_commission
251
@transaction.commit_manually
252
def create_server(serials, request):
253
    # Normal Response Code: 202
254
    # Error Response Codes: computeFault (400, 500),
255
    #                       serviceUnavailable (503),
256
    #                       unauthorized (401),
257
    #                       badMediaType(415),
258
    #                       itemNotFound (404),
259
    #                       badRequest (400),
260
    #                       serverCapacityUnavailable (503),
261
    #                       overLimit (413)
262
    try:
263
        req = util.get_request_dict(request)
264
        log.info('create_server %s', req)
265
        user_id = request.user_uniq
266

    
267
        try:
268
            server = req['server']
269
            name = server['name']
270
            metadata = server.get('metadata', {})
271
            assert isinstance(metadata, dict)
272
            image_id = server['imageRef']
273
            flavor_id = server['flavorRef']
274
            personality = server.get('personality', [])
275
            assert isinstance(personality, list)
276
        except (KeyError, AssertionError):
277
            raise faults.BadRequest("Malformed request")
278

    
279
        # Verify that personalities are well-formed
280
        util.verify_personality(personality)
281
        # Get image information
282
        image = util.get_image_dict(image_id, user_id)
283
        # Get flavor (ensure it is active)
284
        flavor = util.get_flavor(flavor_id, include_deleted=False)
285
        # Allocate VM to backend
286
        backend_allocator = BackendAllocator()
287
        backend = backend_allocator.allocate(request.user_uniq, flavor)
288

    
289
        if backend is None:
290
            log.error("No available backends for VM with flavor %s", flavor)
291
            raise faults.ServiceUnavailable("No available backends")
292
    except:
293
        transaction.rollback()
294
        raise
295
    else:
296
        transaction.commit()
297

    
298
    # Allocate IP from public network
299
    try:
300
        (network, address) = util.get_public_ip(backend)
301
        nic = {'ip': address, 'network': network.backend_id}
302
    except:
303
        transaction.rollback()
304
        raise
305
    else:
306
        transaction.commit()
307

    
308
    try:
309
        # Issue commission
310
        serial = quotas.issue_vm_commission(user_id, flavor)
311
        serials.append(serial)
312
        # Make the commission accepted, since in the end of this
313
        # transaction the VM will have been created in the DB.
314
        serial.accepted = True
315
        serial.save()
316

    
317
        # We must save the VM instance now, so that it gets a valid
318
        # vm.backend_vm_id.
319
        vm = VirtualMachine.objects.create(
320
            name=name,
321
            backend=backend,
322
            userid=user_id,
323
            imageid=image_id,
324
            flavor=flavor,
325
            action="CREATE",
326
            serial=serial)
327

    
328
        password = util.random_password()
329

    
330
        disk_template, provider = util.get_flavor_provider(flavor)
331
        if provider:
332
            flavor.disk_template = disk_template
333
            flavor.disk_provider = provider
334
            flavor.disk_origin = None
335
            if provider == 'vlmc':
336
                flavor.disk_origin = image['backend_id']
337
                image['backend_id'] = 'null'
338
        else:
339
            flavor.disk_provider = None
340

    
341
        # dispatch server created signal
342
        server_created.send(sender=vm, created_vm_params={
343
            'img_id': image['backend_id'],
344
            'img_passwd': password,
345
            'img_format': str(image['format']),
346
            'img_personality': json.dumps(personality),
347
            'img_properties': json.dumps(image['metadata']),
348
        })
349

    
350
        try:
351
            jobID = create_instance(vm, nic, flavor, image)
352
        except GanetiApiError:
353
            vm.delete()
354
            raise
355

    
356
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
357
                user_id, vm, nic, backend, str(jobID))
358

    
359
        vm.backendjobid = jobID
360
        vm.save()
361

    
362
        for key, val in metadata.items():
363
            VirtualMachineMetadata.objects.create(
364
                meta_key=key,
365
                meta_value=val,
366
                vm=vm)
367

    
368
        server = vm_to_dict(vm, detail=True)
369
        server['status'] = 'BUILD'
370
        server['adminPass'] = password
371

    
372
        respsone = render_server(request, server, status=202)
373
    except:
374
        transaction.rollback()
375
        raise
376
    else:
377
        transaction.commit()
378

    
379
    return respsone
380

    
381

    
382
@util.api_method('GET')
383
def get_server_details(request, server_id):
384
    # Normal Response Codes: 200, 203
385
    # Error Response Codes: computeFault (400, 500),
386
    #                       serviceUnavailable (503),
387
    #                       unauthorized (401),
388
    #                       badRequest (400),
389
    #                       itemNotFound (404),
390
    #                       overLimit (413)
391

    
392
    log.debug('get_server_details %s', server_id)
393
    vm = util.get_vm(server_id, request.user_uniq)
394
    server = vm_to_dict(vm, detail=True)
395
    return render_server(request, server)
396

    
397

    
398
@util.api_method('PUT')
399
def update_server_name(request, server_id):
400
    # Normal Response Code: 204
401
    # Error Response Codes: computeFault (400, 500),
402
    #                       serviceUnavailable (503),
403
    #                       unauthorized (401),
404
    #                       badRequest (400),
405
    #                       badMediaType(415),
406
    #                       itemNotFound (404),
407
    #                       buildInProgress (409),
408
    #                       overLimit (413)
409

    
410
    req = util.get_request_dict(request)
411
    log.info('update_server_name %s %s', server_id, req)
412

    
413
    try:
414
        name = req['server']['name']
415
    except (TypeError, KeyError):
416
        raise faults.BadRequest("Malformed request")
417

    
418
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
419
                     non_suspended=True)
420
    vm.name = name
421
    vm.save()
422

    
423
    return HttpResponse(status=204)
424

    
425

    
426
@util.api_method('DELETE')
427
@transaction.commit_on_success
428
def delete_server(request, server_id):
429
    # Normal Response Codes: 204
430
    # Error Response Codes: computeFault (400, 500),
431
    #                       serviceUnavailable (503),
432
    #                       unauthorized (401),
433
    #                       itemNotFound (404),
434
    #                       unauthorized (401),
435
    #                       buildInProgress (409),
436
    #                       overLimit (413)
437

    
438
    log.info('delete_server %s', server_id)
439
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
440
                     non_suspended=True)
441
    start_action(vm, 'DESTROY')
442
    delete_instance(vm)
443
    return HttpResponse(status=204)
444

    
445

    
446
@util.api_method('POST')
447
def server_action(request, server_id):
448
    req = util.get_request_dict(request)
449
    log.debug('server_action %s %s', server_id, req)
450

    
451
    # additional server actions
452
    ARBITRARY_ACTIONS = ['console', 'firewallProfile']
453

    
454
    if len(req) != 1:
455
        raise faults.BadRequest("Malformed request")
456

    
457
    # Do not allow any action on deleted or suspended VMs
458
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
459
                     non_deleted=True, non_suspended=True)
460

    
461
    try:
462
        key = req.keys()[0]
463
        if key not in ARBITRARY_ACTIONS:
464
            start_action(vm, key_to_action(key))
465
        val = req[key]
466
        assert isinstance(val, dict)
467
        return server_actions[key](request, vm, val)
468
    except KeyError:
469
        raise faults.BadRequest("Unknown action")
470
    except AssertionError:
471
        raise faults.BadRequest("Invalid argument")
472

    
473

    
474
def key_to_action(key):
475
    """Map HTTP request key to a VM Action"""
476
    if key == "shutdown":
477
        return "STOP"
478
    if key == "delete":
479
        return "DESTROY"
480
    if key in ARBITRARY_ACTIONS:
481
        return None
482
    else:
483
        return key.upper()
484

    
485

    
486
def start_action(vm, action):
487
    log.debug("Applying action %s to VM %s", action, vm)
488
    if not action:
489
        return
490

    
491
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
492
        raise faults.ServiceUnavailable("Action %s not supported" % action)
493

    
494
    # No actions to deleted VMs
495
    if vm.deleted:
496
        raise VirtualMachine.DeletedError
497

    
498
    # No actions to machines being built. They may be destroyed, however.
499
    if vm.operstate == 'BUILD' and action != 'DESTROY':
500
        raise VirtualMachine.BuildingError
501

    
502
    vm.action = action
503
    vm.backendjobid = None
504
    vm.backendopcode = None
505
    vm.backendjobstatus = None
506
    vm.backendlogmsg = None
507

    
508
    vm.save()
509

    
510

    
511
@util.api_method('GET')
512
def list_addresses(request, server_id):
513
    # Normal Response Codes: 200, 203
514
    # Error Response Codes: computeFault (400, 500),
515
    #                       serviceUnavailable (503),
516
    #                       unauthorized (401),
517
    #                       badRequest (400),
518
    #                       overLimit (413)
519

    
520
    log.debug('list_addresses %s', server_id)
521
    vm = util.get_vm(server_id, request.user_uniq)
522
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
523

    
524
    if request.serialization == 'xml':
525
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
526
    else:
527
        data = json.dumps({'addresses': {'values': addresses}})
528

    
529
    return HttpResponse(data, status=200)
530

    
531

    
532
@util.api_method('GET')
533
def list_addresses_by_network(request, server_id, network_id):
534
    # Normal Response Codes: 200, 203
535
    # Error Response Codes: computeFault (400, 500),
536
    #                       serviceUnavailable (503),
537
    #                       unauthorized (401),
538
    #                       badRequest (400),
539
    #                       itemNotFound (404),
540
    #                       overLimit (413)
541

    
542
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
543
    machine = util.get_vm(server_id, request.user_uniq)
544
    network = util.get_network(network_id, request.user_uniq)
545
    nic = util.get_nic(machine, network)
546
    address = nic_to_dict(nic)
547

    
548
    if request.serialization == 'xml':
549
        data = render_to_string('address.xml', {'address': address})
550
    else:
551
        data = json.dumps({'network': address})
552

    
553
    return HttpResponse(data, status=200)
554

    
555

    
556
@util.api_method('GET')
557
def list_metadata(request, server_id):
558
    # Normal Response Codes: 200, 203
559
    # Error Response Codes: computeFault (400, 500),
560
    #                       serviceUnavailable (503),
561
    #                       unauthorized (401),
562
    #                       badRequest (400),
563
    #                       overLimit (413)
564

    
565
    log.debug('list_server_metadata %s', server_id)
566
    vm = util.get_vm(server_id, request.user_uniq)
567
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
568
    return util.render_metadata(request, metadata, use_values=True, status=200)
569

    
570

    
571
@util.api_method('POST')
572
def update_metadata(request, server_id):
573
    # Normal Response Code: 201
574
    # Error Response Codes: computeFault (400, 500),
575
    #                       serviceUnavailable (503),
576
    #                       unauthorized (401),
577
    #                       badRequest (400),
578
    #                       buildInProgress (409),
579
    #                       badMediaType(415),
580
    #                       overLimit (413)
581

    
582
    req = util.get_request_dict(request)
583
    log.info('update_server_metadata %s %s', server_id, req)
584
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
585
    try:
586
        metadata = req['metadata']
587
        assert isinstance(metadata, dict)
588
    except (KeyError, AssertionError):
589
        raise faults.BadRequest("Malformed request")
590

    
591
    for key, val in metadata.items():
592
        meta, created = vm.metadata.get_or_create(meta_key=key)
593
        meta.meta_value = val
594
        meta.save()
595

    
596
    vm.save()
597
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
598
    return util.render_metadata(request, vm_meta, status=201)
599

    
600

    
601
@util.api_method('GET')
602
def get_metadata_item(request, server_id, key):
603
    # Normal Response Codes: 200, 203
604
    # Error Response Codes: computeFault (400, 500),
605
    #                       serviceUnavailable (503),
606
    #                       unauthorized (401),
607
    #                       itemNotFound (404),
608
    #                       badRequest (400),
609
    #                       overLimit (413)
610

    
611
    log.debug('get_server_metadata_item %s %s', server_id, key)
612
    vm = util.get_vm(server_id, request.user_uniq)
613
    meta = util.get_vm_meta(vm, key)
614
    d = {meta.meta_key: meta.meta_value}
615
    return util.render_meta(request, d, status=200)
616

    
617

    
618
@util.api_method('PUT')
619
@transaction.commit_on_success
620
def create_metadata_item(request, server_id, key):
621
    # Normal Response Code: 201
622
    # Error Response Codes: computeFault (400, 500),
623
    #                       serviceUnavailable (503),
624
    #                       unauthorized (401),
625
    #                       itemNotFound (404),
626
    #                       badRequest (400),
627
    #                       buildInProgress (409),
628
    #                       badMediaType(415),
629
    #                       overLimit (413)
630

    
631
    req = util.get_request_dict(request)
632
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
633
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
634
    try:
635
        metadict = req['meta']
636
        assert isinstance(metadict, dict)
637
        assert len(metadict) == 1
638
        assert key in metadict
639
    except (KeyError, AssertionError):
640
        raise faults.BadRequest("Malformed request")
641

    
642
    meta, created = VirtualMachineMetadata.objects.get_or_create(
643
        meta_key=key,
644
        vm=vm)
645

    
646
    meta.meta_value = metadict[key]
647
    meta.save()
648
    vm.save()
649
    d = {meta.meta_key: meta.meta_value}
650
    return util.render_meta(request, d, status=201)
651

    
652

    
653
@util.api_method('DELETE')
654
@transaction.commit_on_success
655
def delete_metadata_item(request, server_id, key):
656
    # Normal Response Code: 204
657
    # Error Response Codes: computeFault (400, 500),
658
    #                       serviceUnavailable (503),
659
    #                       unauthorized (401),
660
    #                       itemNotFound (404),
661
    #                       badRequest (400),
662
    #                       buildInProgress (409),
663
    #                       badMediaType(415),
664
    #                       overLimit (413),
665

    
666
    log.info('delete_server_metadata_item %s %s', server_id, key)
667
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
668
    meta = util.get_vm_meta(vm, key)
669
    meta.delete()
670
    vm.save()
671
    return HttpResponse(status=204)
672

    
673

    
674
@util.api_method('GET')
675
def server_stats(request, server_id):
676
    # Normal Response Codes: 200
677
    # Error Response Codes: computeFault (400, 500),
678
    #                       serviceUnavailable (503),
679
    #                       unauthorized (401),
680
    #                       badRequest (400),
681
    #                       itemNotFound (404),
682
    #                       overLimit (413)
683

    
684
    log.debug('server_stats %s', server_id)
685
    vm = util.get_vm(server_id, request.user_uniq)
686
    #secret = util.encrypt(vm.backend_vm_id)
687
    secret = vm.backend_vm_id      # XXX disable backend id encryption
688

    
689
    stats = {
690
        'serverRef': vm.id,
691
        'refresh': settings.STATS_REFRESH_PERIOD,
692
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
693
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
694
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
695
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
696

    
697
    if request.serialization == 'xml':
698
        data = render_to_string('server_stats.xml', stats)
699
    else:
700
        data = json.dumps({'stats': stats})
701

    
702
    return HttpResponse(data, status=200)