Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.1 kB)

1
# Copyright 2011-2013 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 django import dispatch
35
from django.conf import settings
36
from django.conf.urls.defaults import patterns
37
from django.db import transaction
38
from django.http import HttpResponse
39
from django.template.loader import render_to_string
40
from django.utils import simplejson as json
41

    
42
from snf_django.lib import api
43
from snf_django.lib.api import faults, utils
44
from synnefo.api import util
45
from synnefo.api.actions import server_actions
46
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata,
47
                               NetworkInterface)
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(__name__)
59

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

    
74

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

    
83

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

    
94

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

    
103

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

    
114

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

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

    
126

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

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

    
144
        vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
145
        attachments = map(nic_to_dict, vm_nics)
146
        if attachments:
147
            d['attachments'] = {'values': attachments}
148

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

    
154
    return d
155

    
156

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

    
163
    for diagnostic in diagnostics:
164
        # format source date if set
165
        formatted_source_date = None
166
        if diagnostic.source_date:
167
            formatted_source_date = utils.isoformat(diagnostic.source_date)
168

    
169
        entry = {
170
            'source': diagnostic.source,
171
            'created': utils.isoformat(diagnostic.created),
172
            'message': diagnostic.message,
173
            'details': diagnostic.details,
174
            'level': diagnostic.level,
175
        }
176

    
177
        if formatted_source_date:
178
            entry['source_date'] = formatted_source_date
179

    
180
        entries.append(entry)
181

    
182
    return entries
183

    
184

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

    
194

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

    
201

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

    
212

    
213
@api.api_method(http_method='GET', user_required=True, logger=log)
214
def list_servers(request, detail=False):
215
    # Normal Response Codes: 200, 203
216
    # Error Response Codes: computeFault (400, 500),
217
    #                       serviceUnavailable (503),
218
    #                       unauthorized (401),
219
    #                       badRequest (400),
220
    #                       overLimit (413)
221

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

    
225
    since = utils.isoparse(request.GET.get('changes-since'))
226

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

    
234
    servers = [vm_to_dict(server, detail)
235
               for server in user_vms.order_by('id')]
236

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

    
244
    return HttpResponse(data, status=200)
245

    
246

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

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

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

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

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

    
310
    # Fix flavor for archipelago
311
    password = util.random_password()
312
    disk_template, provider = util.get_flavor_provider(flavor)
313
    if provider:
314
        flavor.disk_template = disk_template
315
        flavor.disk_provider = provider
316
        flavor.disk_origin = None
317
        if provider == 'vlmc':
318
            flavor.disk_origin = image['checksum']
319
            image['backend_id'] = 'null'
320
    else:
321
        flavor.disk_provider = None
322

    
323
    try:
324
        # Issue commission
325
        serial = quotas.issue_vm_commission(user_id, flavor)
326
        serials.append(serial)
327
        # Make the commission accepted, since in the end of this
328
        # transaction the VM will have been created in the DB.
329
        serial.accepted = True
330
        serial.save()
331

    
332
        # We must save the VM instance now, so that it gets a valid
333
        # vm.backend_vm_id.
334
        vm = VirtualMachine.objects.create(
335
            name=name,
336
            backend=backend,
337
            userid=user_id,
338
            imageid=image_id,
339
            flavor=flavor,
340
            action="CREATE",
341
            serial=serial)
342

    
343
        # Create VM's public NIC. Do not wait notification form ganeti hooks to
344
        # create this NIC, because if the hooks never run (e.g. building error)
345
        # the VM's public IP address will never be released!
346
        NetworkInterface.objects.create(machine=vm, network=network, index=0,
347
                                        ipv4=address, state="Buidling")
348

    
349
        log.info("Created entry in DB for VM '%s'", vm)
350

    
351
        # dispatch server created signal
352
        server_created.send(sender=vm, created_vm_params={
353
            'img_id': image['backend_id'],
354
            'img_passwd': password,
355
            'img_format': str(image['format']),
356
            'img_personality': json.dumps(personality),
357
            'img_properties': json.dumps(image['metadata']),
358
        })
359

    
360
        # Also we must create the VM metadata in the same transaction.
361
        for key, val in metadata.items():
362
            VirtualMachineMetadata.objects.create(
363
                meta_key=key,
364
                meta_value=val,
365
                vm=vm)
366
    except:
367
        transaction.rollback()
368
        raise
369
    else:
370
        transaction.commit()
371

    
372
    try:
373
        jobID = create_instance(vm, nic, flavor, image)
