Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.9 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
from synnefo.api import util
44
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata)
45
from synnefo.logic import servers, utils as logic_utils
46

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

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

    
64

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

    
73

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

    
84

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

    
93

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

    
104

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

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

    
116

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

    
131

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

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

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

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

    
171
    return d
172

    
173

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

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

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

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

    
197
        entries.append(entry)
198

    
199
    return entries
200

    
201

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

    
211

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

    
218

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

    
229

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

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

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

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

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

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

    
261
    return HttpResponse(data, status=200)
262

    
263

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

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

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

    
300
    vm = servers.create(user_id, name, password, flavor, image,
301
                        metadata=metadata, personality=personality)
302

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

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

    
309
    return response
310

    
311

    
312
@api.api_method(http_method='GET', user_required=True, logger=log)
313
def get_server_details(request, server_id):
314
    # Normal Response Codes: 200, 203
315
    # Error Response Codes: computeFault (400, 500),
316
    #                       serviceUnavailable (503),
317
    #                       unauthorized (401),
318
    #                       badRequest (400),
319
    #                       itemNotFound (404),
320
    #                       overLimit (413)
321

    
322
    log.debug('get_server_details %s', server_id)
323
    vm = util.get_vm(server_id, request.user_uniq)
324
    server = vm_to_dict(vm, detail=True)
325
    return render_server(request, server)
326

    
327

    
328
@api.api_method(http_method='PUT', user_required=True, logger=log)
329
def update_server_name(request, server_id):
330
    # Normal Response Code: 204
331
    # Error Response Codes: computeFault (400, 500),
332
    #                       serviceUnavailable (503),
333
    #                       unauthorized (401),
334
    #                       badRequest (400),
335
    #                       badMediaType(415),
336
    #                       itemNotFound (404),
337
    #                       buildInProgress (409),
338
    #                       overLimit (413)
339

    
340
    req = utils.get_request_dict(request)
341
    log.info('update_server_name %s %s', server_id, req)
342

    
343
    try:
344
        name = req['server']['name']
345
    except (TypeError, KeyError):
346
        raise faults.BadRequest("Malformed request")
347

    
348
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
349
                     non_suspended=True)
350
    vm.name = name
351
    vm.save()
352

    
353
    return HttpResponse(status=204)
354

    
355

    
356
@api.api_method(http_method='DELETE', user_required=True, logger=log)
357
def delete_server(request, server_id):
358
    # Normal Response Codes: 204
359
    # Error Response Codes: computeFault (400, 500),
360
    #                       serviceUnavailable (503),
361
    #                       unauthorized (401),
362
    #                       itemNotFound (404),
363
    #                       unauthorized (401),
364
    #                       buildInProgress (409),
365
    #                       overLimit (413)
366

    
367
    log.info('delete_server %s', server_id)
368
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
369
                     non_suspended=True)
370
    vm = servers.destroy(vm)
371
    return HttpResponse(status=204)
372

    
373

    
374
# additional server actions
375
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
376

    
377

    
378
def key_to_action(key):
379
    """Map HTTP request key to a VM Action"""
380
    if key == "shutdown":
381
        return "STOP"
382
    if key == "delete":
383
        return "DESTROY"
384
    if key in ARBITRARY_ACTIONS:
385
        return None
386
    else:
387
        return key.upper()
388

    
389

    
390
@api.api_method(http_method='POST', user_required=True, logger=log)
391
@transaction.commit_on_success
392
def demux_server_action(request, server_id):
393
    req = utils.get_request_dict(request)
394
    log.debug('server_action %s %s', server_id, req)
395

    
396
    if len(req) != 1:
397
        raise faults.BadRequest("Malformed request")
398

    
399
    # Do not allow any action on deleted or suspended VMs
400
    vm = util.get_vm(server_id, request.user_uniq, for_update=True,
401
                     non_deleted=True, non_suspended=True)
402

    
403
    try:
404
        action = req.keys()[0]
405
    except KeyError:
406
        raise faults.BadRequest("Unknown action")
407

    
408
    if key_to_action(action) not in [x[0] for x in VirtualMachine.ACTIONS]:
409
        if action not in ARBITRARY_ACTIONS:
410
            raise faults.BadRequest("Action %s not supported" % action)
411
    action_args = req[action]
