Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (26 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+)/metadata(?:.json|.xml)?$', 'metadata_demux'),
69
    (r'^/(\d+)/metadata/(.+?)(?:.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 nics_to_addresses(nics):
128
    addresses = {}
129
    for nic in nics:
130
        net_nics = []
131
        net_nics.append({"version": 4, "addr": nic.ipv4})
132
        if nic.ipv6:
133
            net_nics.append({"version": 6, "addr": nic.ipv6})
134
        addresses[nic.network.id] = net_nics
135
    return addresses
136

    
137

    
138
def vm_to_dict(vm, detail=False):
139
    d = dict(id=vm.id, name=vm.name)
140
    d['links'] = util.vm_to_links(vm.id)
141
    if detail:
142
        d['user_id'] = vm.userid
143
        d['tenant_id'] = vm.userid
144
        d['status'] = get_rsapi_state(vm)
145
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' \
146
            else vm.buildpercentage
147
        d['hostId'] = vm.hostid
148
        d['updated'] = utils.isoformat(vm.updated)
149
        d['created'] = utils.isoformat(vm.created)
150
        d['flavor'] = {"id": vm.flavor.id,
151
                       "links": util.flavor_to_links(vm.flavor.id)}
152
        d['image'] = {"id": vm.imageid,
153
                      "links": util.image_to_links(vm.imageid)}
154
        d['suspended'] = vm.suspended
155

    
156
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
157
        d['metadata'] = metadata
158

    
159
        vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
160
        attachments = map(nic_to_dict, vm_nics)
161
        d['attachments'] = attachments
162
        d['addresses'] = nics_to_addresses(vm_nics)
163

    
164
        # include the latest vm diagnostic, if set
165
        diagnostic = vm.get_last_diagnostic()
166
        if diagnostic:
167
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
168
        else:
169
            d['diagnostics'] = []
170

    
171
    return d
172

    
173

    
174
def diagnostics_to_dict(diagnostics):
175
    """
176
    Extract api data from diagnostics QuerySet.
177
    """
178
    entries = list()
179

    
180
    for diagnostic in diagnostics:
181
        # format source date if set
182
        formatted_source_date = None
183
        if diagnostic.source_date:
184
            formatted_source_date = utils.isoformat(diagnostic.source_date)
185

    
186
        entry = {
187
            'source': diagnostic.source,
188
            'created': utils.isoformat(diagnostic.created),
189
            'message': diagnostic.message,
190
            'details': diagnostic.details,
191
            'level': diagnostic.level,
192
        }
193

    
194
        if formatted_source_date:
195
            entry['source_date'] = formatted_source_date
196

    
197
        entries.append(entry)
198

    
199
    return entries
200

    
201

    
202
def render_server(request, server, status=200):
203
    if request.serialization == 'xml':
204
        data = render_to_string('server.xml', {
205
            'server': server,
206
            'is_root': True})
207
    else:
208
        data = json.dumps({'server': server})
209
    return HttpResponse(data, status=status)
210

    
211

    
212
def render_diagnostics(request, diagnostics_dict, status=200):
213
    """
214
    Render diagnostics dictionary to json response.
215
    """
216
    return HttpResponse(json.dumps(diagnostics_dict), status=status)
217

    
218

    
219
@api.api_method(http_method='GET', user_required=True, logger=log)
220
def get_server_diagnostics(request, server_id):
221
    """
222
    Virtual machine diagnostics api view.
223
    """
224
    log.debug('server_diagnostics %s', server_id)
225
    vm = util.get_vm(server_id, request.user_uniq)
226
    diagnostics = diagnostics_to_dict(vm.diagnostics.all())
227
    return render_diagnostics(request, diagnostics)
228

    
229

    
230
@api.api_method(http_method='GET', user_required=True, logger=log)
231
def list_servers(request, detail=False):
232
    # Normal Response Codes: 200, 203
233
    # Error Response Codes: computeFault (400, 500),
234
    #                       serviceUnavailable (503),
235
    #                       unauthorized (401),
236
    #                       badRequest (400),
237
    #                       overLimit (413)
238

    
239
    log.debug('list_servers detail=%s', detail)
240
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
241

    
242
    since = utils.isoparse(request.GET.get('changes-since'))
243

    
244
    if since:
245
        user_vms = user_vms.filter(updated__gte=since)
246
        if not user_vms:
247
            return HttpResponse(status=304)
248
    else:
249
        user_vms = user_vms.filter(deleted=False)
250

    
251
    servers = [vm_to_dict(server, detail)
252
               for server in user_vms.order_by('id')]
253

    
254
    if request.serialization == 'xml':
255
        data = render_to_string('list_servers.xml', {
256
            'servers': servers,
257
            'detail': detail})
258
    else:
259
        data = json.dumps({'servers': servers})
260

    
261
    return HttpResponse(data, status=200)
262

    
263

    
264
@api.api_method(http_method='POST', user_required=True, logger=log)
265
def create_server(request):
266
    # Normal Response Code: 202
267
    # Error Response Codes: computeFault (400, 500),
268
    #                       serviceUnavailable (503),
269
    #                       unauthorized (401),
270
    #                       badMediaType(415),
271
    #                       itemNotFound (404),
272
    #                       badRequest (400),
273
    #                       serverCapacityUnavailable (503),
274
    #                       overLimit (413)
275
    req = utils.get_request_dict(request)
276
    log.info('create_server %s', req)
277
    user_id = request.user_uniq
278

    
279
    try:
280
        server = req['server']
281
        name = server['name']
282
        metadata = server.get('metadata', {})
283
        assert isinstance(metadata, dict)
284
        image_id = server['imageRef']
285
        flavor_id = server['flavorRef']
286
        personality = server.get('personality', [])
287
        assert isinstance(personality, list)
288
    except (KeyError, AssertionError):
289
        raise faults.BadRequest("Malformed request")
290

    
291
    # Verify that personalities are well-formed
292
    util.verify_personality(personality)
293
    # Get image information
294
    image = util.get_image_dict(image_id, user_id)
295
    # Get flavor (ensure it is active)
296
    flavor = util.get_flavor(flavor_id, include_deleted=False)
297
    # Generate password
298
    password = util.random_password()
299

    
300
    vm = do_create_server(user_id, name, password, flavor, image,
301
                          metadata=metadata, personality=personality)
302

    
303
    server = vm_to_dict(vm, detail=True)
304
    server['status'] = 'BUILD'
305
    server['adminPass'] = password
306

    
307
    response = render_server(request, server, status=202)
308

    
309
    return response
310

    
311

    
312
@transaction.commit_manually
313
def do_create_server(userid, name, password, flavor, image, metadata={},
314
                     personality=[], network=None, backend=None):
315
    if backend is None:
316
        # Allocate backend to host the server. Commit after allocation to
317
        # release the locks hold by the backend allocator.
318
        try:
319
            backend_allocator = BackendAllocator()
320
            backend = backend_allocator.allocate(userid, flavor)
321
            if backend is None:
322
                log.error("No available backend for VM with flavor %s", flavor)
323
                raise faults.ServiceUnavailable("No available backends")
324
        except:
325
            transaction.rollback()
326
            raise
327
        else:
328
            transaction.commit()
329

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

    
341
    try:
342
        if network is None:
343
            # Allocate IP from public network
344
            (network, address) = util.get_public_ip(backend)
345
            nic = {'ip': address, 'network': network.backend_id}
346
        else:
347
            address = util.get_network_free_address(network)
348

    
349
        # We must save the VM instance now, so that it gets a valid
350
        # vm.backend_vm_id.
351
        vm = VirtualMachine.objects.create(
352
            name=name,
353
            backend=backend,
354
            userid=userid,
355
            imageid=image["id"],
356
            flavor=flavor,
357
            action="CREATE")
358

    
359
        # Create VM's public NIC. Do not wait notification form ganeti hooks to
360
        # create this NIC, because if the hooks never run (e.g. building error)
361
        # the VM's public IP address will never be released!
362
        NetworkInterface.objects.create(machine=vm, network=network, index=0,
363
                                        ipv4=address, state="BUILDING")
364

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

    
367
        # dispatch server created signal
368
        server_created.send(sender=vm, created_vm_params={
369
            'img_id': image['backend_id'],
370
            'img_passwd': password,
371
            'img_format': str(image['format']),
372
            'img_personality': json.dumps(personality),
373
            'img_properties': json.dumps(image['metadata']),
374
        })
375

    
376
        # Also we must create the VM metadata in the same transaction.
377
        for key, val in metadata.items():
378
            VirtualMachineMetadata.objects.create(
379
                meta_key=key,
380
                meta_value=val,
381
                vm=vm)
382
        # Issue commission to Quotaholder and accept it since at the end of
383
        # this transaction the VirtualMachine object will be created in the DB.
384
        # Note: the following call does a commit!
385
        quotas.issue_and_accept_commission(vm)
386
    except:
387
        transaction.rollback()
388
        raise
389
    else:
390
        transaction.commit()
391

    
392
    try:
393
        jobID = create_instance(vm, nic, flavor, image)
394
        # At this point the job is enqueued in the Ganeti backend
395
        vm.backendjobid = jobID
396
        vm.save()
397
        transaction.commit()
398
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
399
                 userid, vm, nic, backend, str(jobID))
