Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.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 random import choice
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
@transaction.commit_manually
251
def create_server(request):
252
    # Normal Response Code: 202
253
    # Error Response Codes: computeFault (400, 500),
254
    #                       serviceUnavailable (503),
255
    #                       unauthorized (401),
256
    #                       badMediaType(415),
257
    #                       itemNotFound (404),
258
    #                       badRequest (400),
259
    #                       serverCapacityUnavailable (503),
260
    #                       overLimit (413)
261
    try:
262
        req = util.get_request_dict(request)
263
        log.info('create_server %s', req)
264

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

    
277
        if len(personality) > settings.MAX_PERSONALITY:
278
            raise faults.OverLimit("Maximum number of personalities exceeded")
279

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

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

    
304
        # Ensure that request if for active flavor
305
        flavor = util.get_flavor(flavor_id, include_deleted=False)
306
        password = util.random_password()
307

    
308
        count = VirtualMachine.objects.filter(userid=request.user_uniq,
309
                                              deleted=False).count()
310

    
311
        # get user limit
312
        vms_limit_for_user = \
313
            settings.VMS_USER_QUOTA.get(request.user_uniq,
314
                    settings.MAX_VMS_PER_USER)
315

    
316
        if count >= vms_limit_for_user:
317
            raise faults.OverLimit("Server count limit exceeded for your account.")
318

    
319
        backend_allocator = BackendAllocator()
320
        backend = backend_allocator.allocate(request.user_uniq, flavor)
321

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

    
331
    # dispatch server created signal
332
    server_created.send(sender=vm, created_vm_params={
333
        'personality': personality,
334
        'password': password
335
    })
336

    
337
    try:
338
        if settings.PUBLIC_USE_POOL:
339
            (network, address) = util.allocate_public_address(backend)
340
            if address is None:
341
                log.error("Public networks of backend %s are full", backend)
342
                msg = "Failed to allocate public IP for new VM"
343
                raise faults.ServiceUnavailable(msg)
344
            nic = {'ip': address, 'network': network.backend_id}
345
        else:
346
            network = choice(list(util.backend_public_networks(backend)))
347
            nic = {'ip': 'pool', 'network': network.backend_id}
348
    except:
349
        transaction.rollback()
350
        raise
351
    else:
352
        transaction.commit()
353

    
354
    try:
355
        # We must save the VM instance now, so that it gets a valid
356
        # vm.backend_vm_id.
357
        vm = VirtualMachine.objects.create(
358
            name=name,
359
            backend=backend,
360
            userid=request.user_uniq,
361
            imageid=image_id,
362
            flavor=flavor,
363
            action="CREATE")
364

    
365
        vmapi_url = vm.get_vmapi_params_urls()
366

    
367
        try:
368
            jobID = create_instance(vm, nic, flavor, image, password, personality)
369
        except GanetiApiError:
370
            vm.delete()
371
            raise
372

    
373
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
374
                request.user_uniq, vm, nic, backend, str(jobID))
375

    
376
        vm.backendjobid = jobID
377
        vm.save()
378

    
379
        for key, val in metadata.items():
380
            VirtualMachineMetadata.objects.create(
381
                meta_key=key,
382
                meta_value=val,
383
                vm=vm)
384

    
385
        server = vm_to_dict(vm, detail=True)
386
        server['status'] = 'BUILD'
387
        server['adminPass'] = password
388

    
389
        respsone = render_server(request, server, status=202)
390
    except:
391
        transaction.rollback()
392
        raise
393
    else:
394
        transaction.commit()
395

    
396
    return respsone
397

    
398

    
399
@util.api_method('GET')
400
def get_server_details(request, server_id):
401
    # Normal Response Codes: 200, 203
402
    # Error Response Codes: computeFault (400, 500),
403
    #                       serviceUnavailable (503),
404
    #                       unauthorized (401),
405
    #                       badRequest (400),
406
    #                       itemNotFound (404),
407
    #                       overLimit (413)
408

    
409
    log.debug('get_server_details %s', server_id)
410
    vm = util.get_vm(server_id, request.user_uniq)
411
    server = vm_to_dict(vm, detail=True)
412
    return render_server(request, server)
413

    
414

    
415
@util.api_method('PUT')
416
def update_server_name(request, server_id):
417
    # Normal Response Code: 204
418
    # Error Response Codes: computeFault (400, 500),
419
    #                       serviceUnavailable (503),
420
    #                       unauthorized (401),
421
    #                       badRequest (400),
422
    #                       badMediaType(415),
423
    #                       itemNotFound (404),
424
    #                       buildInProgress (409),
425
    #                       overLimit (413)
426

    
427
    req = util.get_request_dict(request)
428
    log.info('update_server_name %s %s', server_id, req)
429

    
430
    try:
431
        name = req['server']['name']
432
    except (TypeError, KeyError):
