Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (29.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.conf import settings
35
from django.conf.urls.defaults import patterns
36
from django.db import transaction
37
from django.http import HttpResponse
38
from django.template.loader import render_to_string
39
from django.utils import simplejson as json
40

    
41
from snf_django.lib import api
42
from snf_django.lib.api import faults, utils
43

    
44
from synnefo.api import util
45
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata)
46
from synnefo.logic import servers, utils as logic_utils
47

    
48
from logging import getLogger
49
log = getLogger(__name__)
50

    
51
urlpatterns = patterns(
52
    'synnefo.api.servers',
53
    (r'^(?:/|.json|.xml)?$', 'demux'),
54
    (r'^/detail(?:.json|.xml)?$', 'list_servers', {'detail': True}),
55
    (r'^/(\d+)(?:.json|.xml)?$', 'server_demux'),
56
    (r'^/(\d+)/action(?:.json|.xml)?$', 'demux_server_action'),
57
    (r'^/(\d+)/ips(?:.json|.xml)?$', 'list_addresses'),
58
    (r'^/(\d+)/ips/(.+?)(?:.json|.xml)?$', 'list_addresses_by_network'),
59
    (r'^/(\d+)/metadata(?:.json|.xml)?$', 'metadata_demux'),
60
    (r'^/(\d+)/metadata/(.+?)(?:.json|.xml)?$', 'metadata_item_demux'),
61
    (r'^/(\d+)/stats(?:.json|.xml)?$', 'server_stats'),
62
    (r'^/(\d+)/diagnostics(?:.json)?$', 'get_server_diagnostics'),
63
)
64

    
65

    
66
def demux(request):
67
    if request.method == 'GET':
68
        return list_servers(request)
69
    elif request.method == 'POST':
70
        return create_server(request)
71
    else:
72
        return api.api_method_not_allowed(request)
73

    
74

    
75
def server_demux(request, server_id):
76
    if request.method == 'GET':
77
        return get_server_details(request, server_id)
78
    elif request.method == 'PUT':
79
        return update_server_name(request, server_id)
80
    elif request.method == 'DELETE':
81
        return delete_server(request, server_id)
82
    else:
83
        return api.api_method_not_allowed(request)
84

    
85

    
86
def metadata_demux(request, server_id):
87
    if request.method == 'GET':
88
        return list_metadata(request, server_id)
89
    elif request.method == 'POST':
90
        return update_metadata(request, server_id)
91
    else:
92
        return api.api_method_not_allowed(request)
93

    
94

    
95
def metadata_item_demux(request, server_id, key):
96
    if request.method == 'GET':
97
        return get_metadata_item(request, server_id, key)
98
    elif request.method == 'PUT':
99
        return create_metadata_item(request, server_id, key)
100
    elif request.method == 'DELETE':
101
        return delete_metadata_item(request, server_id, key)
102
    else:
103
        return api.api_method_not_allowed(request)
104

    
105

    
106
def nic_to_dict(nic):
107
    d = {'id': util.construct_nic_id(nic),
108
         'network_id': str(nic.network.id),
109
         'mac_address': nic.mac,
110
         'ipv4': nic.ipv4 if nic.ipv4 else None,
111
         'ipv6': nic.ipv6 if nic.ipv6 else None}
112

    
113
    if nic.firewall_profile:
114
        d['firewallProfile'] = nic.firewall_profile
115
    return d
116

    
117

    
118
def nics_to_addresses(nics):
119
    addresses = {}
120
    for nic in nics:
121
        net_nics = []
122
        net_nics.append({"version": 4,
123
                         "addr": nic.ipv4,
124
                         "OS-EXT-IPS:type": "fixed"})
125
        if nic.ipv6:
126
            net_nics.append({"version": 6,
127
                             "addr": nic.ipv6,
128
                             "OS-EXT-IPS:type": "fixed"})
129
        addresses[nic.network.id] = net_nics
130
    return addresses
131

    
132

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

    
151
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
152
        d['metadata'] = metadata
153

    
154
        vm_nics = vm.nics.filter(state="ACTIVE").order_by("index")
