Statistics
| Branch: | Tag: | Revision:

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

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
    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 if nic.ipv4 else None,
112
         'ipv6': nic.ipv6 if nic.ipv6 else None,
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
    since = utils.isoparse(request.GET.get('changes-since'))
246

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

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

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

    
264
    return HttpResponse(data, status=200)
265

    
266

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

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

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

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

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

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

    
318
    return response
319

    
320

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

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

    
336

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

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

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

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

    
362
    return HttpResponse(status=204)
363

    
364

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

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

    
382

    
383
# additional server actions
384
ARBITRARY_ACTIONS = ['console', 'firewallProfile']
385

    
386

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

    
398

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

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

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

    
412
    action = req.keys()[0]
413

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

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

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

    
424

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

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

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

    
444
    return HttpResponse(data, status=200)
445

    
446

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

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

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

    
468
    return HttpResponse(data, status=200)
469

    
470

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

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

    
486

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

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

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

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

    
516

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

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

    
533

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

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

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

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

    
568

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

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

    
589

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

    
600
    log.debug('server_stats %s', server_id)
601
    vm = util.get_vm(server_id, request.user_uniq)
602
    secret = util.encrypt(vm.backend_vm_id)
603

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

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

    
617
    return HttpResponse(data, status=200)
618

    
619

    
620
# ACTIONS
621

    
622

    
623
server_actions = {}
624
network_actions = {}
625

    
626

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

    
632
    def decorator(func):
633
        server_actions[name] = func
634
        return func
635
    return decorator
636

    
637

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

    
643
    def decorator(func):
644
        network_actions[name] = func
645
        return func
646
    return decorator
647

    
648

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

    
657

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

    
666

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

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

    
687

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

    
705

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

    
726

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

    
739
    log.info("Get console  VM %s: %s", vm, args)
740

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

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

    
755
    return HttpResponse(data, mimetype=mimetype, status=200)
756

    
757

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

    
762

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

    
767

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

    
772

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

    
777

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

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

    
798

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

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

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

    
825
    return HttpResponse(status=202)
826

    
827

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

    
834
    servers.add_floating_ip(vm, address)
835
    return HttpResponse(status=202)
836

    
837

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

    
844
    servers.remove_floating_ip(vm, address)
845
    return HttpResponse(status=202)