Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (23.5 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from base64 import b64decode
35

    
36
from django import dispatch
37
from django.conf import settings
38
from django.conf.urls.defaults import patterns
39
from django.db import transaction
40
from django.http import HttpResponse
41
from django.template.loader import render_to_string
42
from django.utils import simplejson as json
43

    
44
from synnefo.api import faults, util
45
from synnefo.api.actions import server_actions
46
from synnefo.api.common import method_not_allowed
47
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata
48
from synnefo.logic.backend import create_instance, delete_instance
49
from synnefo.logic.utils import get_rsapi_state
50
from synnefo.logic.rapi import GanetiApiError
51
from synnefo.logic.backend_allocator import BackendAllocator
52
from synnefo import quotas
53

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

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

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

    
73

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

    
82

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

    
93

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

    
102

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

    
113

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

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

    
125

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

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

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

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

    
152
    return d
153

    
154

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

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

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

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

    
178
        entries.append(entry)
179

    
180
    return entries
181

    
182

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

    
192

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

    
199

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

    
210

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

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

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

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

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

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

    
242
    return HttpResponse(data, status=200)
243

    
244

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

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

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

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

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

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

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

    
328
        password = util.random_password()
329

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

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

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

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

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

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

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

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

    
379
    return respsone
380

    
381

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

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

    
397

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

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

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

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

    
423
    return HttpResponse(status=204)
424

    
425

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

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

    
445

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

    
451
    if len(req) != 1:
452
        raise faults.BadRequest("Malformed request")
453

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

    
458
    try:
459
        key = req.keys()[0]
460
        if key != 'console':
461
            start_action(vm, key_to_action(key))
462
        val = req[key]
463
        assert isinstance(val, dict)
464
        return server_actions[key](request, vm, val)
465
    except KeyError:
466
        raise faults.BadRequest("Unknown action")
467
    except AssertionError:
468
        raise faults.BadRequest("Invalid argument")
469

    
470

    
471
def key_to_action(key):
472
    """Map HTTP request key to a VM Action"""
473
    if key == "shutdown":
474
        return "STOP"
475
    if key == "delete":
476
        return "DESTROY"
477
    if key == "console":
478
        return None
479
    else:
480
        return key.upper()
481

    
482

    
483
def start_action(vm, action):
484
    log.debug("Applying action %s to VM %s", action, vm)
485
    if not action:
486
        return
487

    
488
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
489
        raise faults.ServiceUnavailable("Action %s not supported" % action)
490

    
491
    # No actions to deleted VMs
492
    if vm.deleted:
493
        raise VirtualMachine.DeletedError
494

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

    
499
    vm.action = action
500
    vm.backendjobid = None
501
    vm.backendopcode = None
502
    vm.backendjobstatus = None
503
    vm.backendlogmsg = None
504

    
505
    vm.save()
506

    
507

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

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

    
521
    if request.serialization == 'xml':
522
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
523
    else:
524
        data = json.dumps({'addresses': {'values': addresses}})
525

    
526
    return HttpResponse(data, status=200)
527

    
528

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

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

    
545
    if request.serialization == 'xml':
546
        data = render_to_string('address.xml', {'address': address})
547
    else:
548
        data = json.dumps({'network': address})
549

    
550
    return HttpResponse(data, status=200)
551

    
552

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

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

    
567

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

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

    
588
    for key, val in metadata.items():
589
        meta, created = vm.metadata.get_or_create(meta_key=key)
590
        meta.meta_value = val
591
        meta.save()
592

    
593
    vm.save()
594
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
595
    return util.render_metadata(request, vm_meta, status=201)
596

    
597

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

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

    
614

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

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

    
639
    meta, created = VirtualMachineMetadata.objects.get_or_create(
640
        meta_key=key,
641
        vm=vm)
642

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

    
649

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

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

    
670

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

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

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

    
694
    if request.serialization == 'xml':
695
        data = render_to_string('server_stats.xml', stats)
696
    else:
697
        data = json.dumps({'stats': stats})
698

    
699
    return HttpResponse(data, status=200)