Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / actions.py @ bb54871d

History | View | Annotate | Download (11.7 kB)

1
# Copyright 2011 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 socket import getfqdn
35
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
36

    
37
from django.conf import settings
38
from django.http import HttpResponse
39
from django.template.loader import render_to_string
40
from django.utils import simplejson as json
41

    
42
from synnefo.api.faults import BadRequest, ServiceUnavailable
43
from synnefo.api.util import random_password, get_vm
44
from synnefo.db.models import NetworkInterface
45
from synnefo.logic import backend
46
from synnefo.logic.utils import get_rsapi_state
47

    
48

    
49
server_actions = {}
50
network_actions = {}
51

    
52

    
53
def server_action(name):
54
    '''Decorator for functions implementing server actions.
55
    `name` is the key in the dict passed by the client.
56
    '''
57

    
58
    def decorator(func):
59
        server_actions[name] = func
60
        return func
61
    return decorator
62

    
63
def network_action(name):
64
    '''Decorator for functions implementing network actions.
65
    `name` is the key in the dict passed by the client.
66
    '''
67

    
68
    def decorator(func):
69
        network_actions[name] = func
70
        return func
71
    return decorator
72

    
73

    
74
@server_action('changePassword')
75
def change_password(request, vm, args):
76
    # Normal Response Code: 202
77
    # Error Response Codes: computeFault (400, 500),
78
    #                       serviceUnavailable (503),
79
    #                       unauthorized (401),
80
    #                       badRequest (400),
81
    #                       badMediaType(415),
82
    #                       itemNotFound (404),
83
    #                       buildInProgress (409),
84
    #                       overLimit (413)
85

    
86
    try:
87
        password = args['adminPass']
88
    except KeyError:
89
        raise BadRequest('Malformed request.')
90

    
91
    raise ServiceUnavailable('Changing password is not supported.')
92

    
93
@server_action('reboot')
94
def reboot(request, vm, args):
95
    # Normal Response Code: 202
96
    # Error Response Codes: computeFault (400, 500),
97
    #                       serviceUnavailable (503),
98
    #                       unauthorized (401),
99
    #                       badRequest (400),
100
    #                       badMediaType(415),
101
    #                       itemNotFound (404),
102
    #                       buildInProgress (409),
103
    #                       overLimit (413)
104

    
105
    reboot_type = args.get('type', '')
106
    if reboot_type not in ('SOFT', 'HARD'):
107
        raise BadRequest('Malformed Request.')
108
    backend.reboot_instance(vm, reboot_type.lower())
109
    return HttpResponse(status=202)
110

    
111
@server_action('start')
112
def start(request, vm, args):
113
    # Normal Response Code: 202
114
    # Error Response Codes: serviceUnavailable (503),
115
    #                       itemNotFound (404)
116

    
117
    if args:
118
        raise BadRequest('Malformed Request.')
119
    backend.startup_instance(vm)
120
    return HttpResponse(status=202)
121

    
122
@server_action('shutdown')
123
def shutdown(request, vm, args):
124
    # Normal Response Code: 202
125
    # Error Response Codes: serviceUnavailable (503),
126
    #                       itemNotFound (404)
127

    
128
    if args:
129
        raise BadRequest('Malformed Request.')
130
    backend.shutdown_instance(vm)
131
    return HttpResponse(status=202)
132

    
133
@server_action('rebuild')
134
def rebuild(request, vm, args):
135
    # Normal Response Code: 202
136
    # Error Response Codes: computeFault (400, 500),
137
    #                       serviceUnavailable (503),
138
    #                       unauthorized (401),
139
    #                       badRequest (400),
140
    #                       badMediaType(415),
141
    #                       itemNotFound (404),
142
    #                       buildInProgress (409),
143
    #                       serverCapacityUnavailable (503),
144
    #                       overLimit (413)
145

    
146
    raise ServiceUnavailable('Rebuild not supported.')
147

    
148
@server_action('resize')
149
def resize(request, vm, args):
150
    # Normal Response Code: 202
151
    # Error Response Codes: computeFault (400, 500),
152
    #                       serviceUnavailable (503),
153
    #                       unauthorized (401),
154
    #                       badRequest (400),
155
    #                       badMediaType(415),
156
    #                       itemNotFound (404),
157
    #                       buildInProgress (409),
158
    #                       serverCapacityUnavailable (503),
159
    #                       overLimit (413),
160
    #                       resizeNotAllowed (403)
161

    
162
    raise ServiceUnavailable('Resize not supported.')
163

    
164
@server_action('confirmResize')
165
def confirm_resize(request, vm, args):
166
    # Normal Response Code: 204
167
    # Error Response Codes: computeFault (400, 500),
168
    #                       serviceUnavailable (503),
169
    #                       unauthorized (401),
170
    #                       badRequest (400),
171
    #                       badMediaType(415),
172
    #                       itemNotFound (404),
173
    #                       buildInProgress (409),
174
    #                       serverCapacityUnavailable (503),
175
    #                       overLimit (413),
176
    #                       resizeNotAllowed (403)
177

    
178
    raise ServiceUnavailable('Resize not supported.')
179

    
180
@server_action('revertResize')
181
def revert_resize(request, vm, args):
182
    # Normal Response Code: 202
183
    # Error Response Codes: computeFault (400, 500),