400
    except GanetiApiError as e:
401
        log.exception("Can not communicate to backend %s: %s.",
402
                      backend, e)
403
        # Failed while enqueuing OP_INSTANCE_CREATE to backend. Restore
404
        # already reserved quotas by issuing a negative commission
405
        vm.operstate = "ERROR"
406
        vm.backendlogmsg = "Can not communicate to backend."
407
        vm.deleted = True
408
        vm.save()
409
        quotas.issue_and_accept_commission(vm, delete=True)
410
        raise
411
    except:
412
        transaction.rollback()
413
        raise
414

    
415
    return vm
416

    
417

    
418
@api.api_method(http_method='GET', user_required=True, logger=log)
419
def get_server_details(request, server_id):
420
    # Normal Response Codes: 200, 203
421
    # Error Response Codes: computeFault (400, 500),
422
    #                       serviceUnavailable (503),
423
    #                       unauthorized (401),
424
    #                       badRequest (400),
425
    #                       itemNotFound (404),
426
    #                       overLimit (413)
427

    
428
    log.debug('get_server_details %s', server_id)
429
    vm = util.get_vm(server_id, request.user_uniq)
430
    server = vm_to_dict(vm, detail=True)
431
    return render_server(request, server)
432

    
433

    
434
@api.api_method(http_method='PUT', user_required=True, logger=log)
435
def update_server_name(request, server_id):
436
    # Normal Response Code: 204