433
        raise faults.BadRequest("Malformed request")
434

    
435
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
436
                     non_suspended=True)
437
    vm.name = name
438
    vm.save()
439

    
440
    return HttpResponse(status=204)
441

    
442

    
443
@util.api_method('DELETE')
444
@transaction.commit_on_success
445
def delete_server(request, server_id):
446
    # Normal Response Codes: 204
447
    # Error Response Codes: computeFault (400, 500),
448
    #                       serviceUnavailable (503),
449
    #                       unauthorized (401),
450
    #                       itemNotFound (404),
451
    #                       unauthorized (401),
452
    #                       buildInProgress (409),
453
    #                       overLimit (413)
454

    
455
    log.info('delete_server %s', server_id)
456
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
457
                     non_suspended=True)
458
    start_action(vm, 'DESTROY')
459
    delete_instance(vm)
460
    return HttpResponse(status=204)
461

    
462

    
463
@util.api_method('POST')
464
def server_action(request, server_id):
465
    req = util.get_request_dict(request)
466
    log.debug('server_action %s %s', server_id, req)
467

    
468
    if len(req) != 1:
469
        raise faults.BadRequest("Malformed request")
470

    
471
    # Do not allow any action on deleted or suspended VMs
472
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
473
                     non_deleted=True, non_suspended=True)
474

    
475
    try:
476
        key = req.keys()[0]
477
        if key != 'console':
478
            start_action(vm, key_to_action(key))
479
        val = req[key]
480
        assert isinstance(val, dict)
481
        return server_actions[key](request, vm, val)
482
    except KeyError:
483
        raise faults.BadRequest("Unknown action")
484
    except AssertionError:
485
        raise faults.BadRequest("Invalid argument")
486

    
487

    
488
def key_to_action(key):
489
    """Map HTTP request key to a VM Action"""
490
    if key == "shutdown":
491
        return "STOP"
492
    if key == "delete":
493
        return "DESTROY"
494
    if key == "console":
495
        return None
496
    else:
497
        return key.upper()
498

    
499

    
500
def start_action(vm, action):
501
    log.debug("Applying action %s to VM %s", action, vm)
502
    if not action:
503
        return
504

    
505
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
506
        raise faults.ServiceUnavailable("Action %s not supported" % action)
507

    
508
    # No actions to deleted VMs
509
    if vm.deleted:
510
        raise VirtualMachine.DeletedError
511

    
512
    # No actions to machines being built. They may be destroyed, however.
513
    if vm.operstate == 'BUILD' and action != 'DESTROY':
514
        raise VirtualMachine.BuildingError
515

    
516
    vm.action = action
517
    vm.backendjobid = None
518
    vm.backendopcode = None
519
    vm.backendjobstatus = None
520
    vm.backendlogmsg = None
521

    
522
    vm.save()
523

    
524

    
525
@util.api_method('GET')
526
def list_addresses(request, server_id):
527
    # Normal Response Codes: 200, 203
528
    # Error Response Codes: computeFault (400, 500),
529
    #                       serviceUnavailable (503),
530
    #                       unauthorized (401),
531
    #                       badRequest (400),
532
    #                       overLimit (413)
533

    
534
    log.debug('list_addresses %s', server_id)
535
    vm = util.get_vm(server_id, request.user_uniq)
536
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
537

    
538
    if request.serialization == 'xml':
539
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
540
    else:
541
        data = json.dumps({'addresses': {'values': addresses}})
542

    
543
    return HttpResponse(data, status=200)
544

    
545

    
546
@util.api_method('GET')
547
def list_addresses_by_network(request, server_id, network_id):
548
    # Normal Response Codes: 200, 203
549
    # Error Response Codes: computeFault (400, 500),
550
    #                       serviceUnavailable (503),
551
    #                       unauthorized (401),
552
    #                       badRequest (400),
553
    #                       itemNotFound (404),
554
    #                       overLimit (413)
555

    
556
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
557
    machine = util.get_vm(server_id, request.user_uniq)
558
    network = util.get_network(network_id, request.user_uniq)
559
    nic = util.get_nic(machine, network)
560
    address = nic_to_dict(nic)
561

    
562
    if request.serialization == 'xml':
563
        data = render_to_string('address.xml', {'address': address})
564
    else:
565
        data = json.dumps({'network': address})
566

    
567
    return HttpResponse(data, status=200)
568

    
569

    
570
@util.api_method('GET')
571
def list_metadata(request, server_id):
572
    # Normal Response Codes: 200, 203
573
    # Error Response Codes: computeFault (400, 500),
574
    #                       serviceUnavailable (503),
575
    #                       unauthorized (401),
576
    #                       badRequest (400),
577
    #                       overLimit (413)
578

    
579
    log.debug('list_server_metadata %s', server_id)
580
    vm = util.get_vm(server_id, request.user_uniq)