155
        attachments = map(nic_to_dict, vm_nics)
156
        d['attachments'] = attachments
157
        d['addresses'] = nics_to_addresses(vm_nics)
158

    
159
        # include the latest vm diagnostic, if set
160
        diagnostic = vm.get_last_diagnostic()
161
        if diagnostic:
162
            d['diagnostics'] = diagnostics_to_dict([diagnostic])
163
        else:
164
            d['diagnostics'] = []
165
        # Fixed
166
        d["security_groups"] = [{"name": "default"}]
167
        d["key_name"] = None
168
        d["config_drive"] = ""
169
        d["accessIPv4"] = ""
170
        d["accessIPv6"] = ""
171

    
172
    return d
173

    
174

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

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

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

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

    
198
        entries.append(entry)
199

    
200
    return entries
201

    
202

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

    
212

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

    
219

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

    
230

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

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

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

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

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

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

    
262
    return HttpResponse(data, status=200)
263

    
264

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

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

    
296
    # Verify that personalities are well-formed
297
    util.verify_personality(personality)
298
    # Get image information
299
    image = util.get_image_dict(image_id, user_id)
300
    # Get flavor (ensure it is active)
301
    flavor = util.get_flavor(flavor_id, include_deleted=False)
302
    # Generate password
303
    password = util.random_password()
304

    
305
    vm = servers.create(user_id, name, password, flavor, image,
306
                        metadata=metadata, personality=personality,
307
                        private_networks=private_networks,
308
                        floating_ips=floating_ips)
309

    
310
    server = vm_to_dict(vm, detail=True)
311
    server['status'] = 'BUILD'
312
    server['adminPass'] = password
313

    
314
    response = render_server(request, server, status=202)
315

    
316
    return response
317

    
318

    
319
@api.api_method(http_method='GET', user_required=True, logger=log)
320
def get_server_details(request, server_id):
321
    # Normal Response Codes: 200, 203
322
    # Error Response Codes: computeFault (400, 500),
323
    #                       serviceUnavailable (503),
324
    #                       unauthorized (401),
325
    #                       badRequest (400),
326
    #                       itemNotFound (404),
327
    #                       overLimit (413)
328

    
329
    log.debug('get_server_details %s', server_id)
330
    vm = util.get_vm(server_id, request.user_uniq)
331
    server = vm_to_dict(vm, detail=True)
332
    return render_server(request, server)
333

    
334

    
335
@api.api_method(http_method='PUT', user_required=True, logger=log)
336
def update_server_name(request, server_id):
337
    # Normal Response Code: 204
338
    # Error Response Codes: computeFault (400, 500),
339
    #                       serviceUnavailable (503),
340
    #                       unauthorized (401),
341
    #                       badRequest (400),
342
    #                       badMediaType(415),
343
    #                       itemNotFound (404),
344
    #                       buildInProgress (409),
345
    #                       overLimit (413)
346

    
347
    req = utils.get_request_dict(request)
348
    log.info('update_server_name %s %s', server_id, req)
349

    
350
    try:
351
        name = req['server']['name']
352
    except (TypeError, KeyError):
353
        raise faults.BadRequest("Malformed request")
354

    
355
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
356
                     non_suspended=True)
357
    vm.name = name
358
    vm.save()
359

    
360
    return HttpResponse(status=204)
361

    
362

    
363
@api.api_method(http_method='DELETE', user_required=True, logger=log)
364
def delete_server(request, server_id):
365
    # Normal Response Codes: 204
366
    # Error Response Codes: computeFault (400, 500),
367
    #                       serviceUnavailable (503),
368
    #                       unauthorized (401),
369
    #                       itemNotFound (404),
370
    #                       unauthorized (401),
371
    #                       buildInProgress (409),
372
    #                       overLimit (413)
373

    
374
    log.info('delete_server %s', server_id)
375
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
376
                     non_suspended=True)
377
    vm = servers.destroy(vm)
378
    return HttpResponse(status=204)
379

    
380

    
381
# additional server actions
382
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
383

    
384

    
385
def key_to_action(key):
386
    """Map HTTP request key to a VM Action"""
