Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (29.1 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
    ip_type = "floating" if nic.is_floating_ip else "fixed"
108
    d = {'id': util.construct_nic_id(nic),
109
         'network_id': str(nic.network.id),
110
         'mac_address': nic.mac,
111
         'ipv4': nic.ipv4,
112
         'ipv6': nic.ipv6,
113
         'OS-EXT-IPS:type': ip_type}
114

    
115
    if nic.firewall_profile:
116
        d['firewallProfile'] = nic.firewall_profile
117
    return d
118

    
119

    
120
def attachments_to_addresses(attachments):
121
    addresses = {}
122
    for nic in attachments:
123
        net_nics = []
124
        net_nics.append({"version": 4,
125
                         "addr": nic["ipv4"],
126
                         "OS-EXT-IPS:type": nic["OS-EXT-IPS:type"]})
127
        if nic["ipv6"]:
128
            net_nics.append({"version": 6,
129
                             "addr": nic["ipv6"],
130
                             "OS-EXT-IPS:type": nic["OS-EXT-IPS:type"]})
131
        addresses[nic["network_id"]] = net_nics
132
    return addresses
133

    
134

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

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

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

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

    
174
    return d
175

    
176

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

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

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

    
197
        if formatted_source_date:
198
            entry['source_date'] = formatted_source_date
199

    
200
        entries.append(entry)
201

    
202
    return entries
203

    
204

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

    
214

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

    
221

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

    
232

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

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

    
245
    user_vms = utils.filter_modified_since(request, objects=user_vms)
246

    
247
    servers_dict = [vm_to_dict(server, detail)
248
                    for server in user_vms.order_by('id')]
249

    
250
    if request.serialization == 'xml':
251
        data = render_to_string('list_servers.xml', {
252
            'servers': servers_dict,
253
            'detail': detail})
254
    else:
255
        data = json.dumps({'servers': servers_dict})
256

    
257
    return HttpResponse(data, status=200)
258

    
259

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

    
275
    try:
276
        server = req['server']
277
        name = server['name']
278
        metadata = server.get('metadata', {})
279
        assert isinstance(metadata, dict)
280
        image_id = server['imageRef']
281
        flavor_id = server['flavorRef']
282
        personality = server.get('personality', [])
283
        assert isinstance(personality, list)
284
        private_networks = server.get("networks", [])
285
        assert isinstance(private_networks, list)
286
        floating_ips = server.get("floating_ips", [])
287
        assert isinstance(floating_ips, 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
                        private_networks=private_networks,
303
                        floating_ips=floating_ips)
304

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

    
309
    response = render_server(request, server, status=202)
310

    
311
    return response
312

    
313

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

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

    
329

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

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

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

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

    
355
    return HttpResponse(status=204)
356

    
357

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

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

    
375

    
376
# additional server actions
377
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
378

    
379

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

    
391

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

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

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

    
405
    action = req.keys()[0]
406

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

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

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

    
417

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

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

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

    
437
    return HttpResponse(data, status=200)
438

    
439

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

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

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

    
461
    return HttpResponse(data, status=200)
462

    
463

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

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

    
479

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

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

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

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

    
509

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

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

    
526

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

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

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

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

    
561

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

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

    
582

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

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

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

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

    
611
    return HttpResponse(data, status=200)
612

    
613

    
614
# ACTIONS
615

    
616

    
617
server_actions = {}
618
network_actions = {}
619

    
620

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

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

    
631

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

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

    
642

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

    
651

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

    
660

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

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

    
681

    
682
@server_action('firewallProfile')
683
def set_firewall_profile(request, vm, args):
684
    # Normal Response Code: 200
685
    # Error Response Codes: computeFault (400, 500),
686
    #                       serviceUnavailable (503),
687
    #                       unauthorized (401),
688
    #                       badRequest (400),
689
    #                       badMediaType(415),
690
    #                       itemNotFound (404),
691
    #                       buildInProgress (409),
692
    #                       overLimit (413)
693
    profile = args.get("profile")
694
    if profile is None:
695
        raise faults.BadRequest("Missing 'profile' attribute")
696
    index = args.get("index", 0)
697
    servers.set_firewall_profile(vm, profile=profile, index=index)
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)