581
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
582
    return util.render_metadata(request, metadata, use_values=True, status=200)
583

    
584

    
585
@util.api_method('POST')
586
def update_metadata(request, server_id):
587
    # Normal Response Code: 201
588
    # Error Response Codes: computeFault (400, 500),
589
    #                       serviceUnavailable (503),
590
    #                       unauthorized (401),
591
    #                       badRequest (400),
592
    #                       buildInProgress (409),
593
    #                       badMediaType(415),
594
    #                       overLimit (413)
595

    
596
    req = util.get_request_dict(request)
597
    log.info('update_server_metadata %s %s', server_id, req)
598
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
599
    try:
600
        metadata = req['metadata']
601
        assert isinstance(metadata, dict)
602
    except (KeyError, AssertionError):
603
        raise faults.BadRequest("Malformed request")
604

    
605
    for key, val in metadata.items():
606
        meta, created = vm.metadata.get_or_create(meta_key=key)
607
        meta.meta_value = val
608
        meta.save()
609

    
610
    vm.save()
611
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
612
    return util.render_metadata(request, vm_meta, status=201)
613

    
614

    
615
@util.api_method('GET')
616
def get_metadata_item(request, server_id, key):
617
    # Normal Response Codes: 200, 203
618
    # Error Response Codes: computeFault (400, 500),
619
    #                       serviceUnavailable (503),
620
    #                       unauthorized (401),
621
    #                       itemNotFound (404),
622
    #                       badRequest (400),
623
    #                       overLimit (413)
624

    
625
    log.debug('get_server_metadata_item %s %s', server_id, key)
626
    vm = util.get_vm(server_id, request.user_uniq)
627
    meta = util.get_vm_meta(vm, key)
628
    d = {meta.meta_key: meta.meta_value}
629
    return util.render_meta(request, d, status=200)
630

    
631

    
632
@util.api_method('PUT')
633
@transaction.commit_on_success
634
def create_metadata_item(request, server_id, key):
635
    # Normal Response Code: 201
636
    # Error Response Codes: computeFault (400, 500),
637
    #                       serviceUnavailable (503),
638
    #                       unauthorized (401),
639
    #                       itemNotFound (404),
640
    #                       badRequest (400),
641
    #                       buildInProgress (409),
642
    #                       badMediaType(415),
643
    #                       overLimit (413)
644

    
645
    req = util.get_request_dict(request)
646
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
647
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
648
    try:
649
        metadict = req['meta']
650
        assert isinstance(metadict, dict)
651
        assert len(metadict) == 1
652
        assert key in metadict
653
    except (KeyError, AssertionError):
654
        raise faults.BadRequest("Malformed request")
655

    
656
    meta, created = VirtualMachineMetadata.objects.get_or_create(
657
        meta_key=key,
658
        vm=vm)
659

    
660
    meta.meta_value = metadict[key]
661
    meta.save()
662
    vm.save()
663
    d = {meta.meta_key: meta.meta_value}
664
    return util.render_meta(request, d, status=201)
665

    
666

    
667
@util.api_method('DELETE')
668
@transaction.commit_on_success
669
def delete_metadata_item(request, server_id, key):
670
    # Normal Response Code: 204
671
    # Error Response Codes: computeFault (400, 500),
672
    #                       serviceUnavailable (503),
673
    #                       unauthorized (401),
674
    #                       itemNotFound (404),
675
    #                       badRequest (400),
676
    #                       buildInProgress (409),
677
    #                       badMediaType(415),
678
    #                       overLimit (413),
679

    
680
    log.info('delete_server_metadata_item %s %s', server_id, key)
681
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
682
    meta = util.get_vm_meta(vm, key)
683
    meta.delete()
684
    vm.save()
685
    return HttpResponse(status=204)
686

    
687

    
688
@util.api_method('GET')
689
def server_stats(request, server_id):
690
    # Normal Response Codes: 200
691
    # Error Response Codes: computeFault (400, 500),
692
    #                       serviceUnavailable (503),
693
    #                       unauthorized (401),
694
    #                       badRequest (400),
695
    #                       itemNotFound (404),
696
    #                       overLimit (413)
697

    
698
    log.debug('server_stats %s', server_id)
699
    vm = util.get_vm(server_id, request.user_uniq)
700
    #secret = util.encrypt(vm.backend_vm_id)
701
    secret = vm.backend_vm_id      # XXX disable backend id encryption
702

    
703
    stats = {
704
        'serverRef': vm.id,
705
        'refresh': settings.STATS_REFRESH_PERIOD,
706
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
707
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
708
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
709
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
710

    
711
    if request.serialization == 'xml':
712
        data = render_to_string('server_stats.xml', stats)
713
    else:
714
        data = json.dumps({'stats': stats})
715

    
716
    return HttpResponse(data, status=200)