Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.5 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
    d['links'] = util.vm_to_links(vm.id)
130
    if detail:
131
        d['status'] = get_rsapi_state(vm)
132
        d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' \
133
            else vm.buildpercentage
134
        d['hostId'] = vm.hostid
135
        d['updated'] = utils.isoformat(vm.updated)
136
        d['created'] = utils.isoformat(vm.created)
137
        d['flavor'] = {"id": vm.flavor.id,
138
                       "links": util.flavor_to_links(vm.flavor.id)}
139
        d['image'] = {"id": vm.imageid,
140
                      "links": util.image_to_links(vm.imageid)}
141
        d['suspended'] = vm.suspended
142

    
143
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
144
        d['metadata'] = metadata
145

    
146
        vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
147
        attachments = map(nic_to_dict, vm_nics)
148
        d['attachments'] = attachments
149

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

    
157
    return d
158

    
159

    
160
def diagnostics_to_dict(diagnostics):
161
    """
162
    Extract api data from diagnostics QuerySet.
163
    """
164
    entries = list()
165

    
166
    for diagnostic in diagnostics:
167
        # format source date if set
168
        formatted_source_date = None
169
        if diagnostic.source_date:
170
            formatted_source_date = utils.isoformat(diagnostic.source_date)
171

    
172
        entry = {
173
            'source': diagnostic.source,
174
            'created': utils.isoformat(diagnostic.created),
175
            'message': diagnostic.message,
176
            'details': diagnostic.details,
177
            'level': diagnostic.level,
178
        }
179

    
180
        if formatted_source_date:
181
            entry['source_date'] = formatted_source_date
182

    
183
        entries.append(entry)
184

    
185
    return entries
186

    
187

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

    
197

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

    
204

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

    
215

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

    
225
    log.debug('list_servers detail=%s', detail)
226
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
227

    
228
    since = utils.isoparse(request.GET.get('changes-since'))
229

    
230
    if since:
231
        user_vms = user_vms.filter(updated__gte=since)
232
        if not user_vms:
233
            return HttpResponse(status=304)
234
    else:
235
        user_vms = user_vms.filter(deleted=False)
236

    
237
    servers = [vm_to_dict(server, detail)
238
               for server in user_vms.order_by('id')]
239

    
240
    if request.serialization == 'xml':
241
        data = render_to_string('list_servers.xml', {
242
            'servers': servers,
243
            'detail': detail})
244
    else:
245
        data = json.dumps({'servers': servers})
246

    
247
    return HttpResponse(data, status=200)
248

    
249

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

    
286
    vm = do_create_server(user_id, name, password, flavor, image,
287
                          metadata=metadata, personality=personality)
288

    
289
    server = vm_to_dict(vm, detail=True)
290
    server['status'] = 'BUILD'
291
    server['adminPass'] = password
292

    
293
    response = render_server(request, server, status=202)
294

    
295
    return response
296

    
297

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

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

    
327
    try:
328
        if network is None:
329
            # Allocate IP from public network
330
            (network, address) = util.get_public_ip(backend)
331
            nic = {'ip': address, 'network': network.backend_id}
332
        else:
333
            address = util.get_network_free_address(network)
334

    
335
        # We must save the VM instance now, so that it gets a valid
336
        # vm.backend_vm_id.
337
        vm = VirtualMachine.objects.create(
338
            name=name,
339
            backend=backend,
340
            userid=userid,
341
            imageid=image["id"],
342
            flavor=flavor,
343
            action="CREATE")
344

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

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

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

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

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

    
401
    return vm
402

    
403

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

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

    
419

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

    
432
    req = utils.get_request_dict(request)
433
    log.info('update_server_name %s %s', server_id, req)
434

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

    
440
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
441
                     non_suspended=True)
442
    vm.name = name
443
    vm.save()
444

    
445
    return HttpResponse(status=204)
446

    
447

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

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

    
467

    
468
# additional server actions
469
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
470

    
471

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

    
477
    if len(req) != 1:
478
        raise faults.BadRequest("Malformed request")
479

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

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

    
496

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

    
508

    
509
def start_action(vm, action):
510
    log.debug("Applying action %s to VM %s", action, vm)
511
    if not action:
512
        return
513

    
514
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
515
        raise faults.ServiceUnavailable("Action %s not supported" % action)
516

    
517
    # No actions to deleted VMs
518
    if vm.deleted:
519
        raise faults.BadRequest("VirtualMachine has been deleted.")
520

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

    
525
    vm.action = action
526
    vm.backendjobid = None
527
    vm.backendopcode = None
528
    vm.backendjobstatus = None
529
    vm.backendlogmsg = None
530

    
531
    vm.save()
532

    
533

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

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

    
547
    if request.serialization == 'xml':
548
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
549
    else:
550
        data = json.dumps({'addresses': addresses})
551

    
552
    return HttpResponse(data, status=200)
553

    
554

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

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

    
571
    if request.serialization == 'xml':
572
        data = render_to_string('address.xml', {'address': address})
573
    else:
574
        data = json.dumps({'network': address})
575

    
576
    return HttpResponse(data, status=200)
577

    
578

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

    
588
    log.debug('list_server_metadata %s', server_id)
589
    vm = util.get_vm(server_id, request.user_uniq)
590
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
591
    return util.render_metadata(request, metadata, use_values=False,
592
                                status=200)
593

    
594

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

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

    
615
    for key, val in metadata.items():
616
        meta, created = vm.metadata.get_or_create(meta_key=key)
617
        meta.meta_value = val
618
        meta.save()
619

    
620
    vm.save()
621
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
622
    return util.render_metadata(request, vm_meta, status=201)
623

    
624

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

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

    
641

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

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

    
666
    meta, created = VirtualMachineMetadata.objects.get_or_create(
667
        meta_key=key,
668
        vm=vm)
669

    
670
    meta.meta_value = metadict[key]
671
    meta.save()
672
    vm.save()
673
    d = {meta.meta_key: meta.meta_value}
674
    return util.render_meta(request, d, status=201)
675

    
676

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

    
690
    log.info('delete_server_metadata_item %s %s', server_id, key)
691
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
692
    meta = util.get_vm_meta(vm, key)
693
    meta.delete()
694
    vm.save()
695
    return HttpResponse(status=204)
696

    
697

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

    
708
    log.debug('server_stats %s', server_id)
709
    vm = util.get_vm(server_id, request.user_uniq)
710
    #secret = util.encrypt(vm.backend_vm_id)
711
    secret = vm.backend_vm_id      # XXX disable backend id encryption
712

    
713
    stats = {
714
        'serverRef': vm.id,
715
        'refresh': settings.STATS_REFRESH_PERIOD,
716
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
717
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
718
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
719
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
720

    
721
    if request.serialization == 'xml':
722
        data = render_to_string('server_stats.xml', stats)
723
    else:
724
        data = json.dumps({'stats': stats})
725

    
726
    return HttpResponse(data, status=200)