Statistics
| Branch: | Tag: | Revision:

root / api / actions.py @ bc923fb7

History | View | Annotate | Download (11.6 kB)

1 adee02b8 Giorgos Verigakis
# Copyright 2011 GRNET S.A. All rights reserved.
2 adee02b8 Giorgos Verigakis
# 
3 adee02b8 Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
4 adee02b8 Giorgos Verigakis
# without modification, are permitted provided that the following
5 adee02b8 Giorgos Verigakis
# conditions are met:
6 adee02b8 Giorgos Verigakis
# 
7 adee02b8 Giorgos Verigakis
#   1. Redistributions of source code must retain the above
8 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
9 adee02b8 Giorgos Verigakis
#      disclaimer.
10 adee02b8 Giorgos Verigakis
# 
11 adee02b8 Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
12 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
13 adee02b8 Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
14 adee02b8 Giorgos Verigakis
#      provided with the distribution.
15 adee02b8 Giorgos Verigakis
# 
16 adee02b8 Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 adee02b8 Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 adee02b8 Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 adee02b8 Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 adee02b8 Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 adee02b8 Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 adee02b8 Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 adee02b8 Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 adee02b8 Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 adee02b8 Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 adee02b8 Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 adee02b8 Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
28 adee02b8 Giorgos Verigakis
# 
29 adee02b8 Giorgos Verigakis
# The views and conclusions contained in the software and
30 adee02b8 Giorgos Verigakis
# documentation are those of the authors and should not be
31 adee02b8 Giorgos Verigakis
# interpreted as representing official policies, either expressed
32 adee02b8 Giorgos Verigakis
# or implied, of GRNET S.A.
33 b016b476 Giorgos Verigakis
34 aea3e7c6 Vangelis Koukis
from socket import getfqdn
35 aea3e7c6 Vangelis Koukis
36 b016b476 Giorgos Verigakis
from django.conf import settings
37 b016b476 Giorgos Verigakis
from django.http import HttpResponse
38 09471611 Vangelis Koukis
from django.template.loader import render_to_string
39 09471611 Vangelis Koukis
from django.utils import simplejson as json
40 b016b476 Giorgos Verigakis
41 529178b1 Giorgos Verigakis
from synnefo.api.faults import BadRequest, ServiceUnavailable
42 64938cb0 Giorgos Verigakis
from synnefo.api.util import random_password, get_vm
43 26563957 Giorgos Verigakis
from synnefo.db.models import NetworkInterface
44 09471611 Vangelis Koukis
from synnefo.util.vapclient import request_forwarding as request_vnc_forwarding
45 64938cb0 Giorgos Verigakis
from synnefo.logic import backend
46 20a51b1e Giorgos Verigakis
from synnefo.logic.utils import get_rsapi_state
47 d8e50a39 Giorgos Verigakis
48 0971fa71 Markos Gogoulos
49 b016b476 Giorgos Verigakis
server_actions = {}
50 e2ee7808 Giorgos Verigakis
network_actions = {}
51 b016b476 Giorgos Verigakis
52 b016b476 Giorgos Verigakis
53 b016b476 Giorgos Verigakis
def server_action(name):
54 d8e50a39 Giorgos Verigakis
    '''Decorator for functions implementing server actions.
55 529178b1 Giorgos Verigakis
    `name` is the key in the dict passed by the client.
56 d8e50a39 Giorgos Verigakis
    '''
57 aa197ee4 Vangelis Koukis
58 b016b476 Giorgos Verigakis
    def decorator(func):
59 b016b476 Giorgos Verigakis
        server_actions[name] = func
60 b016b476 Giorgos Verigakis
        return func
61 b016b476 Giorgos Verigakis
    return decorator
62 b016b476 Giorgos Verigakis
63 e2ee7808 Giorgos Verigakis
def network_action(name):
64 e2ee7808 Giorgos Verigakis
    '''Decorator for functions implementing network actions.
65 e2ee7808 Giorgos Verigakis
    `name` is the key in the dict passed by the client.
66 e2ee7808 Giorgos Verigakis
    '''
67 e2ee7808 Giorgos Verigakis
68 e2ee7808 Giorgos Verigakis
    def decorator(func):
