Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.4 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 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
        d['attachments'] = {'values': attachments}
147

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

    
153
    return d
154

    
155

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

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

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

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

    
179
        entries.append(entry)
180

    
181
    return entries
182

    
183

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

    
193

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

    
200

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

    
211

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

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

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

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

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

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

    
243
    return HttpResponse(data, status=200)
244

    
245

    
246
@api.api_method(http_method='POST', user_required=True, logger=log)
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
    req = utils.get_request_dict(request)
258
    log.info('create_server %s', req)
259
    user_id = request.user_uniq
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
    # Verify that personalities are well-formed
274
    util.verify_personality(personality)
275
    # Get image information
276
    image = util.get_image_dict(image_id, user_id)
277
    # Get flavor (ensure it is active)
278
    flavor = util.get_flavor(flavor_id, include_deleted=False)
279
    # Generate password
280
    password = util.random_password()
281

    
282
    vm = do_create_server(user_id, name, password, flavor, image,
283
                          metadata=metadata, personality=personality)
284

    
285
    server = vm_to_dict(vm, detail=True)
286
    server['status'] = 'BUILD'
287
    server['adminPass'] = password
288

    
289
    response = render_server(request, server, status=202)
290

    
291
    return response
292

    
293

    
294
@transaction.commit_manually
295
def do_create_server(userid, name, password, flavor, image, metadata={},
296
                  personality=[], network=None, backend=None):
297
    if backend is None:
298
        # Allocate backend to host the server. Commit after allocation to
299
        # release the locks hold by the backend allocator.
300
        try:
301
            backend_allocator = BackendAllocator()
302
            backend = backend_allocator.allocate(userid, flavor)
303
            if backend is None:
304
                log.error("No available backend for VM with flavor %s", flavor)
305
                raise faults.ServiceUnavailable("No available backends")
306
        except:
307
            transaction.rollback()
308
            raise
309
        else:
310
            transaction.commit()
311

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

    
324
    try:
325
        if network is None:
326
            # Allocate IP from public network
327
            (network, address) = util.get_public_ip(backend)
328
            nic = {'ip': address, 'network': network.backend_id}
329
        else:
330
            address = util.get_network_free_address(network)
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=userid,
338
            imageid=image["id"],
339
            flavor=flavor,
340
            action="CREATE")
341

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

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

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

    
359
        # Also we must create the VM metadata in the same transaction.
360
        for key, val in metadata.items():
361
            VirtualMachineMetadata.objects.create(
362
                meta_key=key,
363
                meta_value=val,
364
                vm=vm)
365
        # Issue commission to Quotaholder and accept it since at the end of
366
        # this transaction the VirtualMachine object will be created in the DB.
367
        # Note: the following call does a commit!
368
        quotas.issue_and_accept_commission(vm)
369
    except:
370
        transaction.rollback()
371
        raise
372
    else:
373
        transaction.commit()
374

    
375
    try:
376
        jobID = create_instance(vm, nic, flavor, image)
377
        # At this point the job is enqueued in the Ganeti backend
378
        vm.backendjobid = jobID
379
        vm.save()
380
        transaction.commit()
381
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
382
                 userid, vm, nic, backend, str(jobID))
383
    except GanetiApiError as e:
384
        log.exception("Can not communicate to backend %s: %s.",
385
                      backend, e)
386
        # Failed while enqueuing OP_INSTANCE_CREATE to backend. Restore
387
        # already reserved quotas by issuing a negative commission
388
        vm.operstate = "ERROR"
389
        vm.backendlogmsg = "Can not communicate to backend."
390
        vm.deleted = True
391
        vm.save()
392
        quotas.issue_and_accept_commission(vm, delete=True)
393
        raise
394
    except:
395
        transaction.rollback()
396
        raise
397

    
398
    return vm
399

    
400

    
401
@api.api_method(http_method='GET', user_required=True, logger=log)
402
def get_server_details(request, server_id):
403
    # Normal Response Codes: 200, 203
404
    # Error Response Codes: computeFault (400, 500),
405
    #                       serviceUnavailable (503),
406
    #                       unauthorized (401),
407
    #                       badRequest (400),
408
    #                       itemNotFound (404),
409
    #                       overLimit (413)
410

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

    
416

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

    
429
    req = utils.get_request_dict(request)
430
    log.info('update_server_name %s %s', server_id, req)
431

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

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

    
442
    return HttpResponse(status=204)
443

    
444

    
445
@api.api_method(http_method='DELETE', user_required=True, logger=log)
446
@transaction.commit_on_success
447
def delete_server(request, server_id):
448
    # Normal Response Codes: 204
449
    # Error Response Codes: computeFault (400, 500),
450
    #                       serviceUnavailable (503),
451
    #                       unauthorized (401),
452
    #                       itemNotFound (404),
453
    #                       unauthorized (401),
454
    #                       buildInProgress (409),
455
    #                       overLimit (413)
456

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

    
464

    
465
# additional server actions
466
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
467

    
468

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

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

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

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

    
493

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

    
505

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

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

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

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

    
522
    vm.action = action
523
    vm.backendjobid = None
524
    vm.backendopcode = None
525
    vm.backendjobstatus = None
526
    vm.backendlogmsg = None
527

    
528
    vm.save()
529

    
530

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

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

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

    
549
    return HttpResponse(data, status=200)
550

    
551

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

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

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

    
573
    return HttpResponse(data, status=200)
574

    
575

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

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

    
590

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

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

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

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

    
620

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

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

    
637

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

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

    
662
    meta, created = VirtualMachineMetadata.objects.get_or_create(
663
        meta_key=key,
664
        vm=vm)
665

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

    
672

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

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

    
693

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

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

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

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

    
722
    return HttpResponse(data, status=200)