Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (26.2 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.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.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.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.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,
132
                         "addr": nic.ipv4,
133
                         "OS-EXT-IPS:type": "fixed"})
134
        if nic.ipv6:
135
            net_nics.append({"version": 6,
136
                             "addr": nic.ipv6,
137
                             "OS-EXT-IPS:type": "fixed"})
138
        addresses[nic.network.id] = net_nics
139
    return addresses
140

    
141

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

    
160
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
161
        d['metadata'] = metadata
162

    
163
        vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
164
        attachments = map(nic_to_dict, vm_nics)
165
        d['attachments'] = attachments
166
        d['addresses'] = nics_to_addresses(vm_nics)
167

    
168
        # include the latest vm diagnostic, if set
169
        diagnostic = vm.get_last_diagnostic()
170
        if diagnostic:
171
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
172
        else:
173
            d['diagnostics'] = []
174
        # Fixed
175
        d["security_groups"] = [{"name": "default"}]
176
        d["key_name"] = None
177
        d["config_drive"] = ""
178
        d["accessIPv4"] = ""
179
        d["accessIPv6"] = ""
180

    
181
    return d
182

    
183

    
184
def diagnostics_to_dict(diagnostics):
185
    """
186
    Extract api data from diagnostics QuerySet.
187
    """
188
    entries = list()
189

    
190
    for diagnostic in diagnostics:
191
        # format source date if set
192
        formatted_source_date = None
193
        if diagnostic.source_date:
194
            formatted_source_date = utils.isoformat(diagnostic.source_date)
195

    
196
        entry = {
197
            'source': diagnostic.source,
198
            'created': utils.isoformat(diagnostic.created),
199
            'message': diagnostic.message,
200
            'details': diagnostic.details,
201
            'level': diagnostic.level,
202
        }
203

    
204
        if formatted_source_date:
205
            entry['source_date'] = formatted_source_date
206

    
207
        entries.append(entry)
208

    
209
    return entries
210

    
211

    
212
def render_server(request, server, status=200):
213
    if request.serialization == 'xml':
214
        data = render_to_string('server.xml', {
215
            'server': server,
216
            'is_root': True})
217
    else:
218
        data = json.dumps({'server': server})
219
    return HttpResponse(data, status=status)
220

    
221

    
222
def render_diagnostics(request, diagnostics_dict, status=200):
223
    """
224
    Render diagnostics dictionary to json response.
225
    """
226
    return HttpResponse(json.dumps(diagnostics_dict), status=status)
227

    
228

    
229
@api.api_method(http_method='GET', user_required=True, logger=log)
230
def get_server_diagnostics(request, server_id):
231
    """
232
    Virtual machine diagnostics api view.
233
    """
234
    log.debug('server_diagnostics %s', server_id)
235
    vm = util.get_vm(server_id, request.user_uniq)
236
    diagnostics = diagnostics_to_dict(vm.diagnostics.all())
237
    return render_diagnostics(request, diagnostics)
238

    
239

    
240
@api.api_method(http_method='GET', user_required=True, logger=log)
241
def list_servers(request, detail=False):
242
    # Normal Response Codes: 200, 203
243
    # Error Response Codes: computeFault (400, 500),
244
    #                       serviceUnavailable (503),
245
    #                       unauthorized (401),
246
    #                       badRequest (400),
247
    #                       overLimit (413)
248

    
249
    log.debug('list_servers detail=%s', detail)
250
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
251

    
252
    since = utils.isoparse(request.GET.get('changes-since'))
253

    
254
    if since:
255
        user_vms = user_vms.filter(updated__gte=since)
256
        if not user_vms:
257
            return HttpResponse(status=304)
258
    else:
259
        user_vms = user_vms.filter(deleted=False)
260

    
261
    servers = [vm_to_dict(server, detail)
262
               for server in user_vms.order_by('id')]
263

    
264
    if request.serialization == 'xml':
265
        data = render_to_string('list_servers.xml', {
266
            'servers': servers,
267
            'detail': detail})
268
    else:
269
        data = json.dumps({'servers': servers})
270

    
271
    return HttpResponse(data, status=200)
272

    
273

    
274
@api.api_method(http_method='POST', user_required=True, logger=log)
275
def create_server(request):
276
    # Normal Response Code: 202
277
    # Error Response Codes: computeFault (400, 500),
278
    #                       serviceUnavailable (503),
279
    #                       unauthorized (401),
280
    #                       badMediaType(415),
281
    #                       itemNotFound (404),
282
    #                       badRequest (400),
283
    #                       serverCapacityUnavailable (503),
284
    #                       overLimit (413)
285
    req = utils.get_request_dict(request)
286
    log.info('create_server %s', req)
287
    user_id = request.user_uniq
