Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24 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
    # Fix flavor for archipelago
309
    password = util.random_password()
310
    disk_template, provider = util.get_flavor_provider(flavor)
311
    if provider:
312
        flavor.disk_template = disk_template
313
        flavor.disk_provider = provider
314
        flavor.disk_origin = None
315
        if provider == 'vlmc':
316
            flavor.disk_origin = image['checksum']
317
            image['backend_id'] = 'null'
318
    else:
319
        flavor.disk_provider = None
320

    
321
    try:
322
        # Issue commission
323
        serial = quotas.issue_vm_commission(user_id, flavor)
324
        serials.append(serial)
325
        # Make the commission accepted, since in the end of this
326
        # transaction the VM will have been created in the DB.
327
        serial.accepted = True
328
        serial.save()
329

    
330
        # We must save the VM instance now, so that it gets a valid
331
        # vm.backend_vm_id.
332
        vm = VirtualMachine.objects.create(
333
            name=name,
334
            backend=backend,
335
            userid=user_id,
336
            imageid=image_id,
337
            flavor=flavor,
338
            action="CREATE",
339
            serial=serial)
340

    
341
        log.info("Created entry in DB for VM '%s'", vm)
342

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

    
352
        # Also we must create the VM metadata in the same transaction.
353
        for key, val in metadata.items():
354
            VirtualMachineMetadata.objects.create(
355
                meta_key=key,
356
                meta_value=val,
357
                vm=vm)
358
    except:
359
        transaction.rollback()
360
        raise
361
    else:
362
        transaction.commit()
363

    
364
    try:
365
        jobID = create_instance(vm, nic, flavor, image)
366
        # At this point the job is enqueued in the Ganeti backend
367
        vm.backendjobid = jobID
368
        vm.save()
369
        transaction.commit()
370
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
371
                 user_id, vm, nic, backend, str(jobID))
372
    except GanetiApiError as e:
373
        log.exception("Can not communicate to backend %s: %s. Deleting VM %s",
374
                      backend, e, vm)
375
        vm.delete()
376
        transaction.commit()
377
        raise
378
    except:
379
        transaction.rollback()
380
        raise
381

    
382
    server = vm_to_dict(vm, detail=True)
383
    server['status'] = 'BUILD'
384
    server['adminPass'] = password
385

    
386
    respsone = render_server(request, server, status=202)
387

    
388
    return respsone
389

    
390

    
391
@util.api_method('GET')
392
def get_server_details(request, server_id):
393
    # Normal Response Codes: 200, 203
394
    # Error Response Codes: computeFault (400, 500),
395
    #                       serviceUnavailable (503),
396
    #                       unauthorized (401),
397
    #                       badRequest (400),
398
    #                       itemNotFound (404),
399
    #                       overLimit (413)
400

    
401
    log.debug('get_server_details %s', server_id)
402
    vm = util.get_vm(server_id, request.user_uniq)
403
    server = vm_to_dict(vm, detail=True)
404
    return render_server(request, server)
405

    
406

    
407
@util.api_method('PUT')
408
def update_server_name(request, server_id):
409
    # Normal Response Code: 204
410
    # Error Response Codes: computeFault (400, 500),
411
    #                       serviceUnavailable (503),
412
    #                       unauthorized (401),
413
    #                       badRequest (400),
414
    #                       badMediaType(415),
415
    #                       itemNotFound (404),
416
    #                       buildInProgress (409),
417
    #                       overLimit (413)
418

    
419
    req = util.get_request_dict(request)
420
    log.info('update_server_name %s %s', server_id, req)
421

    
422
    try:
423
        name = req['server']['name']
424
    except (TypeError, KeyError):
425
        raise faults.BadRequest("Malformed request")
426

    
427
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
428
                     non_suspended=True)
429
    vm.name = name
430
    vm.save()
431

    
432
    return HttpResponse(status=204)
433

    
434

    
435
@util.api_method('DELETE')
436
@transaction.commit_on_success
437
def delete_server(request, server_id):
438
    # Normal Response Codes: 204
439
    # Error Response Codes: computeFault (400, 500),
440
    #                       serviceUnavailable (503),
441
    #                       unauthorized (401),
442
    #                       itemNotFound (404),
443
    #                       unauthorized (401),
444
    #                       buildInProgress (409),
445
    #                       overLimit (413)
446

    
447
    log.info('delete_server %s', server_id)
448
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
449
                     non_suspended=True)
450
    start_action(vm, 'DESTROY')
