Statistics
| Branch: | Tag: | Revision:

root / api / actions.py @ 1f092444

History | View | Annotate | Download (11.6 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
    # Normal Response Code: 200
208
    # Error Response Codes: computeFault (400, 500),
209
    #                       serviceUnavailable (503),
210
    #                       unauthorized (401),
211
    #                       badRequest (400),
212
    #                       badMediaType(415),
213
    #                       itemNotFound (404),
214
    #                       buildInProgress (409),
215
    #                       overLimit (413)
216

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

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

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

    
230
    if console_data['kind'] != 'vnc':
231
        message = 'Could not create a console of requested type.'
232
        raise ServiceUnavailable(message)
233

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

    
242
    try:
243
        if settings.TEST:
244
            fwd = {'source_port': 1234, 'status': 'OK'}
245
        else:
246
            fwd = request_vnc_forwarding(sport, daddr, dport, password)
247
    except Exception:
248
        raise ServiceUnavailable('Could not allocate VNC console port.')
249

    
250
    if fwd['status'] != "OK":
251
        raise ServiceUnavailable('Could not allocate VNC console.')
252

    
253
    console = {
254
        'type': 'vnc',
255
        'host': getfqdn(),
256
        'port': fwd['source_port'],
257
        'password': password}
258

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

    
266
    return HttpResponse(data, mimetype=mimetype, status=200)
267

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

    
286

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

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

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

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