387
    if key == "shutdown":
388
        return "STOP"
389
    if key == "delete":
390
        return "DESTROY"
391
    if key in ARBITRARY_ACTIONS:
392
        return None
393
    else:
394
        return key.upper()
395

    
396

    
397
@api.api_method(http_method='POST', user_required=True, logger=log)
398
@transaction.commit_on_success
399
def demux_server_action(request, server_id):
400
    req = utils.get_request_dict(request)
401
    log.debug('server_action %s %s', server_id, req)
402

    
403
    if len(req) != 1:
404
        raise faults.BadRequest("Malformed request")
405

    
406
    # Do not allow any action on deleted or suspended VMs
407
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
408
                     non_deleted=True, non_suspended=True)
409

    
410
    try:
411
        action = req.keys()[0]
412
    except KeyError:
413
        raise faults.BadRequest("Unknown action")
414

    
415
    if key_to_action(action) not in [x[0] for x in VirtualMachine.ACTIONS]:
416
        if action not in ARBITRARY_ACTIONS:
417
            raise faults.BadRequest("Action %s not supported" % action)
418
    action_args = req[action]
419

    
420
    if not isinstance(action_args, dict):
421
        raise faults.BadRequest("Invalid argument")
422

    
423
    return server_actions[action](request, vm, action_args)
424

    
425

    
426
@api.api_method(http_method='GET', user_required=True, logger=log)
427
def list_addresses(request, server_id):
428
    # Normal Response Codes: 200, 203
429
    # Error Response Codes: computeFault (400, 500),
430
    #                       serviceUnavailable (503),
431
    #                       unauthorized (401),
432
    #                       badRequest (400),
433
    #                       overLimit (413)
434

    
435
    log.debug('list_addresses %s', server_id)
436
    vm = util.get_vm(server_id, request.user_uniq)
437
    attachments = [nic_to_dict(nic) for nic in vm.nics.all()]
438
    addresses = nics_to_addresses(vm.nics.all())
439

    
440
    if request.serialization == 'xml':
441
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
442
    else:
443
        data = json.dumps({'addresses': addresses, 'attachments': attachments})
444

    
445
    return HttpResponse(data, status=200)
446

    
447

    
448
@api.api_method(http_method='GET', user_required=True, logger=log)
449
def list_addresses_by_network(request, server_id, network_id):
450
    # Normal Response Codes: 200, 203
451
    # Error Response Codes: computeFault (400, 500),
452
    #                       serviceUnavailable (503),
453
    #                       unauthorized (401),
454
    #                       badRequest (400),
455
    #                       itemNotFound (404),
456
    #                       overLimit (413)
457

    
458
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
459
    machine = util.get_vm(server_id, request.user_uniq)
460
    network = util.get_network(network_id, request.user_uniq)
461
    nics = machine.nics.filter(network=network).all()
462
    addresses = nics_to_addresses(nics)
463

    
464
    if request.serialization == 'xml':
465
        data = render_to_string('address.xml', {'addresses': addresses})
466
    else:
467
        data = json.dumps({'network': addresses})
468

    
469
    return HttpResponse(data, status=200)
470

    
471

    
472
@api.api_method(http_method='GET', user_required=True, logger=log)
473
def list_metadata(request, server_id):
474
    # Normal Response Codes: 200, 203
475
    # Error Response Codes: computeFault (400, 500),
476
    #                       serviceUnavailable (503),
477
    #                       unauthorized (401),
478
    #                       badRequest (400),
479
    #                       overLimit (413)
480

    
481
    log.debug('list_server_metadata %s', server_id)
482
    vm = util.get_vm(server_id, request.user_uniq)
483
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
484
    return util.render_metadata(request, metadata, use_values=False,
485
                                status=200)
486

    
487

    
488
@api.api_method(http_method='POST', user_required=True, logger=log)
489
def update_metadata(request, server_id):
490
    # Normal Response Code: 201
491
    # Error Response Codes: computeFault (400, 500),
492
    #                       serviceUnavailable (503),