451
    delete_instance(vm)
452
    return HttpResponse(status=204)
453

    
454

    
455
# additional server actions
456
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
457

    
458
@util.api_method('POST')
459
def server_action(request, server_id):
460
    req = util.get_request_dict(request)
461
    log.debug('server_action %s %s', server_id, req)
462

    
463

    
464
    if len(req) != 1:
465
        raise faults.BadRequest("Malformed request")
466

    
467
    # Do not allow any action on deleted or suspended VMs
468
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
469
                     non_deleted=True, non_suspended=True)
470

    
471
    try:
472
        key = req.keys()[0]
473
        if key not in ARBITRARY_ACTIONS:
474
            start_action(vm, key_to_action(key))
475
        val = req[key]
476
        assert isinstance(val, dict)
477
        return server_actions[key](request, vm, val)
478
    except KeyError:
479
        raise faults.BadRequest("Unknown action")
480
    except AssertionError:
481
        raise faults.BadRequest("Invalid argument")
482

    
483

    
484
def key_to_action(key):
485
    """Map HTTP request key to a VM Action"""
486
    if key == "shutdown":
487
        return "STOP"
488
    if key == "delete":
489
        return "DESTROY"
490
    if key in ARBITRARY_ACTIONS:
491
        return None
492
    else:
493
        return key.upper()
494

    
495

    
496
def start_action(vm, action):
497
    log.debug("Applying action %s to VM %s", action, vm)
498
    if not action:
499
        return
500

    
501
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
502
        raise faults.ServiceUnavailable("Action %s not supported" % action)
503

    
504
    # No actions to deleted VMs
505
    if vm.deleted:
506
        raise VirtualMachine.DeletedError
507

    
508
    # No actions to machines being built. They may be destroyed, however.
509
    if vm.operstate == 'BUILD' and action != 'DESTROY':
510
        raise VirtualMachine.BuildingError
511

    
512
    vm.action = action
513
    vm.backendjobid = None
514
    vm.backendopcode = None
515
    vm.backendjobstatus = None
516
    vm.backendlogmsg = None
517

    
518
    vm.save()
519

    
520

    
521
@util.api_method('GET')
522
def list_addresses(request, server_id):
523
    # Normal Response Codes: 200, 203
524
    # Error Response Codes: computeFault (400, 500),
525
    #                       serviceUnavailable (503),
526
    #                       unauthorized (401),
527
    #                       badRequest (400),
528
    #                       overLimit (413)
529

    
530
    log.debug('list_addresses %s', server_id)
531
    vm = util.get_vm(server_id, request.user_uniq)
532
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
533

    
534
    if request.serialization == 'xml':
535
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
536
    else:
537
        data = json.dumps({'addresses': {'values': addresses}})
538

    
539
    return HttpResponse(data, status=200)
540

    
541

    
542
@util.api_method('GET')
543
def list_addresses_by_network(request, server_id, network_id):
544
    # Normal Response Codes: 200, 203
545
    # Error Response Codes: computeFault (400, 500),
546
    #                       serviceUnavailable (503),
547
    #                       unauthorized (401),
548
    #                       badRequest (400),
549
    #                       itemNotFound (404),
550
    #                       overLimit (413)
551

    
552
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
553
    machine = util.get_vm(server_id, request.user_uniq)
554
    network = util.get_network(network_id, request.user_uniq)
555
    nic = util.get_nic(machine, network)
556
    address = nic_to_dict(nic)
557

    
558
    if request.serialization == 'xml':
559
        data = render_to_string('address.xml', {'address': address})
560
    else:
561
        data = json.dumps({'network': address})
562

    
563
    return HttpResponse(data, status=200)
564

    
565

    
566
@util.api_method('GET')
567
def list_metadata(request, server_id):
568
    # Normal Response Codes: 200, 203
569
    # Error Response Codes: computeFault (400, 500),
570
    #                       serviceUnavailable (503),
571
    #                       unauthorized (401),
572
    #                       badRequest (400),
573
    #                       overLimit (413)
574

    
575
    log.debug('list_server_metadata %s', server_id)
576
    vm = util.get_vm(server_id, request.user_uniq)
577
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
578
    return util.render_metadata(request, metadata, use_values=True, status=200)
579

    
580

    
581
@util.api_method('POST')
582
def update_metadata(request, server_id):
583
    # Normal Response Code: 201
584
    # Error Response Codes: computeFault (400, 500),
585
    #                       serviceUnavailable (503),
