Statistics
| Branch: | Tag: | Revision:

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

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

    
74

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

    
83

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

    
94

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

    
103

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

    
114

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

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

    
126

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

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

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

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

    
153
    return d
154

    
155

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

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

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

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

    
179
        entries.append(entry)
180

    
181
    return entries
182

    
183

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

    
193

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

    
200

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

    
211

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

    
221
    log.debug('list_servers detail=%s', detail)
222
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
223

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

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

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

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

    
243
    return HttpResponse(data, status=200)
244

    
245

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

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

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

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

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

    
309
    # Fix flavor for archipelago
310
    password = util.random_password()
311
    disk_template, provider = util.get_flavor_provider(flavor)
312
    if provider:
313
        flavor.disk_template = disk_template
314
        flavor.disk_provider = provider
315
        flavor.disk_origin = None
316
        if provider == 'vlmc':
317
            flavor.disk_origin = image['checksum']
318
            image['backend_id'] = 'null'
319
    else:
320
        flavor.disk_provider = None
321

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

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

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

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

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

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

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

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

    
389
    return respsone
390

    
391

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

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

    
407

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

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

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

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

    
433
    return HttpResponse(status=204)
434

    
435

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

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

    
455

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

    
459

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

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

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

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

    
484

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

    
496

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

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

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

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

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

    
519
    vm.save()
520

    
521

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

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

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

    
540
    return HttpResponse(data, status=200)
541

    
542

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

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

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

    
564
    return HttpResponse(data, status=200)
565

    
566

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

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

    
581

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

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

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

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

    
611

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

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

    
628

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

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

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

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

    
663

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

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

    
684

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

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

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

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

    
713
    return HttpResponse(data, status=200)