493
    #                       unauthorized (401),
494
    #                       badRequest (400),
495
    #                       buildInProgress (409),
496
    #                       badMediaType(415),
497
    #                       overLimit (413)
498

    
499
    req = utils.get_request_dict(request)
500
    log.info('update_server_metadata %s %s', server_id, req)
501
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
502
    try:
503
        metadata = req['metadata']
504
        assert isinstance(metadata, dict)
505
    except (KeyError, AssertionError):
506
        raise faults.BadRequest("Malformed request")
507

    
508
    for key, val in metadata.items():
509
        meta, created = vm.metadata.get_or_create(meta_key=key)
510
        meta.meta_value = val
511
        meta.save()
512

    
513
    vm.save()
514
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
515
    return util.render_metadata(request, vm_meta, status=201)
516

    
517

    
518
@api.api_method(http_method='GET', user_required=True, logger=log)
519
def get_metadata_item(request, server_id, key):
520
    # Normal Response Codes: 200, 203
521
    # Error Response Codes: computeFault (400, 500),
522
    #                       serviceUnavailable (503),
523
    #                       unauthorized (401),
524
    #                       itemNotFound (404),
525
    #                       badRequest (400),
526
    #                       overLimit (413)
527

    
528
    log.debug('get_server_metadata_item %s %s', server_id, key)
529
    vm = util.get_vm(server_id, request.user_uniq)
530
    meta = util.get_vm_meta(vm, key)
531
    d = {meta.meta_key: meta.meta_value}
532
    return util.render_meta(request, d, status=200)
533

    
534

    
535
@api.api_method(http_method='PUT', user_required=True, logger=log)
536
@transaction.commit_on_success
537
def create_metadata_item(request, server_id, key):
538
    # Normal Response Code: 201
539
    # Error Response Codes: computeFault (400, 500),
540
    #                       serviceUnavailable (503),
541
    #                       unauthorized (401),
542
    #                       itemNotFound (404),
543
    #                       badRequest (400),
544
    #                       buildInProgress (409),
545
    #                       badMediaType(415),
546
    #                       overLimit (413)
547

    
548
    req = utils.get_request_dict(request)
549
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
550
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
551
    try:
552
        metadict = req['meta']
553
        assert isinstance(metadict, dict)
554
        assert len(metadict) == 1
555
        assert key in metadict
556
    except (KeyError, AssertionError):
557
        raise faults.BadRequest("Malformed request")
558

    
559
    meta, created = VirtualMachineMetadata.objects.get_or_create(
560
        meta_key=key,
561
        vm=vm)
562

    
563
    meta.meta_value = metadict[key]
564
    meta.save()
565
    vm.save()
566
    d = {meta.meta_key: meta.meta_value}
567
    return util.render_meta(request, d, status=201)
568

    
569

    
570
@api.api_method(http_method='DELETE', user_required=True, logger=log)
571
@transaction.commit_on_success
572
def delete_metadata_item(request, server_id, key):
573
    # Normal Response Code: 204
574
    # Error Response Codes: computeFault (400, 500),
575
    #                       serviceUnavailable (503),
576
    #                       unauthorized (401),
577
    #                       itemNotFound (404),
578
    #                       badRequest (400),
579
    #                       buildInProgress (409),
580
    #                       badMediaType(415),
581
    #                       overLimit (413),
582

    
583
    log.info('delete_server_metadata_item %s %s', server_id, key)
584
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
585
    meta = util.get_vm_meta(vm, key)
586
    meta.delete()
587
    vm.save()
588
    return HttpResponse(status=204)
589

    
590

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

    
601
    log.debug('server_stats %s', server_id)
602
    vm = util.get_vm(server_id, request.user_uniq)
603
    #secret = util.encrypt(vm.backend_vm_id)
604
    secret = vm.backend_vm_id      # XXX disable backend id encryption