586
    #                       unauthorized (401),
587
    #                       badRequest (400),
588
    #                       buildInProgress (409),
589
    #                       badMediaType(415),
590
    #                       overLimit (413)
591

    
592
    req = util.get_request_dict(request)
593
    log.info('update_server_metadata %s %s', server_id, req)
594
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
595
    try:
596
        metadata = req['metadata']
597
        assert isinstance(metadata, dict)
598
    except (KeyError, AssertionError):
599
        raise faults.BadRequest("Malformed request")
600

    
601
    for key, val in metadata.items():
602
        meta, created = vm.metadata.get_or_create(meta_key=key)
603
        meta.meta_value = val
604
        meta.save()
605

    
606
    vm.save()
607
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
608
    return util.render_metadata(request, vm_meta, status=201)
609

    
610

    
611
@util.api_method('GET')
612
def get_metadata_item(request, server_id, key):
613
    # Normal Response Codes: 200, 203
614
    # Error Response Codes: computeFault (400, 500),
615
    #                       serviceUnavailable (503),
616
    #                       unauthorized (401),
617
    #                       itemNotFound (404),
618
    #                       badRequest (400),
619
    #                       overLimit (413)
620

    
621
    log.debug('get_server_metadata_item %s %s', server_id, key)
622
    vm = util.get_vm(server_id, request.user_uniq)
623
    meta = util.get_vm_meta(vm, key)
624
    d = {meta.meta_key: meta.meta_value}
625
    return util.render_meta(request, d, status=200)
626

    
627

    
628
@util.api_method('PUT')
629
@transaction.commit_on_success
630
def create_metadata_item(request, server_id, key):
631
    # Normal Response Code: 201
632
    # Error Response Codes: computeFault (400, 500),
633
    #                       serviceUnavailable (503),
634
    #                       unauthorized (401),
635
    #                       itemNotFound (404),
636
    #                       badRequest (400),
637
    #                       buildInProgress (409),
638
    #                       badMediaType(415),
639
    #                       overLimit (413)
640

    
641
    req = util.get_request_dict(request)
642
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
643
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
644
    try:
645
        metadict = req['meta']
646
        assert isinstance(metadict, dict)
647
        assert len(metadict) == 1
648
        assert key in metadict
649
    except (KeyError, AssertionError):
650
        raise faults.BadRequest("Malformed request")
651

    
652
    meta, created = VirtualMachineMetadata.objects.get_or_create(
653
        meta_key=key,
654
        vm=vm)
655

    
656
    meta.meta_value = metadict[key]
657
    meta.save()
658
    vm.save()
659
    d = {meta.meta_key: meta.meta_value}
660
    return util.render_meta(request, d, status=201)
661

    
662

    
663
@util.api_method('DELETE')
664
@transaction.commit_on_success
665
def delete_metadata_item(request, server_id, key):
666
    # Normal Response Code: 204
667
    # Error Response Codes: computeFault (400, 500),
668
    #                       serviceUnavailable (503),
669
    #                       unauthorized (401),
670
    #                       itemNotFound (404),
671
    #                       badRequest (400),
672
    #                       buildInProgress (409),
673
    #                       badMediaType(415),
674
    #                       overLimit (413),
675

    
676
    log.info('delete_server_metadata_item %s %s', server_id, key)
677
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
678
    meta = util.get_vm_meta(vm, key)
679
    meta.delete()
680
    vm.save()
681
    return HttpResponse(status=204)
682

    
683

    
684
@util.api_method('GET')
685
def server_stats(request, server_id):
686
    # Normal Response Codes: 200
687
    # Error Response Codes: computeFault (400, 500),
688
    #                       serviceUnavailable (503),
689
    #                       unauthorized (401),
690
    #                       badRequest (400),
691
    #                       itemNotFound (404),
692
    #                       overLimit (413)
693

    
694
    log.debug('server_stats %s', server_id)
695
    vm = util.get_vm(server_id, request.user_uniq)
696
    #secret = util.encrypt(vm.backend_vm_id)
697
    secret = vm.backend_vm_id      # XXX disable backend id encryption
698

    
699
    stats = {
700
        'serverRef': vm.id,
701
        'refresh': settings.STATS_REFRESH_PERIOD,
702
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
703
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
704
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
705
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
706

    
707
    if request.serialization == 'xml':
708
        data = render_to_string('server_stats.xml', stats)
709
    else:
710
        data = json.dumps({'stats': stats})
711

    
712
    return HttpResponse(data, status=200)