Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (23 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.all()]
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) for server in user_vms]
230

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

    
238
    return HttpResponse(data, status=200)
239

    
240

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

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

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

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

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

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

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

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

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

    
315
        backend_allocator = BackendAllocator()
316
        backend = backend_allocator.allocate(flavor)
317

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

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

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

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

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

    
363
        vm.backendjobid = jobID
364
        vm.save()
365

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

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

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

    
383
    return respsone
384

    
385

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

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

    
401

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

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

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

    
422
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
423
    vm.name = name
424
    vm.save()
425

    
426
    return HttpResponse(status=204)
427

    
428

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

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

    
446

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

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

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

    
459
    try:
460
        key = req.keys()[0]
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
@util.api_method('GET')
471
def list_addresses(request, server_id):
472
    # Normal Response Codes: 200, 203
473
    # Error Response Codes: computeFault (400, 500),
474
    #                       serviceUnavailable (503),
475
    #                       unauthorized (401),
476
    #                       badRequest (400),
477
    #                       overLimit (413)
478

    
479
    log.debug('list_addresses %s', server_id)
480
    vm = util.get_vm(server_id, request.user_uniq)
481
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
482

    
483
    if request.serialization == 'xml':
484
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
485
    else:
486
        data = json.dumps({'addresses': {'values': addresses}})
487

    
488
    return HttpResponse(data, status=200)
489

    
490

    
491
@util.api_method('GET')
492
def list_addresses_by_network(request, server_id, network_id):
493
    # Normal Response Codes: 200, 203
494
    # Error Response Codes: computeFault (400, 500),
495
    #                       serviceUnavailable (503),
496
    #                       unauthorized (401),
497
    #                       badRequest (400),
498
    #                       itemNotFound (404),
499
    #                       overLimit (413)
500

    
501
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
502
    machine = util.get_vm(server_id, request.user_uniq)
503
    network = util.get_network(network_id, request.user_uniq)
504
    nic = util.get_nic(machine, network)
505
    address = nic_to_dict(nic)
506

    
507
    if request.serialization == 'xml':
508
        data = render_to_string('address.xml', {'address': address})
509
    else:
510
        data = json.dumps({'network': address})
511

    
512
    return HttpResponse(data, status=200)
513

    
514

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

    
524
    log.debug('list_server_metadata %s', server_id)
525
    vm = util.get_vm(server_id, request.user_uniq)
526
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
527
    return util.render_metadata(request, metadata, use_values=True, status=200)
528

    
529

    
530
@util.api_method('POST')
531
def update_metadata(request, server_id):
532
    # Normal Response Code: 201
533
    # Error Response Codes: computeFault (400, 500),
534
    #                       serviceUnavailable (503),
535
    #                       unauthorized (401),
536
    #                       badRequest (400),
537
    #                       buildInProgress (409),
538
    #                       badMediaType(415),
539
    #                       overLimit (413)
540

    
541
    req = util.get_request_dict(request)
542
    log.info('update_server_metadata %s %s', server_id, req)
543
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
544
    try:
545
        metadata = req['metadata']
546
        assert isinstance(metadata, dict)
547
    except (KeyError, AssertionError):
548
        raise faults.BadRequest("Malformed request")
549

    
550
    for key, val in metadata.items():
551
        meta, created = vm.metadata.get_or_create(meta_key=key)
552
        meta.meta_value = val
553
        meta.save()
554

    
555
    vm.save()
556
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
557
    return util.render_metadata(request, vm_meta, status=201)
558

    
559

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

    
570
    log.debug('get_server_metadata_item %s %s', server_id, key)
571
    vm = util.get_vm(server_id, request.user_uniq)
572
    meta = util.get_vm_meta(vm, key)
573
    d = {meta.meta_key: meta.meta_value}
574
    return util.render_meta(request, d, status=200)
575

    
576

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

    
590
    req = util.get_request_dict(request)
591
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
592
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
593
    try:
594
        metadict = req['meta']
595
        assert isinstance(metadict, dict)
596
        assert len(metadict) == 1
597
        assert key in metadict
598
    except (KeyError, AssertionError):
599
        raise faults.BadRequest("Malformed request")
600

    
601
    meta, created = VirtualMachineMetadata.objects.get_or_create(
602
        meta_key=key,
603
        vm=vm)
604

    
605
    meta.meta_value = metadict[key]
606
    meta.save()
607
    vm.save()
608
    d = {meta.meta_key: meta.meta_value}
609
    return util.render_meta(request, d, status=201)
610

    
611

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

    
625
    log.info('delete_server_metadata_item %s %s', server_id, key)
626
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
627
    meta = util.get_vm_meta(vm, key)
628
    meta.delete()
629
    vm.save()
630
    return HttpResponse(status=204)
631

    
632

    
633
@util.api_method('GET')
634
def server_stats(request, server_id):
635
    # Normal Response Codes: 200
636
    # Error Response Codes: computeFault (400, 500),
637
    #                       serviceUnavailable (503),
638
    #                       unauthorized (401),
639
    #                       badRequest (400),
640
    #                       itemNotFound (404),
641
    #                       overLimit (413)
642

    
643
    log.debug('server_stats %s', server_id)
644
    vm = util.get_vm(server_id, request.user_uniq)
645
    #secret = util.encrypt(vm.backend_vm_id)
646
    secret = vm.backend_vm_id      # XXX disable backend id encryption
647

    
648
    stats = {
649
        'serverRef': vm.id,
650
        'refresh': settings.STATS_REFRESH_PERIOD,
651
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
652
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
653
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
654
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
655

    
656
    if request.serialization == 'xml':
657
        data = render_to_string('server_stats.xml', stats)
658
    else:
659
        data = json.dumps({'stats': stats})
660

    
661
    return HttpResponse(data, status=200)