412

    
413
    if not isinstance(action_args, dict):
414
        raise faults.BadRequest("Invalid argument")
415

    
416
    return server_actions[action](request, vm, action_args)
417

    
418

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

    
428
    log.debug('list_addresses %s', server_id)
429
    vm = util.get_vm(server_id, request.user_uniq)
430
    attachments = [nic_to_dict(nic) for nic in vm.nics.all()]
431
    addresses = nics_to_addresses(vm.nics.all())
432

    
433
    if request.serialization == 'xml':
434
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
435
    else:
436
        data = json.dumps({'addresses': addresses, 'attachments': attachments})
437

    
438
    return HttpResponse(data, status=200)
439

    
440

    
441
@api.api_method(http_method='GET', user_required=True, logger=log)
442
def list_addresses_by_network(request, server_id, network_id):
443
    # Normal Response Codes: 200, 203
444
    # Error Response Codes: computeFault (400, 500),
445
    #                       serviceUnavailable (503),
446
    #                       unauthorized (401),
447
    #                       badRequest (400),
448
    #                       itemNotFound (404),
449
    #                       overLimit (413)
450

    
451
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
452
    machine = util.get_vm(server_id, request.user_uniq)
453
    network = util.get_network(network_id, request.user_uniq)
454
    nics = machine.nics.filter(network=network).all()
455
    addresses = nics_to_addresses(nics)
456

    
457
    if request.serialization == 'xml':
458
        data = render_to_string('address.xml', {'addresses': addresses})
459
    else:
460
        data = json.dumps({'network': addresses})
461

    
462
    return HttpResponse(data, status=200)
463

    
464

    
465
@api.api_method(http_method='GET', user_required=True, logger=log)
466
def list_metadata(request, server_id):
467
    # Normal Response Codes: 200, 203
468
    # Error Response Codes: computeFault (400, 500),
469
    #                       serviceUnavailable (503),
470
    #                       unauthorized (401),
471
    #                       badRequest (400),
472
    #                       overLimit (413)
473

    
474
    log.debug('list_server_metadata %s', server_id)
475
    vm = util.get_vm(server_id, request.user_uniq)
476
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
477
    return util.render_metadata(request, metadata, use_values=False,
478
                                status=200)
479

    
480

    
481
@api.api_method(http_method='POST', user_required=True, logger=log)
482
def update_metadata(request, server_id):
483
    # Normal Response Code: 201
484
    # Error Response Codes: computeFault (400, 500),
485
    #                       serviceUnavailable (503),
486
    #                       unauthorized (401),
487
    #                       badRequest (400),
488
    #                       buildInProgress (409),
489
    #                       badMediaType(415),
490
    #                       overLimit (413)
491

    
492
    req = utils.get_request_dict(request)
493
    log.info('update_server_metadata %s %s', server_id, req)
494
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
495
    try:
496
        metadata = req['metadata']
497
        assert isinstance(metadata, dict)
498
    except (KeyError, AssertionError):
499
        raise faults.BadRequest("Malformed request")
500

    
501
    for key, val in metadata.items():
502
        meta, created = vm.metadata.get_or_create(meta_key=key)
503
        meta.meta_value = val
504
        meta.save()
505

    
506
    vm.save()
507
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
508
    return util.render_metadata(request, vm_meta, status=201)
509

    
510

    
511
@api.api_method(http_method='GET', user_required=True, logger=log)
512
def get_metadata_item(request, server_id, key):
513
    # Normal Response Codes: 200, 203
514
    # Error Response Codes: computeFault (400, 500),
515
    #                       serviceUnavailable (503),
516
    #                       unauthorized (401),
517
    #                       itemNotFound (404),
518
    #                       badRequest (400),
519
    #                       overLimit (413)
520

    
521
    log.debug('get_server_metadata_item %s %s', server_id, key)
522
    vm = util.get_vm(server_id, request.user_uniq)
523
    meta = util.get_vm_meta(vm, key)
524
    d = {meta.meta_key: meta.meta_value}
525
    return util.render_meta(request, d, status=200)
526

    
527

    
528
@api.api_method(http_method='PUT', user_required=True, logger=log)
529
@transaction.commit_on_success
530
def create_metadata_item(request, server_id, key):
531
    # Normal Response Code: 201