605

    
606
    stats = {
607
        'serverRef': vm.id,
608
        'refresh': settings.STATS_REFRESH_PERIOD,
609
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
610
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
611
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
612
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
613

    
614
    if request.serialization == 'xml':
615
        data = render_to_string('server_stats.xml', stats)
616
    else:
617
        data = json.dumps({'stats': stats})
618

    
619
    return HttpResponse(data, status=200)
620

    
621

    
622
# ACTIONS
623

    
624

    
625
server_actions = {}
626
network_actions = {}
627

    
628

    
629
def server_action(name):
630
    '''Decorator for functions implementing server actions.
631
    `name` is the key in the dict passed by the client.
632
    '''
633

    
634
    def decorator(func):
635
        server_actions[name] = func
636
        return func
637
    return decorator
638

    
639

    
640
def network_action(name):
641
    '''Decorator for functions implementing network actions.
642
    `name` is the key in the dict passed by the client.
643
    '''
644

    
645
    def decorator(func):
646
        network_actions[name] = func
647
        return func
648
    return decorator
649

    
650

    
651
@server_action('start')
652
def start(request, vm, args):
653
    # Normal Response Code: 202
654
    # Error Response Codes: serviceUnavailable (503),
655
    #                       itemNotFound (404)
656
    vm = servers.start(vm)
657
    return HttpResponse(status=202)
658

    
659

    
660
@server_action('shutdown')
661
def shutdown(request, vm, args):
662
    # Normal Response Code: 202
663
    # Error Response Codes: serviceUnavailable (503),
664
    #                       itemNotFound (404)
665
    vm = servers.stop(vm)
666
    return HttpResponse(status=202)
667

    
668

    
669
@server_action('reboot')
670
def reboot(request, vm, args):
671
    # Normal Response Code: 202
672
    # Error Response Codes: computeFault (400, 500),
673
    #                       serviceUnavailable (503),
674
    #                       unauthorized (401),
675
    #                       badRequest (400),
676
    #                       badMediaType(415),
677
    #                       itemNotFound (404),
678
    #                       buildInProgress (409),
679
    #                       overLimit (413)
680

    
681
    reboot_type = args.get("type")
682
    if reboot_type is None:
683
        raise faults.BadRequest("Missing 'type' attribute.")
684
    elif reboot_type not in ["SOFT", "HARD"]:
685
        raise faults.BadRequest("Invalid 'type' attribute.")
686
    vm = servers.reboot(vm, reboot_type=reboot_type)
687
    return HttpResponse(status=202)
688

    
689

    
690
@server_action('firewallProfile')
691
def set_firewall_profile(request, vm, args):
692
    # Normal Response Code: 200
693
    # Error Response Codes: computeFault (400, 500),
694
    #                       serviceUnavailable (503),
695
    #                       unauthorized (401),
696
    #                       badRequest (400),
697
    #                       badMediaType(415),
698
    #                       itemNotFound (404),
699
    #                       buildInProgress (409),
700
    #                       overLimit (413)
701
    profile = args.get("profile")
702
    if profile is None:
703
        raise faults.BadRequest("Missing 'profile' attribute")
704
    servers.set_firewall_profile(vm, profile=profile)
705
    return HttpResponse(status=202)
706

    
707

    
708
@server_action('resize')
709
def resize(request, vm, args):
710
    # Normal Response Code: 202
711
    # Error Response Codes: computeFault (400, 500),
712
    #                       serviceUnavailable (503),
713
    #                       unauthorized (401),
714
    #                       badRequest (400),
715
    #                       badMediaType(415),
716
    #                       itemNotFound (404),
717
    #                       buildInProgress (409),
718
    #                       serverCapacityUnavailable (503),
719
    #                       overLimit (413),
720
    #                       resizeNotAllowed (403)
721
    flavorRef = args.get("flavorRef")
722
    if flavorRef is None:
723
        raise faults.BadRequest("Missing 'flavorRef' attribute.")
724
    flavor = util.get_flavor(flavor_id=flavorRef, include_deleted=False)
725
    servers.resize(vm, flavor=flavor)
726
    return HttpResponse(status=202)
727

    
728

    
729
@server_action('console')
730
def get_console(request, vm, args):
731
    # Normal Response Code: 200
732
    # Error Response Codes: computeFault (400, 500),
733
    #                       serviceUnavailable (503),
734
    #                       unauthorized (401),
735
    #                       badRequest (400),
736
    #                       badMediaType(415),
737
    #                       itemNotFound (404),
738
    #                       buildInProgress (409),
739
    #                       overLimit (413)
740

    
741
    log.info("Get console  VM %s: %s", vm, args)
742

    
743
    console_type = args.get("type")
744
    if console_type is None:
745
        raise faults.BadRequest("No console 'type' specified.")
746
    elif console_type != "vnc":
747
        raise faults.BadRequest("Console 'type' can only be 'vnc'.")
748
    console_info = servers.console(vm, console_type)
749

    
750
    if request.serialization == 'xml':
751
        mimetype = 'application/xml'
752
        data = render_to_string('console.xml', {'console': console_info})
753
    else:
754
        mimetype = 'application/json'
755
        data = json.dumps({'console': console_info})
756

    
757
    return HttpResponse(data, mimetype=mimetype, status=200)
758

    
759

    
760
@server_action('changePassword')
761
def change_password(request, vm, args):
762
    raise faults.NotImplemented('Changing password is not supported.')
763

    
764

    
765
@server_action('rebuild')
766
def rebuild(request, vm, args):
767
    raise faults.NotImplemented('Rebuild not supported.')
768

    
769

    
770
@server_action('confirmResize')
771
def confirm_resize(request, vm, args):
772
    raise faults.NotImplemented('Resize not supported.')
773

    
774

    
775
@server_action('revertResize')
776
def revert_resize(request, vm, args):
777
    raise faults.NotImplemented('Resize not supported.')
778

    
779

    
780
@network_action('add')
781
@transaction.commit_on_success
782
def add(request, net, args):
783
    # Normal Response Code: 202
784
    # Error Response Codes: computeFault (400, 500),
785
    #                       serviceUnavailable (503),
786
    #                       unauthorized (401),
787
    #                       badRequest (400),
788
    #                       buildInProgress (409),
789
    #                       badMediaType(415),
790
    #                       itemNotFound (404),
791
    #                       overLimit (413)
792
    server_id = args.get('serverRef', None)
793
    if not server_id:
794
        raise faults.BadRequest('Malformed Request.')
795

    
796
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
797
    servers.connect(vm, network=net)
798
    return HttpResponse(status=202)
799

    
800

    
801
@network_action('remove')
802
@transaction.commit_on_success
803
def remove(request, net, args):
804
    # Normal Response Code: 202
805
    # Error Response Codes: computeFault (400, 500),
806
    #                       serviceUnavailable (503),
807
    #                       unauthorized (401),
808
    #                       badRequest (400),
809
    #                       badMediaType(415),
810
    #                       itemNotFound (404),
811
    #                       overLimit (413)
812

    
813
    attachment = args.get("attachment")
814
    if attachment is None:
815
        raise faults.BadRequest("Missing 'attachment' attribute.")
816
    try:
817
        # attachment string: nic-<vm-id>-<nic-index>
818
        _, server_id, nic_index = attachment.split("-", 2)
819
        server_id = int(server_id)
820
        nic_index = int(nic_index)
821
    except (ValueError, TypeError):
822
        raise faults.BadRequest("Invalid 'attachment' attribute.")
823

    
824
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
825
    servers.disconnect(vm, nic_index=nic_index)
826

    
827
    return HttpResponse(status=202)
828

    
829

    
830
@server_action("addFloatingIp")
831
def add_floating_ip(request, vm, args):
832
    address = args.get("address")
833
    if address is None:
834
        raise faults.BadRequest("Missing 'address' attribute")
835

    
836
    servers.add_floating_ip(vm, address)
837
    return HttpResponse(status=202)
838

    
839

    
840
@server_action("removeFloatingIp")
841
def remove_floating_ip(request, vm, args):
842
    address = args.get("address")
843
    if address is None:
844
        raise faults.BadRequest("Missing 'address' attribute")
845

    
846
    servers.remove_floating_ip(vm, address)
847
    return HttpResponse(status=202)