Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.3 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.conf import settings
37
from django.conf.urls.defaults import patterns
38
from django.db import transaction
39
from django.http import HttpResponse
40
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
45
from synnefo.api.common import method_not_allowed
46
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
47
from synnefo.logic.backend import create_instance, delete_instance
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

    
54
from logging import getLogger
55
log = getLogger('synnefo.api')
56

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

    
70

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

    
79

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

    
90

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

    
99

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

    
110

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

    
118
    if nic.firewall_profile:
119
        d['firewallProfile'] = nic.firewall_profile
120
    return d
121

    
122

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

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

    
140
        attachments = [nic_to_dict(nic) for nic in vm.nics.order_by('index')]
141
        if attachments:
142
            d['attachments'] = {'values': attachments}
143

    
144
        # include the latest vm diagnostic, if set
145
        diagnostic = vm.get_last_diagnostic()
146
        if diagnostic:
147
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
148

    
149
    return d
150

    
151

    
152
def diagnostics_to_dict(diagnostics):
153
    """
154
    Extract api data from diagnostics QuerySet.
155
    """
156
    entries = list()
157

    
158
    for diagnostic in diagnostics:
159
        # format source date if set
160
        formatted_source_date = None
161
        if diagnostic.source_date:
162
            formatted_source_date = util.isoformat(diagnostic.source_date)
163

    
164
        entry = {
165
            'source': diagnostic.source,
166
            'created': util.isoformat(diagnostic.created),
167
            'message': diagnostic.message,
168
            'details': diagnostic.details,
169
            'level': diagnostic.level,
170
        }
171

    
172
        if formatted_source_date:
173
            entry['source_date'] = formatted_source_date
174

    
175
        entries.append(entry)
176

    
177
    return entries
178

    
179

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

    
189

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

    
196

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

    
207

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

    
217
    log.debug('list_servers detail=%s', detail)
218
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
219

    
220
    since = util.isoparse(request.GET.get('changes-since'))
221

    
222
    if since:
223
        user_vms = user_vms.filter(updated__gte=since)
224
        if not user_vms:
225
            return HttpResponse(status=304)
226
    else:
227
        user_vms = user_vms.filter(deleted=False)
228

    
229
    servers = [vm_to_dict(server, detail)\
230
               for server in user_vms.order_by('id')]
231

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

    
239
    return HttpResponse(data, status=200)
240

    
241

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

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

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

    
277
        for p in personality:
278
            # Verify that personalities are well-formed
279
            try:
280
                assert isinstance(p, dict)
281
                keys = set(p.keys())
282
                allowed = set(['contents', 'group', 'mode', 'owner', 'path'])
283
                assert keys.issubset(allowed)
284
                contents = p['contents']
285
                if len(contents) > settings.MAX_PERSONALITY_SIZE:
286
                    # No need to decode if contents already exceed limit
287
                    raise faults.OverLimit("Maximum size of personality exceeded")
288
                if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE:
289
                    raise faults.OverLimit("Maximum size of personality exceeded")
290
            except AssertionError:
291
                raise faults.BadRequest("Malformed personality in request")
292

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

    
301
        # Ensure that request if for active flavor
302
        flavor = util.get_flavor(flavor_id, include_deleted=False)
303
        password = util.random_password()
304

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

    
308
        # get user limit
309
        vms_limit_for_user = \
310
            settings.VMS_USER_QUOTA.get(request.user_uniq,
311
                    settings.MAX_VMS_PER_USER)
312

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

    
316
        backend_allocator = BackendAllocator()
317
        backend = backend_allocator.allocate(request.user_uniq, flavor)
318

    
319
        if backend is None:
320
            log.error("No available backends for VM with flavor %s", flavor)
321
            raise faults.ServiceUnavailable("No available backends")
322
    except:
323
        transaction.rollback()
324
        raise
325
    else:
326
        transaction.commit()
327

    
328
    try:
329
        if settings.PUBLIC_USE_POOL:
330
            (network, address) = util.allocate_public_address(backend)
331
            if address is None:
332
                log.error("Public networks of backend %s are full", backend)