184
    #                       serviceUnavailable (503),
185
    #                       unauthorized (401),
186
    #                       badRequest (400),
187
    #                       badMediaType(415),
188
    #                       itemNotFound (404),
189
    #                       buildInProgress (409),
190
    #                       serverCapacityUnavailable (503),
191
    #                       overLimit (413),
192
    #                       resizeNotAllowed (403)
193

    
194
    raise ServiceUnavailable('Resize not supported.')
195

    
196
@server_action('console')
197
def get_console(request, vm, args):
198
    """Arrange for an OOB console of the specified type
199

200
    This method arranges for an OOB console of the specified type.
201
    Only consoles of type "vnc" are supported for now.
202

203
    It uses a running instance of vncauthproxy to setup proper
204
    VNC forwarding with a random password, then returns the necessary
205
    VNC connection info to the caller.
206

207
    """
208
    # Normal Response Code: 200
209
    # Error Response Codes: computeFault (400, 500),
210
    #                       serviceUnavailable (503),
211
    #                       unauthorized (401),
212
    #                       badRequest (400),
213
    #                       badMediaType(415),
214
    #                       itemNotFound (404),
215
    #                       buildInProgress (409),
216
    #                       overLimit (413)
217

    
218
    console_type = args.get('type', '')
219
    if console_type != 'vnc':
220
        raise BadRequest('Type can only be "vnc".')
221

    
222
    # Use RAPI to get VNC console information for this instance
223
    if get_rsapi_state(vm) != 'ACTIVE':
224
        raise BadRequest('Server not in ACTIVE state.')
225

    
226
    if settings.TEST:
227
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
228
    else:
229
        console_data = backend.get_instance_console(vm)
230

    
231
    if console_data['kind'] != 'vnc':
232
        message = 'got console of kind %s, not "vnc"' % console_data['kind']
233
        raise ServiceUnavailable(message)
234

    
235
    # Let vncauthproxy decide on the source port.
236
    # The alternative: static allocation, e.g.
237
    # sport = console_data['port'] - 1000
238
    sport = 0
239
    daddr = console_data['host']
240
    dport = console_data['port']
241
    password = random_password()
242

    
243
    if settings.TEST:
244
        fwd = {'source_port': 1234, 'status': 'OK'}
245
    else:
246
        fwd = request_vnc_forwarding(sport, daddr, dport, password)
247

    
248
    if fwd['status'] != "OK":
249
        raise ServiceUnavailable('vncauthproxy returned error status')
250

    
251
    # Verify that the VNC server settings haven't changed
252
    if not settings.TEST:
253
        if console_data != backend.get_instance_console(vm):
254
            raise ServiceUnavailable('VNC Server settings changed.')
255

    
256
    console = {
257
        'type': 'vnc',
258
        'host': getfqdn(),
259
        'port': fwd['source_port'],
260
        'password': password}
261

    
262
    if request.serialization == 'xml':
263
        mimetype = 'application/xml'
264
        data = render_to_string('console.xml', {'console': console})
265
    else:
266
        mimetype = 'application/json'
267
        data = json.dumps({'console': console})
268

    
269
    return HttpResponse(data, mimetype=mimetype, status=200)
270

    
271
@server_action('firewallProfile')
272
def set_firewall_profile(request, vm, args):
273
    # Normal Response Code: 200
274
    # Error Response Codes: computeFault (400, 500),
275
    #                       serviceUnavailable (503),
276
    #                       unauthorized (401),
277
    #                       badRequest (400),
278
    #                       badMediaType(415),
279
    #                       itemNotFound (404),
280
    #                       buildInProgress (409),
281
    #                       overLimit (413)
282
    
283
    profile = args.get('profile', '')
284
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
285
        raise BadRequest("Unsupported firewall profile")
286
    backend.set_firewall_profile(vm, profile)
287
    return HttpResponse(status=202)
288

    
289

    
290
@network_action('add')
291
def add(request, net, args):
292
    # Normal Response Code: 202
293
    # Error Response Codes: computeFault (400, 500),
294
    #                       serviceUnavailable (503),
295
    #                       unauthorized (401),
296
    #                       badRequest (400),
297
    #                       badMediaType(415),
298
    #                       itemNotFound (404),
299
    #                       overLimit (413)
300

    
301
    server_id = args.get('serverRef', None)
302
    if not server_id:
303
        raise BadRequest('Malformed Request.')
304
    vm = get_vm(server_id, request.user_uniq)
305
    backend.connect_to_network(vm, net)
306
    vm.save()
307
    net.save()
308
    return HttpResponse(status=202)
309

    
310
@network_action('remove')
311
def remove(request, net, args):
312
    # Normal Response Code: 202
313
    # Error Response Codes: computeFault (400, 500),
314
    #                       serviceUnavailable (503),
315
    #                       unauthorized (401),
316
    #                       badRequest (400),
317
    #                       badMediaType(415),
318
    #                       itemNotFound (404),
319
    #                       overLimit (413)
320

    
321
    server_id = args.get('serverRef', None)
322
    if not server_id:
323
        raise BadRequest('Malformed Request.')
324
    vm = get_vm(server_id, request.user_uniq)
325
    backend.disconnect_from_network(vm, net)
326
    vm.save()
327
    net.save()
328
    return HttpResponse(status=202)