69 e2ee7808 Giorgos Verigakis
        network_actions[name] = func
70 e2ee7808 Giorgos Verigakis
        return func
71 e2ee7808 Giorgos Verigakis
    return decorator
72 e2ee7808 Giorgos Verigakis
73 b016b476 Giorgos Verigakis
74 b016b476 Giorgos Verigakis
@server_action('changePassword')
75 20a51b1e Giorgos Verigakis
def change_password(request, vm, args):
76 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
77 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
78 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
79 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
80 b016b476 Giorgos Verigakis
    #                       badRequest (400),
81 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
82 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
83 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
84 b016b476 Giorgos Verigakis
    #                       overLimit (413)
85 aa197ee4 Vangelis Koukis
86 b016b476 Giorgos Verigakis
    try:
87 529178b1 Giorgos Verigakis
        password = args['adminPass']
88 b016b476 Giorgos Verigakis
    except KeyError:
89 529178b1 Giorgos Verigakis
        raise BadRequest('Malformed request.')
90 b016b476 Giorgos Verigakis
91 529178b1 Giorgos Verigakis
    raise ServiceUnavailable('Changing password is not supported.')
92 b016b476 Giorgos Verigakis
93 b016b476 Giorgos Verigakis
@server_action('reboot')
94 20a51b1e Giorgos Verigakis
def reboot(request, vm, args):
95 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
96 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
97 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
98 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
99 b016b476 Giorgos Verigakis
    #                       badRequest (400),
100 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
101 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
102 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
103 b016b476 Giorgos Verigakis
    #                       overLimit (413)
104 aa197ee4 Vangelis Koukis
105 0971fa71 Markos Gogoulos
    reboot_type = args.get('type', '')
106 0971fa71 Markos Gogoulos
    if reboot_type not in ('SOFT', 'HARD'):
107 529178b1 Giorgos Verigakis
        raise BadRequest('Malformed Request.')
108 64938cb0 Giorgos Verigakis
    backend.reboot_instance(vm, reboot_type.lower())
109 b016b476 Giorgos Verigakis
    return HttpResponse(status=202)
110 b016b476 Giorgos Verigakis
111 b016b476 Giorgos Verigakis
@server_action('start')
112 20a51b1e Giorgos Verigakis
def start(request, vm, args):
113 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
114 d8e50a39 Giorgos Verigakis
    # Error Response Codes: serviceUnavailable (503),
115 d8e50a39 Giorgos Verigakis
    #                       itemNotFound (404)
116 aa197ee4 Vangelis Koukis
117 529178b1 Giorgos Verigakis
    if args:
118 529178b1 Giorgos Verigakis
        raise BadRequest('Malformed Request.')
119 64938cb0 Giorgos Verigakis
    backend.startup_instance(vm)
120 b016b476 Giorgos Verigakis
    return HttpResponse(status=202)
121 b016b476 Giorgos Verigakis
122 b016b476 Giorgos Verigakis
@server_action('shutdown')
123 20a51b1e Giorgos Verigakis
def shutdown(request, vm, args):
124 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
125 d8e50a39 Giorgos Verigakis
    # Error Response Codes: serviceUnavailable (503),
126 d8e50a39 Giorgos Verigakis
    #                       itemNotFound (404)
127 aa197ee4 Vangelis Koukis
128 529178b1 Giorgos Verigakis
    if args:
129 529178b1 Giorgos Verigakis
        raise BadRequest('Malformed Request.')
130 64938cb0 Giorgos Verigakis
    backend.shutdown_instance(vm)
131 b016b476 Giorgos Verigakis
    return HttpResponse(status=202)
132 b016b476 Giorgos Verigakis
133 b016b476 Giorgos Verigakis
@server_action('rebuild')
134 20a51b1e Giorgos Verigakis
def rebuild(request, vm, args):
135 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
136 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
137 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
138 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
139 b016b476 Giorgos Verigakis
    #                       badRequest (400),
140 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
141 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
142 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
143 b016b476 Giorgos Verigakis
    #                       serverCapacityUnavailable (503),
144 b016b476 Giorgos Verigakis
    #                       overLimit (413)