374
        # At this point the job is enqueued in the Ganeti backend
375
        vm.backendjobid = jobID
376
        vm.save()
377
        transaction.commit()
378
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
379
                 user_id, vm, nic, backend, str(jobID))
380
    except GanetiApiError as e:
381
        log.exception("Can not communicate to backend %s: %s. Deleting VM %s",
382
                      backend, e, vm)
383
        vm.delete()
384
        transaction.commit()
385
        raise
386
    except:
387
        transaction.rollback()
388
        raise
389

    
390
    server = vm_to_dict(vm, detail=True)
391
    server['status'] = 'BUILD'
392
    server['adminPass'] = password
393

    
394
    respsone = render_server(request, server, status=202)
395

    
396
    return respsone
397

    
398

    
399
@api.api_method(http_method='GET', user_required=True, logger=log)
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
@api.api_method(http_method='PUT', user_required=True, logger=log)
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 = utils.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
@api.api_method(http_method='DELETE', user_required=True, logger=log)
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
# additional server actions
464
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
465

    
466

    
467
@api.api_method(http_method='POST', user_required=True, logger=log)
468
def server_action(request, server_id):
469
    req = utils.get_request_dict(request)
470
    log.debug('server_action %s %s', server_id, req)
471

    
472
    if len(req) != 1:
473
        raise faults.BadRequest("Malformed request")
474

    
475
    # Do not allow any action on deleted or suspended VMs
476
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
477
                     non_deleted=True, non_suspended=True)
478

    
479
    try:
480
        key = req.keys()[0]
481
        if key not in ARBITRARY_ACTIONS:
482
            start_action(vm, key_to_action(key))
483
        val = req[key]
484
        assert isinstance(val, dict)
485
        return server_actions[key](request, vm, val)
486
    except KeyError:
487
        raise faults.BadRequest("Unknown action")
488
    except AssertionError:
489
        raise faults.BadRequest("Invalid argument")
490

    
491

    
492
def key_to_action(key):
493
    """Map HTTP request key to a VM Action"""
494
    if key == "shutdown":
495
        return "STOP"
496
    if key == "delete":
497
        return "DESTROY"
498
    if key in ARBITRARY_ACTIONS:
499
        return None
500
    else:
501
        return key.upper()
502

    
503

    
504
def start_action(vm, action):
505
    log.debug("Applying action %s to VM %s", action, vm)
506
    if not action:
507
        return
508

    
509
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
510
        raise faults.ServiceUnavailable("Action %s not supported" % action)
511

    
512
    # No actions to deleted VMs
513
    if vm.deleted:
514
        raise faults.BadRequest("VirtualMachine has been deleted.")
515

    
516
    # No actions to machines being built. They may be destroyed, however.
517
    if vm.operstate == 'BUILD' and action != 'DESTROY':
518
        raise faults.BuildInProgress("Server is being build.")
519

    
520
    vm.action = action
521
    vm.backendjobid = None
522
    vm.backendopcode = None
523
    vm.backendjobstatus = None
524
    vm.backendlogmsg = None
525

    
526
    vm.save()
527

    
528

    
529
@api.api_method(http_method='GET', user_required=True, logger=log)
530
def list_addresses(request, server_id):
531
    # Normal Response Codes: 200, 203
532
    # Error Response Codes: computeFault (400, 500),
533
    #                       serviceUnavailable (503),
534
    #                       unauthorized (401),
535
    #                       badRequest (400),
536
    #                       overLimit (413)
537

    
538
    log.debug('list_addresses %s', server_id)
539
    vm = util.get_vm(server_id, request.user_uniq)
540
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
541

    
542
    if request.serialization == 'xml':
543
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
544
    else:
545
        data = json.dumps({'addresses': {'values': addresses}})
546

    
547
    return HttpResponse(data, status=200)
548

    
549

    
550
@api.api_method(http_method='GET', user_required=True, logger=log)
551
def list_addresses_by_network(request, server_id, network_id):
552
    # Normal Response Codes: 200, 203
553
    # Error Response Codes: computeFault (400, 500),
554
    #                       serviceUnavailable (503),
555
    #                       unauthorized (401),
556
    #                       badRequest (400),
557
    #                       itemNotFound (404),
558
    #                       overLimit (413)
559

    
560
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
561
    machine = util.get_vm(server_id, request.user_uniq)
562
    network = util.get_network(network_id, request.user_uniq)
563
    nic = util.get_nic(machine, network)
564
    address = nic_to_dict(nic)
565

    
566
    if request.serialization == 'xml':