437
    # Error Response Codes: computeFault (400, 500),
438
    #                       serviceUnavailable (503),
439
    #                       unauthorized (401),
440
    #                       badRequest (400),
441
    #                       badMediaType(415),
442
    #                       itemNotFound (404),
443
    #                       buildInProgress (409),
444
    #                       overLimit (413)
445

    
446
    req = utils.get_request_dict(request)
447
    log.info('update_server_name %s %s', server_id, req)
448

    
449
    try:
450
        name = req['server']['name']
451
    except (TypeError, KeyError):
452
        raise faults.BadRequest("Malformed request")
453

    
454
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
455
                     non_suspended=True)
456
    vm.name = name
457
    vm.save()
458

    
459
    return HttpResponse(status=204)
460

    
461

    
462
@api.api_method(http_method='DELETE', user_required=True, logger=log)
463
@transaction.commit_on_success
464
def delete_server(request, server_id):
465
    # Normal Response Codes: 204
466
    # Error Response Codes: computeFault (400, 500),
467
    #                       serviceUnavailable (503),
468
    #                       unauthorized (401),
469
    #                       itemNotFound (404),
470
    #                       unauthorized (401),
471
    #                       buildInProgress (409),
472
    #                       overLimit (413)
473

    
474
    log.info('delete_server %s', server_id)
475
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
476
                     non_suspended=True)
477
    start_action(vm, 'DESTROY')
478
    delete_instance(vm)
479
    return HttpResponse(status=204)
480

    
481

    
482
# additional server actions
483
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
484

    
485

    
486
@api.api_method(http_method='POST', user_required=True, logger=log)
487
def server_action(request, server_id):
488
    req = utils.get_request_dict(request)
489
    log.debug('server_action %s %s', server_id, req)
490

    
491
    if len(req) != 1:
