Statistics
| Branch: | Tag: | Revision:

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

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_personalitity(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
        # TODO: Just copied code from backend.py to fix the images backend_id
331
        # for archipelagos. Find a better way and remove double checks
332
        img_id = image['backend_id']
333
        provider = None
334
        disk_template = flavor.disk_template
335
        if flavor.disk_template.startswith("ext"):
336
            disk_template, provider = flavor.disk_template.split("_", 1)
337
            if provider == 'vlmc':
338
                img_id = 'null'
339

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

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

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

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

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

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

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

    
378
    return respsone
379

    
380

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

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

    
396

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

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

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

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

    
422
    return HttpResponse(status=204)
423

    
424

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

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

    
444

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

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

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

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

    
469

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

    
481

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

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

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

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

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

    
504
    vm.save()
505

    
506

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

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

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

    
525
    return HttpResponse(data, status=200)
526

    
527

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

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

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

    
549
    return HttpResponse(data, status=200)
550

    
551

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

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

    
566

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

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

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

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

    
596

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

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

    
613

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

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

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

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

    
648

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

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

    
669

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

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

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

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

    
698
    return HttpResponse(data, status=200)