567
        data = render_to_string('address.xml', {'address': address})
568
    else:
569
        data = json.dumps({'network': address})
570

    
571
    return HttpResponse(data, status=200)
572

    
573

    
574
@api.api_method(http_method='GET', user_required=True, logger=log)
575
def list_metadata(request, server_id):
576
    # Normal Response Codes: 200, 203
577
    # Error Response Codes: computeFault (400, 500),
578
    #                       serviceUnavailable (503),
579
    #                       unauthorized (401),
580
    #                       badRequest (400),
581
    #                       overLimit (413)
582

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

    
588

    
589
@api.api_method(http_method='POST', user_required=True, logger=log)
590
def update_metadata(request, server_id):
591
    # Normal Response Code: 201
592
    # Error Response Codes: computeFault (400, 500),
593
    #                       serviceUnavailable (503),
594
    #                       unauthorized (401),
595
    #                       badRequest (400),
596
    #                       buildInProgress (409),
597
    #                       badMediaType(415),
598
    #                       overLimit (413)
599

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

    
609
    for key, val in metadata.items():
610
        meta, created = vm.metadata.get_or_create(meta_key=key)
611
        meta.meta_value = val
612
        meta.save()
613

    
614
    vm.save()
615
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
616
    return util.render_metadata(request, vm_meta, status=201)
617

    
618

    
619
@api.api_method(http_method='GET', user_required=True, logger=log)
620
def get_metadata_item(request, server_id, key):
621
    # Normal Response Codes: 200, 203
622
    # Error Response Codes: computeFault (400, 500),
623
    #                       serviceUnavailable (503),
624
    #                       unauthorized (401),
625
    #                       itemNotFound (404),
626
    #                       badRequest (400),
627
    #                       overLimit (413)
628

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

    
635

    
636
@api.api_method(http_method='PUT', user_required=True, logger=log)
637
@transaction.commit_on_success
638
def create_metadata_item(request, server_id, key):
639
    # Normal Response Code: 201
640
    # Error Response Codes: computeFault (400, 500),
641
    #                       serviceUnavailable (503),
642
    #                       unauthorized (401),
643
    #                       itemNotFound (404),
644
    #                       badRequest (400),
645
    #                       buildInProgress (409),
646
    #                       badMediaType(415),
647
    #                       overLimit (413)
648

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

    
660
    meta, created = VirtualMachineMetadata.objects.get_or_create(
661
        meta_key=key,
662
        vm=vm)
663

    
664
    meta.meta_value = metadict[key]
665
    meta.save()
666
    vm.save()
667
    d = {meta.meta_key: meta.meta_value}
668
    return util.render_meta(request, d, status=201)
669

    
670

    
671
@api.api_method(http_method='DELETE', user_required=True, logger=log)
672
@transaction.commit_on_success
673
def delete_metadata_item(request, server_id, key):
674
    # Normal Response Code: 204
675
    # Error Response Codes: computeFault (400, 500),
676
    #                       serviceUnavailable (503),
677
    #                       unauthorized (401),
678
    #                       itemNotFound (404),
679
    #                       badRequest (400),
680
    #                       buildInProgress (409),
681
    #                       badMediaType(415),
682
    #                       overLimit (413),
683

    
684
    log.info('delete_server_metadata_item %s %s', server_id, key)
685
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
686
    meta = util.get_vm_meta(vm, key)
687
    meta.delete()
688
    vm.save()
689
    return HttpResponse(status=204)
690

    
691

    
692
@api.api_method(http_method='GET', user_required=True, logger=log)
693
def server_stats(request, server_id):
694
    # Normal Response Codes: 200
695
    # Error Response Codes: computeFault (400, 500),
696
    #                       serviceUnavailable (503),
697
    #                       unauthorized (401),
698
    #                       badRequest (400),
699
    #                       itemNotFound (404),
700
    #                       overLimit (413)
701

    
702
    log.debug('server_stats %s', server_id)
703
    vm = util.get_vm(server_id, request.user_uniq)
704
    #secret = util.encrypt(vm.backend_vm_id)
705
    secret = vm.backend_vm_id      # XXX disable backend id encryption
706

    
707
    stats = {
708
        'serverRef': vm.id,
709
        'refresh': settings.STATS_REFRESH_PERIOD,
710
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
711
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
712
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
713
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
714

    
715
    if request.serialization == 'xml':
716
        data = render_to_string('server_stats.xml', stats)
717
    else:
718
        data = json.dumps({'stats': stats})
719

    
720
    return HttpResponse(data, status=200)