145 b016b476 Giorgos Verigakis
146 529178b1 Giorgos Verigakis
    raise ServiceUnavailable('Rebuild not supported.')
147 b016b476 Giorgos Verigakis
148 b016b476 Giorgos Verigakis
@server_action('resize')
149 20a51b1e Giorgos Verigakis
def resize(request, vm, args):
150 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
151 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
152 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
153 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
154 b016b476 Giorgos Verigakis
    #                       badRequest (400),
155 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
156 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
157 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
158 b016b476 Giorgos Verigakis
    #                       serverCapacityUnavailable (503),
159 b016b476 Giorgos Verigakis
    #                       overLimit (413),
160 b016b476 Giorgos Verigakis
    #                       resizeNotAllowed (403)
161 aa197ee4 Vangelis Koukis
162 529178b1 Giorgos Verigakis
    raise ServiceUnavailable('Resize not supported.')
163 b016b476 Giorgos Verigakis
164 b016b476 Giorgos Verigakis
@server_action('confirmResize')
165 20a51b1e Giorgos Verigakis
def confirm_resize(request, vm, args):
166 b016b476 Giorgos Verigakis
    # Normal Response Code: 204
167 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
168 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
169 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
170 b016b476 Giorgos Verigakis
    #                       badRequest (400),
171 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
172 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
173 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
174 b016b476 Giorgos Verigakis
    #                       serverCapacityUnavailable (503),
175 b016b476 Giorgos Verigakis
    #                       overLimit (413),
176 b016b476 Giorgos Verigakis
    #                       resizeNotAllowed (403)
177 aa197ee4 Vangelis Koukis
178 529178b1 Giorgos Verigakis
    raise ServiceUnavailable('Resize not supported.')
179 b016b476 Giorgos Verigakis
180 b016b476 Giorgos Verigakis
@server_action('revertResize')
181 20a51b1e Giorgos Verigakis
def revert_resize(request, vm, args):
182 b016b476 Giorgos Verigakis
    # Normal Response Code: 202
183 b016b476 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
184 b016b476 Giorgos Verigakis
    #                       serviceUnavailable (503),
185 b016b476 Giorgos Verigakis
    #                       unauthorized (401),
186 b016b476 Giorgos Verigakis
    #                       badRequest (400),
187 b016b476 Giorgos Verigakis
    #                       badMediaType(415),
188 b016b476 Giorgos Verigakis
    #                       itemNotFound (404),
189 b016b476 Giorgos Verigakis
    #                       buildInProgress (409),
190 b016b476 Giorgos Verigakis
    #                       serverCapacityUnavailable (503),
191 b016b476 Giorgos Verigakis
    #                       overLimit (413),
192 b016b476 Giorgos Verigakis
    #                       resizeNotAllowed (403)
193 b016b476 Giorgos Verigakis
194 529178b1 Giorgos Verigakis
    raise ServiceUnavailable('Resize not supported.')
195 529178b1 Giorgos Verigakis
196 529178b1 Giorgos Verigakis
@server_action('console')
197 529178b1 Giorgos Verigakis
def get_console(request, vm, args):
198 529178b1 Giorgos Verigakis
    """Arrange for an OOB console of the specified type
199 529178b1 Giorgos Verigakis

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

203 529178b1 Giorgos Verigakis
    It uses a running instance of vncauthproxy to setup proper
204 529178b1 Giorgos Verigakis
    VNC forwarding with a random password, then returns the necessary
205 529178b1 Giorgos Verigakis
    VNC connection info to the caller.
206 529178b1 Giorgos Verigakis
    """
207 529178b1 Giorgos Verigakis
    # Normal Response Code: 200
208 529178b1 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
209 529178b1 Giorgos Verigakis
    #                       serviceUnavailable (503),
210 529178b1 Giorgos Verigakis
    #                       unauthorized (401),
211 529178b1 Giorgos Verigakis
    #                       badRequest (400),
212 529178b1 Giorgos Verigakis
    #                       badMediaType(415),
213 529178b1 Giorgos Verigakis
    #                       itemNotFound (404),
214 529178b1 Giorgos Verigakis
    #                       buildInProgress (409),
