Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.5 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
    console = {
252
        'type': 'vnc',
253
        'host': getfqdn(),
254
        'port': fwd['source_port'],
255
        'password': password}
256

    
257
    if request.serialization == 'xml':
258
        mimetype = 'application/xml'
259
        data = render_to_string('console.xml', {'console': console})
260
    else:
261
        mimetype = 'application/json'
262
        data = json.dumps({'console': console})
263

    
264
    return HttpResponse(data, mimetype=mimetype, status=200)
265

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

    
284

    
285
@network_action('add')
286
def add(request, net, args):
287
    # Normal Response Code: 202
288
    # Error Response Codes: computeFault (400, 500),
289
    #                       serviceUnavailable (503),
290
    #                       unauthorized (401),
291
    #                       badRequest (400),
292
    #                       badMediaType(415),
293
    #                       itemNotFound (404),
294
    #                       overLimit (413)
295

    
296
    server_id = args.get('serverRef', None)
297
    if not server_id:
298
        raise BadRequest('Malformed Request.')
299
    vm = get_vm(server_id, request.user_uniq)
300
    backend.connect_to_network(vm, net)
301
    vm.save()
302
    net.save()
303
    return HttpResponse(status=202)
304

    
305
@network_action('remove')
306
def remove(request, net, args):
307
    # Normal Response Code: 202
308
    # Error Response Codes: computeFault (400, 500),
309
    #                       serviceUnavailable (503),
310
    #                       unauthorized (401),
311
    #                       badRequest (400),
312
    #                       badMediaType(415),
313
    #                       itemNotFound (404),
314
    #                       overLimit (413)
315

    
316
    server_id = args.get('serverRef', None)
317
    if not server_id:
318
        raise BadRequest('Malformed Request.')
319
    vm = get_vm(server_id, request.user_uniq)
320
    backend.disconnect_from_network(vm, net)
321
    vm.save()
322
    net.save()
323
    return HttpResponse(status=202)