492
        raise faults.BadRequest("Malformed request")
493

    
494
    # Do not allow any action on deleted or suspended VMs
495
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
496
                     non_deleted=True, non_suspended=True)
497

    
498
    try:
499
        key = req.keys()[0]
500
        if key not in ARBITRARY_ACTIONS:
501
            start_action(vm, key_to_action(key))
502
        val = req[key]
503
        assert isinstance(val, dict)
504
        return server_actions[key](request, vm, val)
505
    except KeyError:
506
        raise faults.BadRequest("Unknown action")
507
    except AssertionError:
508
        raise faults.BadRequest("Invalid argument")
509

    
510

    
511
def key_to_action(key):
512
    """Map HTTP request key to a VM Action"""
513
    if key == "shutdown":
514
        return "STOP"
515
    if key == "delete":
516
        return "DESTROY"
517
    if key in ARBITRARY_ACTIONS:
518
        return None
519
    else:
520
        return key.upper()
521

    
522

    
523
def start_action(vm, action):
524
    log.debug("Applying action %s to VM %s", action, vm)
525
    if not action:
526
        return
527

    
528
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
529
        raise faults.ServiceUnavailable("Action %s not supported" % action)
530

    
531
    # No actions to deleted VMs
532
    if vm.deleted:
533
        raise faults.BadRequest("VirtualMachine has been deleted.")
534

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

    
539
    vm.action = action
540
    vm.backendjobid = None
541
    vm.backendopcode = None
542
    vm.backendjobstatus = None
543
    vm.backendlogmsg = None
544

    
545
    vm.save()
546

    
547

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

    
557
    log.debug('list_addresses %s', server_id)
558
    vm = util.get_vm(server_id, request.user_uniq)
559
    attachments = [nic_to_dict(nic) for nic in vm.nics.all()]
560
    addresses = nics_to_addresses(vm.nics.all())
561

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

    
567
    return HttpResponse(data, status=200)
568

    
569

    
570
@api.api_method(http_method='GET', user_required=True, logger=log)
571
def list_addresses_by_network(request, server_id, network_id):
572
    # Normal Response Codes: 200, 203
573
    # Error Response Codes: computeFault (400, 500),
574
    #                       serviceUnavailable (503),
575
    #                       unauthorized (401),
576
    #                       badRequest (400),
577
    #                       itemNotFound (404),
578
    #                       overLimit (413)
579

    
580
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
581
    machine = util.get_vm(server_id, request.user_uniq)
582
    network = util.get_network(network_id, request.user_uniq)
583
    nics = machine.nics.filter(network=network).all()
584
    addresses = nics_to_addresses(nics)
585

    
586
    if request.serialization == 'xml':
587
        data = render_to_string('address.xml', {'addresses': addresses})
588
    else:
589
        data = json.dumps({'network': addresses})
590

    
591
    return HttpResponse(data, status=200)
592

    
593

    
594
@api.api_method(http_method='GET', user_required=True, logger=log)
595
def list_metadata(request, server_id):
596
    # Normal Response Codes: 200, 203
597
    # Error Response Codes: computeFault (400, 500),
598
    #                       serviceUnavailable (503),
599
    #                       unauthorized (401),
600
    #                       badRequest (400),
601
    #                       overLimit (413)
602

    
603
    log.debug('list_server_metadata %s', server_id)
604
    vm = util.get_vm(server_id, request.user_uniq)
605
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
606
    return util.render_metadata(request, metadata, use_values=False,
607
                                status=200)
608

    
609

    
610
@api.api_method(http_method='POST', user_required=True, logger=log)
611
def update_metadata(request, server_id):
612
    # Normal Response Code: 201
613
    # Error Response Codes: computeFault (400, 500),
614
    #                       serviceUnavailable (503),
615
    #                       unauthorized (401),
616
    #                       badRequest (400),
617
    #                       buildInProgress (409),
618
    #                       badMediaType(415),
619
    #                       overLimit (413)
620

    
621
    req = utils.get_request_dict(request)
622
    log.info('update_server_metadata %s %s', server_id, req)
623
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
624
    try:
625
        metadata = req['metadata']
626
        assert isinstance(metadata, dict)
627
    except (KeyError, AssertionError):