532
    # Error Response Codes: computeFault (400, 500),
533
    #                       serviceUnavailable (503),
534
    #                       unauthorized (401),
535
    #                       itemNotFound (404),
536
    #                       badRequest (400),
537
    #                       buildInProgress (409),
538
    #                       badMediaType(415),
539
    #                       overLimit (413)
540

    
541
    req = utils.get_request_dict(request)
542
    log.info('create_server_metadata_item %s %s %s', server_id, key, req)
543
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
544
    try:
545
        metadict = req['meta']
546
        assert isinstance(metadict, dict)
547
        assert len(metadict) == 1
548
        assert key in metadict
549
    except (KeyError, AssertionError):
550
        raise faults.BadRequest("Malformed request")
551

    
552
    meta, created = VirtualMachineMetadata.objects.get_or_create(
553
        meta_key=key,
554
        vm=vm)
555

    
556
    meta.meta_value = metadict[key]
557
    meta.save()
558
    vm.save()
559
    d = {meta.meta_key: meta.meta_value}
560
    return util.render_meta(request, d, status=201)
561

    
562

    
563
@api.api_method(http_method='DELETE', user_required=True, logger=log)
564
@transaction.commit_on_success
565
def delete_metadata_item(request, server_id, key):
566
    # Normal Response Code: 204
567
    # Error Response Codes: computeFault (400, 500),
568
    #                       serviceUnavailable (503),
569
    #                       unauthorized (401),
570
    #                       itemNotFound (404),
571
    #                       badRequest (400),
572
    #                       buildInProgress (409),
573
    #                       badMediaType(415),
574
    #                       overLimit (413),
575

    
576
    log.info('delete_server_metadata_item %s %s', server_id, key)
577
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
578
    meta = util.get_vm_meta(vm, key)
579
    meta.delete()
580
    vm.save()
581
    return HttpResponse(status=204)
582

    
583

    
584
@api.api_method(http_method='GET', user_required=True, logger=log)
585
def server_stats(request, server_id):
586
    # Normal Response Codes: 200
587
    # Error Response Codes: computeFault (400, 500),
588
    #                       serviceUnavailable (503),
589
    #                       unauthorized (401),
590
    #                       badRequest (400),
591
    #                       itemNotFound (404),
592
    #                       overLimit (413)
593

    
594
    log.debug('server_stats %s', server_id)
595
    vm = util.get_vm(server_id, request.user_uniq)
596
    #secret = util.encrypt(vm.backend_vm_id)
597
    secret = vm.backend_vm_id      # XXX disable backend id encryption