215 529178b1 Giorgos Verigakis
    #                       overLimit (413)
216 aa197ee4 Vangelis Koukis
217 529178b1 Giorgos Verigakis
    console_type = args.get('type', '')
218 529178b1 Giorgos Verigakis
    if console_type != 'vnc':
219 529178b1 Giorgos Verigakis
        raise BadRequest('Type can only be "vnc".')
220 529178b1 Giorgos Verigakis
221 529178b1 Giorgos Verigakis
    # Use RAPI to get VNC console information for this instance
222 529178b1 Giorgos Verigakis
    if get_rsapi_state(vm) != 'ACTIVE':
223 529178b1 Giorgos Verigakis
        raise BadRequest('Server not in ACTIVE state.')
224 aa197ee4 Vangelis Koukis
225 529178b1 Giorgos Verigakis
    if settings.TEST:
226 529178b1 Giorgos Verigakis
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
227 529178b1 Giorgos Verigakis
    else:
228 64938cb0 Giorgos Verigakis
        console_data = backend.get_instance_console(vm)
229 aa197ee4 Vangelis Koukis
230 529178b1 Giorgos Verigakis
    if console_data['kind'] != 'vnc':
231 b36f78fa Giorgos Verigakis
        message = 'Could not create a console of requested type.'
232 b36f78fa Giorgos Verigakis
        raise ServiceUnavailable(message)
233 aa197ee4 Vangelis Koukis
234 529178b1 Giorgos Verigakis
    # Let vncauthproxy decide on the source port.
235 529178b1 Giorgos Verigakis
    # The alternative: static allocation, e.g.
236 529178b1 Giorgos Verigakis
    # sport = console_data['port'] - 1000
237 529178b1 Giorgos Verigakis
    sport = 0
238 529178b1 Giorgos Verigakis
    daddr = console_data['host']
239 529178b1 Giorgos Verigakis
    dport = console_data['port']
240 529178b1 Giorgos Verigakis
    password = random_password()
241 aa197ee4 Vangelis Koukis
242 529178b1 Giorgos Verigakis
    try:
243 529178b1 Giorgos Verigakis
        if settings.TEST:
244 529178b1 Giorgos Verigakis
            fwd = {'source_port': 1234, 'status': 'OK'}
245 529178b1 Giorgos Verigakis
        else:
246 529178b1 Giorgos Verigakis
            fwd = request_vnc_forwarding(sport, daddr, dport, password)
247 529178b1 Giorgos Verigakis
    except Exception:
248 529178b1 Giorgos Verigakis
        raise ServiceUnavailable('Could not allocate VNC console port.')
249 529178b1 Giorgos Verigakis
250 529178b1 Giorgos Verigakis
    if fwd['status'] != "OK":
251 529178b1 Giorgos Verigakis
        raise ServiceUnavailable('Could not allocate VNC console.')
252 aa197ee4 Vangelis Koukis
253 529178b1 Giorgos Verigakis
    console = {
254 529178b1 Giorgos Verigakis
        'type': 'vnc',
255 529178b1 Giorgos Verigakis
        'host': getfqdn(),
256 529178b1 Giorgos Verigakis
        'port': fwd['source_port'],
257 529178b1 Giorgos Verigakis
        'password': password}
258 aa197ee4 Vangelis Koukis
259 529178b1 Giorgos Verigakis
    if request.serialization == 'xml':
260 529178b1 Giorgos Verigakis
        mimetype = 'application/xml'
261 529178b1 Giorgos Verigakis
        data = render_to_string('console.xml', {'console': console})
262 529178b1 Giorgos Verigakis
    else:
263 529178b1 Giorgos Verigakis
        mimetype = 'application/json'
264 529178b1 Giorgos Verigakis
        data = json.dumps({'console': console})
265 aa197ee4 Vangelis Koukis
266 529178b1 Giorgos Verigakis
    return HttpResponse(data, mimetype=mimetype, status=200)
267 e2ee7808 Giorgos Verigakis
268 91826390 Giorgos Verigakis
@server_action('firewallProfile')
269 91826390 Giorgos Verigakis
def set_firewall_profile(request, vm, args):
270 26563957 Giorgos Verigakis
    # Normal Response Code: 200
