Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.2 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
@util.api_method('POST')
453
def server_action(request, server_id):
454
    req = util.get_request_dict(request)
455
    log.debug('server_action %s %s', server_id, req)
456

    
457
    if len(req) != 1:
458
        raise faults.BadRequest("Malformed request")
459

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

    
464
    try:
465
        key = req.keys()[0]
466
        if key != 'console':
467
            start_action(vm, key_to_action(key))
468
        val = req[key]
469
        assert isinstance(val, dict)
470
        return server_actions[key](request, vm, val)
471
    except KeyError:
472
        raise faults.BadRequest("Unknown action")
473
    except AssertionError:
474
        raise faults.BadRequest("Invalid argument")
475

    
476

    
477
def key_to_action(key):
478
    """Map HTTP request key to a VM Action"""
479
    if key == "shutdown":
480
        return "STOP"
481
    if key == "delete":
482
        return "DESTROY"
483
    if key == "console":
484
        return None
485
    else:
486
        return key.upper()
487

    
488

    
489
def start_action(vm, action):
490
    log.debug("Applying action %s to VM %s", action, vm)
491
    if not action:
492
        return
493

    
494
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
495
        raise faults.ServiceUnavailable("Action %s not supported" % action)
496

    
497
    # No actions to deleted VMs
498
    if vm.deleted:
499
        raise VirtualMachine.DeletedError
500

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

    
505
    vm.action = action
506
    vm.backendjobid = None
507
    vm.backendopcode = None
508
    vm.backendjobstatus = None
509
    vm.backendlogmsg = None
510

    
511
    vm.save()
512

    
513

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

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

    
527
    if request.serialization == 'xml':
528
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
529
    else:
530
        data = json.dumps({'addresses': {'values': addresses}})
531

    
532
    return HttpResponse(data, status=200)
533

    
534

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

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

    
551
    if request.serialization == 'xml':
552
        data = render_to_string('address.xml', {'address': address})
553
    else:
554
        data = json.dumps({'network': address})
555

    
556
    return HttpResponse(data, status=200)
557

    
558

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

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

    
573

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

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

    
594
    for key, val in metadata.items():
595
        meta, created = vm.metadata.get_or_create(meta_key=key)
596
        meta.meta_value = val
597
        meta.save()
598

    
599
    vm.save()
600
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
601
    return util.render_metadata(request, vm_meta, status=201)
602

    
603

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

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

    
620

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

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

    
645
    meta, created = VirtualMachineMetadata.objects.get_or_create(
646
        meta_key=key,
647
        vm=vm)
648

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

    
655

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

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

    
676

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

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

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

    
700
    if request.serialization == 'xml':
701
        data = render_to_string('server_stats.xml', stats)
702
    else:
703
        data = json.dumps({'stats': stats})
704

    
705
    return HttpResponse(data, status=200)