Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (29 kB)

1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from django.conf import settings
35
from django.conf.urls.defaults import patterns
36
from django.db import transaction
37
from django.http import HttpResponse
38
from django.template.loader import render_to_string
39
from django.utils import simplejson as json
40

    
41
from snf_django.lib import api
42
from snf_django.lib.api import faults, utils
43
from synnefo.api import util
44
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata)
45
from synnefo.logic import servers, utils as logic_utils
46

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

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

    
64

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

    
73

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

    
84

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

    
93

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

    
104

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

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

    
116

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

    
131

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

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

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

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

    
171
    return d
172

    
173

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

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

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

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

    
197
        entries.append(entry)
198

    
199
    return entries
200

    
201

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

    
211

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

    
218

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

    
229

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

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

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

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

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

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

    
261
    return HttpResponse(data, status=200)
262

    
263

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

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

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

    
302
    vm = servers.create(user_id, name, password, flavor, image,
303
                        metadata=metadata, personality=personality,
304
                        private_networks=private_networks)
305

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

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

    
312
    return response
313

    
314

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

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

    
330

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

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

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

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

    
356
    return HttpResponse(status=204)
357

    
358

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

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

    
376

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

    
380

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

    
392

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

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

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

    
406
    try:
407
        action = req.keys()[0]
408
    except KeyError:
409
        raise faults.BadRequest("Unknown action")
410

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

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

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

    
421

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

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

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

    
441
    return HttpResponse(data, status=200)
442

    
443

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

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

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

    
465
    return HttpResponse(data, status=200)
466

    
467

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

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

    
483

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

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

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

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

    
513

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

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

    
530

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

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

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

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

    
565

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

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

    
586

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

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

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

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

    
615
    return HttpResponse(data, status=200)
616

    
617

    
618
# ACTIONS
619

    
620

    
621
server_actions = {}
622
network_actions = {}
623

    
624

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

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

    
635

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

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

    
646

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

    
655

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

    
664

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

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

    
685

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

    
703

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

    
724

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

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

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

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

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

    
755

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

    
760

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

    
765

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

    
770

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

    
775

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

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

    
796

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

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

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

    
823
    return HttpResponse(status=202)
824

    
825

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

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

    
835

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

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