Statistics
| Branch: | Tag: | Revision:

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

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
    d = {'id': util.construct_nic_id(nic),
108
         'network_id': str(nic.network.id),
109
         'mac_address': nic.mac,
110
         'ipv4': nic.ipv4 if nic.ipv4 else None,
111
         'ipv6': nic.ipv6 if nic.ipv6 else None}
112

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

    
117

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

    
132

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

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

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

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

    
172
    return d
173

    
174

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

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

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

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

    
198
        entries.append(entry)
199

    
200
    return entries
201

    
202

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

    
212

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

    
219

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

    
230

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

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

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

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

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

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

    
262
    return HttpResponse(data, status=200)
263

    
264

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

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

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

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

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

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

    
316
    return response
317

    
318

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

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

    
334

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

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

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

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

    
360
    return HttpResponse(status=204)
361

    
362

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

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

    
380

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

    
384

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

    
396

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

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

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

    
410
    action = req.keys()[0]
411

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

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

    
420
    return server_actions[action](request, vm, action_args)
421

    
422

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

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

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

    
442
    return HttpResponse(data, status=200)
443

    
444

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

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

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

    
466
    return HttpResponse(data, status=200)
467

    
468

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

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

    
484

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

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

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

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

    
514

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

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

    
531

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

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

    
556
    meta, created = VirtualMachineMetadata.objects.get_or_create(
557
        meta_key=key,
558
        vm=vm)
559

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

    
566

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

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

    
587

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

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

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

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

    
616
    return HttpResponse(data, status=200)
617

    
618

    
619
# ACTIONS
620

    
621

    
622
server_actions = {}
623
network_actions = {}
624

    
625

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

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

    
636

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

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

    
647

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

    
656

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

    
665

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

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

    
686

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

    
704

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

    
725

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

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

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

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

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

    
756

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

    
761

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

    
766

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

    
771

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

    
776

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

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

    
797

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

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

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

    
824
    return HttpResponse(status=202)
825

    
826

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

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

    
836

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

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