271 26563957 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
272 26563957 Giorgos Verigakis
    #                       serviceUnavailable (503),
273 26563957 Giorgos Verigakis
    #                       unauthorized (401),
274 26563957 Giorgos Verigakis
    #                       badRequest (400),
275 26563957 Giorgos Verigakis
    #                       badMediaType(415),
276 26563957 Giorgos Verigakis
    #                       itemNotFound (404),
277 26563957 Giorgos Verigakis
    #                       buildInProgress (409),
278 26563957 Giorgos Verigakis
    #                       overLimit (413)
279 26563957 Giorgos Verigakis
    
280 91826390 Giorgos Verigakis
    profile = args.get('profile', '')
281 26563957 Giorgos Verigakis
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
282 91826390 Giorgos Verigakis
        raise BadRequest("Unsupported firewall profile")
283 91826390 Giorgos Verigakis
    backend.set_firewall_profile(vm, profile)
284 91826390 Giorgos Verigakis
    return HttpResponse(status=202)
285 91826390 Giorgos Verigakis
286 e2ee7808 Giorgos Verigakis
287 e2ee7808 Giorgos Verigakis
@network_action('add')
288 e2ee7808 Giorgos Verigakis
def add(request, net, args):
289 13b954b0 Giorgos Verigakis
    # Normal Response Code: 202
290 13b954b0 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
291 13b954b0 Giorgos Verigakis
    #                       serviceUnavailable (503),
292 13b954b0 Giorgos Verigakis
    #                       unauthorized (401),
293 13b954b0 Giorgos Verigakis
    #                       badRequest (400),
294 13b954b0 Giorgos Verigakis
    #                       badMediaType(415),
295 13b954b0 Giorgos Verigakis
    #                       itemNotFound (404),
296 13b954b0 Giorgos Verigakis
    #                       overLimit (413)
297 aa197ee4 Vangelis Koukis
298 dca6520b Giorgos Verigakis
    server_id = args.get('serverRef', None)
299 e2ee7808 Giorgos Verigakis
    if not server_id:
300 e2ee7808 Giorgos Verigakis
        raise BadRequest('Malformed Request.')
301 e2ee7808 Giorgos Verigakis
    vm = get_vm(server_id, request.user)
302 64938cb0 Giorgos Verigakis
    backend.connect_to_network(vm, net)
303 5509b599 Giorgos Verigakis
    vm.save()
304 d44c236b Giorgos Verigakis
    net.save()
305 e2ee7808 Giorgos Verigakis
    return HttpResponse(status=202)
306 e2ee7808 Giorgos Verigakis
307 e2ee7808 Giorgos Verigakis
@network_action('remove')
308 e2ee7808 Giorgos Verigakis
def remove(request, net, args):
309 13b954b0 Giorgos Verigakis
    # Normal Response Code: 202
310 13b954b0 Giorgos Verigakis
    # Error Response Codes: computeFault (400, 500),
311 13b954b0 Giorgos Verigakis
    #                       serviceUnavailable (503),
312 13b954b0 Giorgos Verigakis
    #                       unauthorized (401),
313 13b954b0 Giorgos Verigakis
    #                       badRequest (400),
314 13b954b0 Giorgos Verigakis
    #                       badMediaType(415),
315 13b954b0 Giorgos Verigakis
    #                       itemNotFound (404),
316 13b954b0 Giorgos Verigakis
    #                       overLimit (413)
317 aa197ee4 Vangelis Koukis
318 dca6520b Giorgos Verigakis
    server_id = args.get('serverRef', None)
319 e2ee7808 Giorgos Verigakis
    if not server_id:
320 e2ee7808 Giorgos Verigakis
        raise BadRequest('Malformed Request.')
321 e2ee7808 Giorgos Verigakis
    vm = get_vm(server_id, request.user)
322 64938cb0 Giorgos Verigakis
    backend.disconnect_from_network(vm, net)
323 5509b599 Giorgos Verigakis
    vm.save()
324 d44c236b Giorgos Verigakis
    net.save()
325 e2ee7808 Giorgos Verigakis
    return HttpResponse(status=202)