333
                msg = "Failed to allocate public IP for new VM"
334
                raise faults.ServiceUnavailable(msg)
335
            nic = {'ip': address, 'network': network.backend_id}
336
        else:
337
            network = choice(list(util.backend_public_networks(backend)))
338
            nic = {'ip': 'pool', 'network': network.backend_id}
339
    except:
340
        transaction.rollback()
341
        raise
342
    else:
343
        transaction.commit()
344

    
345
    try:
346
        # We must save the VM instance now, so that it gets a valid
347
        # vm.backend_vm_id.
348
        vm = VirtualMachine.objects.create(
349
            name=name,
350
            backend=backend,
351
            userid=request.user_uniq,
352
            imageid=image_id,
353
            flavor=flavor,
354
            action="CREATE")
355

    
356
        try:
357
            jobID = create_instance(vm, nic, flavor, image, password, personality)
358
        except GanetiApiError:
359
            vm.delete()
360
            raise
361

    
362
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
363
                request.user_uniq, vm, nic, backend, str(jobID))
364

    
365
        vm.backendjobid = jobID
366
        vm.save()
367

    
368
        for key, val in metadata.items():
369
            VirtualMachineMetadata.objects.create(
370
                meta_key=key,
371
                meta_value=val,
372
                vm=vm)
373

    
374
        server = vm_to_dict(vm, detail=True)
375
        server['status'] = 'BUILD'
376
        server['adminPass'] = password
377

    
378
        respsone = render_server(request, server, status=202)
379
    except:
380
        transaction.rollback()
381
        raise
382
    else:
383
        transaction.commit()
384

    
385
    return respsone
386

    
387

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

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

    
403

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

    
416
    req = util.get_request_dict(request)
417
    log.info('update_server_name %s %s', server_id, req)
418

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

    
424
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
425
                     non_suspended=True)
426
    vm.name = name
427
    vm.save()
428

    
429
    return HttpResponse(status=204)
430

    
431

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

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

    
451

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

    
455
@util.api_method('POST')
456
def server_action(request, server_id):
457
    req = util.get_request_dict(request)
458
    log.debug('server_action %s %s', server_id, req)
459

    
460

    
461
    if len(req) != 1:
462
        raise faults.BadRequest("Malformed request")
463

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

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

    
480

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

    
492

    
493
def start_action(vm, action):
494
    log.debug("Applying action %s to VM %s", action, vm)
495
    if not action:
496
        return
497

    
498
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
499
        raise faults.ServiceUnavailable("Action %s not supported" % action)
500

    
501
    # No actions to deleted VMs
502
    if vm.deleted:
503
        raise VirtualMachine.DeletedError
504

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

    
509
    vm.action = action
510
    vm.backendjobid = None
511
    vm.backendopcode = None
512
    vm.backendjobstatus = None
513
    vm.backendlogmsg = None
514

    
515
    vm.save()
516

    
517

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

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

    
531
    if request.serialization == 'xml':
532
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
533
    else:
534
        data = json.dumps({'addresses': {'values': addresses}})
535

    
536
    return HttpResponse(data, status=200)
537

    
538

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

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

    
555
    if request.serialization == 'xml':
556
        data = render_to_string('address.xml', {'address': address})
557
    else:
558
        data = json.dumps({'network': address})
559

    
560
    return HttpResponse(data, status=200)
561

    
562

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

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

    
577

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

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

    
598
    for key, val in metadata.items():
599
        meta, created = vm.metadata.get_or_create(meta_key=key)
600
        meta.meta_value = val
601
        meta.save()
602

    
603
    vm.save()
604
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
605
    return util.render_metadata(request, vm_meta, status=201)
606

    
607

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

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

    
624

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

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

    
649
    meta, created = VirtualMachineMetadata.objects.get_or_create(
650
        meta_key=key,
651
        vm=vm)
652

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

    
659

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

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

    
680

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

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

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

    
704
    if request.serialization == 'xml':
705
        data = render_to_string('server_stats.xml', stats)
706
    else:
707
        data = json.dumps({'stats': stats})
708

    
709
    return HttpResponse(data, status=200)