598

    
599
    stats = {
600
        'serverRef': vm.id,
601
        'refresh': settings.STATS_REFRESH_PERIOD,
602
        'cpuBar': settings.CPU_BAR_GRAPH_URL % secret,
603
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
604
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
605
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
606

    
607
    if request.serialization == 'xml':
608
        data = render_to_string('server_stats.xml', stats)
609
    else:
610
        data = json.dumps({'stats': stats})
611

    
612
    return HttpResponse(data, status=200)
613

    
614

    
615
# ACTIONS
616

    
617

    
618
server_actions = {}
619
network_actions = {}
620

    
621

    
622
def server_action(name):
623
    '''Decorator for functions implementing server actions.
624
    `name` is the key in the dict passed by the client.
625
    '''
626

    
627
    def decorator(func):
628
        server_actions[name] = func
629
        return func
630
    return decorator
631

    
632

    
633
def network_action(name):
634
    '''Decorator for functions implementing network actions.
635
    `name` is the key in the dict passed by the client.
636
    '''
637

    
638
    def decorator(func):
639
        network_actions[name] = func
640
        return func
641
    return decorator
642

    
643

    
644
@server_action('start')
645
def start(request, vm, args):
646
    # Normal Response Code: 202
647
    # Error Response Codes: serviceUnavailable (503),
648
    #                       itemNotFound (404)
649
    vm = servers.start(vm)
650
    return HttpResponse(status=202)
651

    
652

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

    
661

    
662
@server_action('reboot')
663
def reboot(request, vm, args):
664
    # Normal Response Code: 202
665
    # Error Response Codes: computeFault (400, 500),
666
    #                       serviceUnavailable (503),
667
    #                       unauthorized (401),
668
    #                       badRequest (400),
669
    #                       badMediaType(415),
670
    #                       itemNotFound (404),
671
    #                       buildInProgress (409),
672
    #                       overLimit (413)
673

    
674
    reboot_type = args.get("type")
675
    if reboot_type is None:
676
        raise faults.BadRequest("Missing 'type' attribute.")
677
    elif reboot_type not in ["SOFT", "HARD"]:
678
        raise faults.BadRequest("Invalid 'type' attribute.")
679
    vm = servers.reboot(vm, reboot_type=reboot_type)
680
    return HttpResponse(status=202)
681

    
682

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

    
700

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

    
721

    
722
@server_action('console')
723
def get_console(request, vm, args):
724
    # Normal Response Code: 200
725
    # Error Response Codes: computeFault (400, 500),
726
    #                       serviceUnavailable (503),
727
    #                       unauthorized (401),
728
    #                       badRequest (400),
729
    #                       badMediaType(415),
730
    #                       itemNotFound (404),
731
    #                       buildInProgress (409),
732
    #                       overLimit (413)
733

    
734
    log.info("Get console  VM %s: %s", vm, args)
735

    
736
    console_type = args.get("type")
737
    if console_type is None:
738
        raise faults.BadRequest("No console 'type' specified.")
739
    elif console_type != "vnc":
740
        raise faults.BadRequest("Console 'type' can only be 'vnc'.")
741
    console_info = servers.console(vm, console_type)
742

    
743
    if request.serialization == 'xml':
744
        mimetype = 'application/xml'
745
        data = render_to_string('console.xml', {'console': console_info})
746
    else:
747
        mimetype = 'application/json'
748
        data = json.dumps({'console': console_info})
749

    
750
    return HttpResponse(data, mimetype=mimetype, status=200)
751

    
752

    
753
@server_action('changePassword')
754
def change_password(request, vm, args):
755
    raise faults.NotImplemented('Changing password is not supported.')
756

    
757

    
758
@server_action('rebuild')
759
def rebuild(request, vm, args):
760
    raise faults.NotImplemented('Rebuild not supported.')
761

    
762

    
763
@server_action('confirmResize')
764
def confirm_resize(request, vm, args):
765
    raise faults.NotImplemented('Resize not supported.')
766

    
767

    
768
@server_action('revertResize')
769
def revert_resize(request, vm, args):
770
    raise faults.NotImplemented('Resize not supported.')
771

    
772

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

    
789
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
790
    servers.connect(vm, network=net)
791
    return HttpResponse(status=202)
792

    
793

    
794
@network_action('remove')
795
@transaction.commit_on_success
796
def remove(request, net, args):
797
    # Normal Response Code: 202
798
    # Error Response Codes: computeFault (400, 500),
799
    #                       serviceUnavailable (503),
800
    #                       unauthorized (401),
801
    #                       badRequest (400),
802
    #                       badMediaType(415),
803
    #                       itemNotFound (404),
804
    #                       overLimit (413)
805

    
806
    attachment = args.get("attachment")
807
    if attachment is None:
808
        raise faults.BadRequest("Missing 'attachment' attribute.")
809
    try:
810
        # attachment string: nic-<vm-id>-<nic-index>
811
        _, server_id, nic_index = attachment.split("-", 2)
812
        server_id = int(server_id)
813
        nic_index = int(nic_index)
814
    except (ValueError, TypeError):
815
        raise faults.BadRequest("Invalid 'attachment' attribute.")
816

    
817
    vm = util.get_vm(server_id, request.user_uniq, non_suspended=True)
818
    servers.disconnect(vm, nic_index=nic_index)
819

    
820
    return HttpResponse(status=202)
821

    
822

    
823
@server_action("addfloatingip")
824
def add_floating_ip(request, vm, args):
825
    address = args.get("address")
826
    if address is None:
827
        raise faults.BadRequest("Missing 'address' attribute")
828

    
829
    servers.add_floating_ip(vm, address)
830
    return HttpResponse(status=202)
831

    
832

    
833
@server_action("removefloatingip")
834
def remove_floating_ip(request, vm, args):
835
    address = args.get("address")
836
    if address is None:
837
        raise faults.BadRequest("Missing 'address' attribute")
838

    
839
    servers.remove_floating_ip(vm, address)
840
    return HttpResponse(status=202)