288

    
289
    try:
290
        server = req['server']
291
        name = server['name']
292
        metadata = server.get('metadata', {})
293
        assert isinstance(metadata, dict)
294
        image_id = server['imageRef']
295
        flavor_id = server['flavorRef']
296
        personality = server.get('personality', [])
297
        assert isinstance(personality, list)
298
    except (KeyError, AssertionError):
299
        raise faults.BadRequest("Malformed request")
300

    
301
    # Verify that personalities are well-formed
302
    util.verify_personality(personality)
303
    # Get image information
304
    image = util.get_image_dict(image_id, user_id)
305
    # Get flavor (ensure it is active)
306
    flavor = util.get_flavor(flavor_id, include_deleted=False)
307
    # Generate password
308
    password = util.random_password()
309

    
310
    vm = do_create_server(user_id, name, password, flavor, image,
311
                          metadata=metadata, personality=personality)
312

    
313
    server = vm_to_dict(vm, detail=True)
314
    server['status'] = 'BUILD'
315
    server['adminPass'] = password
316

    
317
    response = render_server(request, server, status=202)
318

    
319
    return response
320

    
321

    
322
@transaction.commit_manually
323
def do_create_server(userid, name, password, flavor, image, metadata={},
324
                     personality=[], network=None, backend=None):
325
    # Fix flavor for archipelago
326
    disk_template, provider = util.get_flavor_provider(flavor)
327
    if provider:
328
        flavor.disk_template = disk_template
329
        flavor.disk_provider = provider
330
        flavor.disk_origin = image['checksum']
331
        image['backend_id'] = 'null'
332
    else:
333
        flavor.disk_provider = None
334
        flavor.disk_origin = None
335

    
336
    try:
337
        if backend is None:
338
            # Allocate backend to host the server.
339
            backend_allocator = BackendAllocator()
340
            backend = backend_allocator.allocate(userid, flavor)
341
            if backend is None:
342
                log.error("No available backend for VM with flavor %s", flavor)
343
                raise faults.ServiceUnavailable("No available backends")
344

    
345
        if network is None:
346
            # Allocate IP from public network
347
            (network, address) = util.get_public_ip(backend)
348
            nic = {'ip': address, 'network': network.backend_id}
349
        else:
350
            address = util.get_network_free_address(network)
351

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

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

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

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

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

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

    
418
    return vm
419

    
420

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

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

    
436

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

    
449
    req = utils.get_request_dict(request)
450
    log.info('update_server_name %s %s', server_id, req)
451

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

    
457
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
458
                     non_suspended=True)
459
    vm.name = name
460
    vm.save()
461

    
462
    return HttpResponse(status=204)
463

    
464

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

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

    
484

    
485
# additional server actions
486
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
487

    
488

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

    
494
    if len(req) != 1:
495
        raise faults.BadRequest("Malformed request")
496

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

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

    
513

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

    
525

    
526
def start_action(vm, action):
527
    log.debug("Applying action %s to VM %s", action, vm)
528
    if not action:
529
        return
530

    
531
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
532
        raise faults.ServiceUnavailable("Action %s not supported" % action)
533

    
534
    # No actions to deleted VMs
535
    if vm.deleted:
536
        raise faults.BadRequest("VirtualMachine has been deleted.")
537

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

    
542
    vm.action = action
543
    vm.backendjobid = None
544
    vm.backendopcode = None
545
    vm.backendjobstatus = None
546
    vm.backendlogmsg = None
547

    
548
    vm.save()
549

    
550

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

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

    
565
    if request.serialization == 'xml':
566
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
567
    else:
568
        data = json.dumps({'addresses': addresses, 'attachments': attachments})
569

    
570
    return HttpResponse(data, status=200)
571

    
572

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

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

    
589
    if request.serialization == 'xml':
590
        data = render_to_string('address.xml', {'addresses': addresses})
591
    else:
592
        data = json.dumps({'network': addresses})
593

    
594
    return HttpResponse(data, status=200)
595

    
596

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

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

    
612

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

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

    
633
    for key, val in metadata.items():
634
        meta, created = vm.metadata.get_or_create(meta_key=key)
635
        meta.meta_value = val
636
        meta.save()
637

    
638
    vm.save()
639
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
640
    return util.render_metadata(request, vm_meta, status=201)
641

    
642

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

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

    
659

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

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

    
684
    meta, created = VirtualMachineMetadata.objects.get_or_create(
685
        meta_key=key,
686
        vm=vm)
687

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

    
694

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

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

    
715

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

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

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

    
739
    if request.serialization == 'xml':
740
        data = render_to_string('server_stats.xml', stats)
741
    else:
742
        data = json.dumps({'stats': stats})
743

    
744
    return HttpResponse(data, status=200)