628
        raise faults.BadRequest("Malformed request")
629

    
630
    for key, val in metadata.items():
631
        meta, created = vm.metadata.get_or_create(meta_key=key)
632
        meta.meta_value = val
633
        meta.save()
634

    
635
    vm.save()
636
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
637
    return util.render_metadata(request, vm_meta, status=201)
638

    
639

    
640
@api.api_method(http_method='GET', user_required=True, logger=log)
641
def get_metadata_item(request, server_id, key):
642
    # Normal Response Codes: 200, 203
643
    # Error Response Codes: computeFault (400, 500),
644
    #                       serviceUnavailable (503),
645
    #                       unauthorized (401),
646
    #                       itemNotFound (404),
647
    #                       badRequest (400),
648
    #                       overLimit (413)
649

    
650
    log.debug('get_server_metadata_item %s %s', server_id, key)
651
    vm = util.get_vm(server_id, request.user_uniq)
652
    meta = util.get_vm_meta(vm, key)
653
    d = {meta.meta_key: meta.meta_value}
654
    return util.render_meta(request, d, status=200)
655

    
656

    
657
@api.api_method(http_method='PUT', user_required=True, logger=log)
658
@transaction.commit_on_success
659
def create_metadata_item(request, server_id, key):
660
    # Normal Response Code: 201
661
    # Error Response Codes: computeFault (400, 500),
662
    #                       serviceUnavailable (503),
663
    #                       unauthorized (401),
664
    #                       itemNotFound (404),
665
    #                       badRequest (400),
666
    #                       buildInProgress (409),
667
    #                       badMediaType(415),
668
    #                       overLimit (413)
669

    
670
    req = utils.get_request_dict(request)
671
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
672
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
673
    try:
674
        metadict = req['meta']
675
        assert isinstance(metadict, dict)
676
        assert len(metadict) == 1
677
        assert key in metadict
678
    except (KeyError, AssertionError):
679
        raise faults.BadRequest("Malformed request")
680

    
681
    meta, created = VirtualMachineMetadata.objects.get_or_create(
682
        meta_key=key,
683
        vm=vm)
684

    
685
    meta.meta_value = metadict[key]
686
    meta.save()
687
    vm.save()
688
    d = {meta.meta_key: meta.meta_value}
689
    return util.render_meta(request, d, status=201)
690

    
691

    
692
@api.api_method(http_method='DELETE', user_required=True, logger=log)
693
@transaction.commit_on_success
694
def delete_metadata_item(request, server_id, key):
695
    # Normal Response Code: 204
696
    # Error Response Codes: computeFault (400, 500),
697
    #                       serviceUnavailable (503),
698
    #                       unauthorized (401),
699
    #                       itemNotFound (404),
700
    #                       badRequest (400),
701
    #                       buildInProgress (409),
702
    #                       badMediaType(415),
703
    #                       overLimit (413),
704

    
705
    log.info('delete_server_metadata_item %s %s', server_id, key)
706
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
707
    meta = util.get_vm_meta(vm, key)
708
    meta.delete()
709
    vm.save()
710
    return HttpResponse(status=204)
711

    
712

    
713
@api.api_method(http_method='GET', user_required=True, logger=log)
714
def server_stats(request, server_id):
715
    # Normal Response Codes: 200
716
    # Error Response Codes: computeFault (400, 500),
717
    #                       serviceUnavailable (503),
718
    #                       unauthorized (401),
719
    #                       badRequest (400),
720
    #                       itemNotFound (404),
721
    #                       overLimit (413)
722

    
723
    log.debug('server_stats %s', server_id)
724
    vm = util.get_vm(server_id, request.user_uniq)
725
    #secret = util.encrypt(vm.backend_vm_id)
726
    secret = vm.backend_vm_id      # XXX disable backend id encryption
727

    
728
    stats = {
729
        'serverRef': vm.id,
730
        'refresh': settings.STATS_REFRESH_PERIOD,
731
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
732
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
733
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
734
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
735

    
736
    if request.serialization == 'xml':
737
        data = render_to_string('server_stats.xml', stats)
738
    else:
739
        data = json.dumps({'stats': stats})
740

    
741
    return HttpResponse(data, status=200)