Revision 529178b1
b/api/actions.py | ||
---|---|---|
9 | 9 |
from django.template.loader import render_to_string |
10 | 10 |
from django.utils import simplejson as json |
11 | 11 |
|
12 |
from synnefo.api.faults import BadRequest, ResizeNotAllowed, ServiceUnavailable
|
|
12 |
from synnefo.api.faults import BadRequest, ServiceUnavailable |
|
13 | 13 |
from synnefo.api.util import random_password |
14 |
from synnefo.util.rapi import GanetiRapiClient |
|
15 | 14 |
from synnefo.util.vapclient import request_forwarding as request_vnc_forwarding |
16 |
from synnefo.logic import backend |
|
15 |
from synnefo.logic.backend import (reboot_instance, startup_instance, shutdown_instance, |
|
16 |
get_instance_console) |
|
17 | 17 |
from synnefo.logic.utils import get_rsapi_state |
18 | 18 |
|
19 | 19 |
|
20 | 20 |
server_actions = {} |
21 | 21 |
|
22 |
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO) |
|
23 |
|
|
24 | 22 |
|
25 | 23 |
def server_action(name): |
26 | 24 |
'''Decorator for functions implementing server actions. |
27 | 25 |
|
28 |
`name` is the key in the dict passed by the client.
|
|
26 |
`name` is the key in the dict passed by the client. |
|
29 | 27 |
''' |
30 | 28 |
|
31 | 29 |
def decorator(func): |
... | ... | |
33 | 31 |
return func |
34 | 32 |
return decorator |
35 | 33 |
|
36 |
@server_action('console') |
|
37 |
def get_console(request, vm, args): |
|
38 |
"""Arrange for an OOB console of the specified type |
|
39 |
|
|
40 |
This method arranges for an OOB console of the specified type. |
|
41 |
Only consoles of type "vnc" are supported for now. |
|
42 |
|
|
43 |
It uses a running instance of vncauthproxy to setup proper |
|
44 |
VNC forwarding with a random password, then returns the necessary |
|
45 |
VNC connection info to the caller. |
|
46 |
|
|
47 |
JSON Request: { |
|
48 |
"console": { |
|
49 |
"type": "vnc" |
|
50 |
} |
|
51 |
} |
|
52 |
|
|
53 |
JSON Reply: { |
|
54 |
"vnc": { |
|
55 |
"host": "fqdn_here", |
|
56 |
"port": a_port_here, |
|
57 |
"password": "a_password_here" |
|
58 |
} |
|
59 |
} |
|
60 |
|
|
61 |
""" |
|
62 |
# Normal Response Code: 200 |
|
63 |
# Error Response Codes: computeFault (400, 500), |
|
64 |
# serviceUnavailable (503), |
|
65 |
# unauthorized (401), |
|
66 |
# badRequest (400), |
|
67 |
# badMediaType(415), |
|
68 |
# itemNotFound (404), |
|
69 |
# buildInProgress (409), |
|
70 |
# overLimit (413) |
|
71 |
try: |
|
72 |
console_type = args.get('type', '') |
|
73 |
if console_type != 'vnc': |
|
74 |
raise BadRequest(message="type can only be 'vnc'") |
|
75 |
except KeyError: |
|
76 |
raise BadRequest() |
|
77 |
|
|
78 |
# Use RAPI to get VNC console information for this instance |
|
79 |
if get_rsapi_state(vm) != 'ACTIVE': |
|
80 |
raise BadRequest(message="Server not in ACTIVE state") |
|
81 |
if settings.TEST: |
|
82 |
console_data = { 'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000 } |
|
83 |
else: |
|
84 |
console_data = rapi.GetInstanceConsole(vm.backend_id) |
|
85 |
if console_data['kind'] != 'vnc': |
|
86 |
raise ServiceUnavailable() |
|
87 |
|
|
88 |
# Let vncauthproxy decide on the source port. |
|
89 |
# The alternative: static allocation, e.g. |
|
90 |
# sport = console_data['port'] - 1000 |
|
91 |
sport = 0 |
|
92 |
daddr = console_data['host'] |
|
93 |
dport = console_data['port'] |
|
94 |
passwd = random_password() |
|
95 |
|
|
96 |
try: |
|
97 |
if settings.TEST: |
|
98 |
fwd = { 'source_port': 1234, 'status': 'OK' } |
|
99 |
else: |
|
100 |
fwd = request_vnc_forwarding(sport, daddr, dport, passwd) |
|
101 |
if fwd['status'] != "OK": |
|
102 |
raise ServiceUnavailable() |
|
103 |
vnc = { 'host': getfqdn(), 'port': fwd['source_port'], 'password': passwd } |
|
104 |
except Exception: |
|
105 |
raise ServiceUnavailable("Could not allocate VNC console port") |
|
106 |
|
|
107 |
# Format to be reviewed by [verigak], FIXME |
|
108 |
if request.serialization == 'xml': |
|
109 |
mimetype = 'application/xml' |
|
110 |
data = render_to_string('vnc.xml', {'vnc': vnc}) |
|
111 |
else: |
|
112 |
mimetype = 'application/json' |
|
113 |
data = json.dumps({'vnc': vnc}) |
|
114 |
|
|
115 |
return HttpResponse(data, mimetype=mimetype, status=200) |
|
116 |
|
|
117 | 34 |
|
118 | 35 |
@server_action('changePassword') |
119 | 36 |
def change_password(request, vm, args): |
... | ... | |
128 | 45 |
# overLimit (413) |
129 | 46 |
|
130 | 47 |
try: |
131 |
adminPass = args['adminPass']
|
|
48 |
password = args['adminPass']
|
|
132 | 49 |
except KeyError: |
133 |
raise BadRequest() |
|
50 |
raise BadRequest('Malformed request.')
|
|
134 | 51 |
|
135 |
raise ServiceUnavailable() |
|
52 |
raise ServiceUnavailable('Changing password is not supported.')
|
|
136 | 53 |
|
137 | 54 |
@server_action('reboot') |
138 | 55 |
def reboot(request, vm, args): |
... | ... | |
148 | 65 |
|
149 | 66 |
reboot_type = args.get('type', '') |
150 | 67 |
if reboot_type not in ('SOFT', 'HARD'): |
151 |
raise BadRequest() |
|
152 |
|
|
153 |
backend.start_action(vm, 'REBOOT') |
|
154 |
rapi.RebootInstance(vm.backend_id, reboot_type.lower()) |
|
68 |
raise BadRequest('Malformed Request.') |
|
69 |
reboot_instance(vm, reboot_type.lower()) |
|
155 | 70 |
return HttpResponse(status=202) |
156 | 71 |
|
157 | 72 |
@server_action('start') |
... | ... | |
159 | 74 |
# Normal Response Code: 202 |
160 | 75 |
# Error Response Codes: serviceUnavailable (503), |
161 | 76 |
# itemNotFound (404) |
162 |
|
|
163 |
backend.start_action(vm, 'START') |
|
164 |
rapi.StartupInstance(vm.backend_id) |
|
77 |
|
|
78 |
if args: |
|
79 |
raise BadRequest('Malformed Request.') |
|
80 |
startup_instance(vm) |
|
165 | 81 |
return HttpResponse(status=202) |
166 | 82 |
|
167 | 83 |
@server_action('shutdown') |
... | ... | |
170 | 86 |
# Error Response Codes: serviceUnavailable (503), |
171 | 87 |
# itemNotFound (404) |
172 | 88 |
|
173 |
backend.start_action(vm, 'STOP') |
|
174 |
rapi.ShutdownInstance(vm.backend_id) |
|
89 |
if args: |
|
90 |
raise BadRequest('Malformed Request.') |
|
91 |
shutdown_instance(vm) |
|
175 | 92 |
return HttpResponse(status=202) |
176 | 93 |
|
177 | 94 |
@server_action('rebuild') |
... | ... | |
187 | 104 |
# serverCapacityUnavailable (503), |
188 | 105 |
# overLimit (413) |
189 | 106 |
|
190 |
raise ServiceUnavailable() |
|
107 |
raise ServiceUnavailable('Rebuild not supported.')
|
|
191 | 108 |
|
192 | 109 |
@server_action('resize') |
193 | 110 |
def resize(request, vm, args): |
... | ... | |
203 | 120 |
# overLimit (413), |
204 | 121 |
# resizeNotAllowed (403) |
205 | 122 |
|
206 |
raise ResizeNotAllowed()
|
|
123 |
raise ServiceUnavailable('Resize not supported.')
|
|
207 | 124 |
|
208 | 125 |
@server_action('confirmResize') |
209 | 126 |
def confirm_resize(request, vm, args): |
... | ... | |
219 | 136 |
# overLimit (413), |
220 | 137 |
# resizeNotAllowed (403) |
221 | 138 |
|
222 |
raise ResizeNotAllowed()
|
|
139 |
raise ServiceUnavailable('Resize not supported.')
|
|
223 | 140 |
|
224 | 141 |
@server_action('revertResize') |
225 | 142 |
def revert_resize(request, vm, args): |
... | ... | |
235 | 152 |
# overLimit (413), |
236 | 153 |
# resizeNotAllowed (403) |
237 | 154 |
|
238 |
raise ResizeNotAllowed() |
|
155 |
raise ServiceUnavailable('Resize not supported.') |
|
156 |
|
|
157 |
@server_action('console') |
|
158 |
def get_console(request, vm, args): |
|
159 |
"""Arrange for an OOB console of the specified type |
|
160 |
|
|
161 |
This method arranges for an OOB console of the specified type. |
|
162 |
Only consoles of type "vnc" are supported for now. |
|
163 |
|
|
164 |
It uses a running instance of vncauthproxy to setup proper |
|
165 |
VNC forwarding with a random password, then returns the necessary |
|
166 |
VNC connection info to the caller. |
|
167 |
""" |
|
168 |
# Normal Response Code: 200 |
|
169 |
# Error Response Codes: computeFault (400, 500), |
|
170 |
# serviceUnavailable (503), |
|
171 |
# unauthorized (401), |
|
172 |
# badRequest (400), |
|
173 |
# badMediaType(415), |
|
174 |
# itemNotFound (404), |
|
175 |
# buildInProgress (409), |
|
176 |
# overLimit (413) |
|
177 |
|
|
178 |
console_type = args.get('type', '') |
|
179 |
if console_type != 'vnc': |
|
180 |
raise BadRequest('Type can only be "vnc".') |
|
181 |
|
|
182 |
# Use RAPI to get VNC console information for this instance |
|
183 |
if get_rsapi_state(vm) != 'ACTIVE': |
|
184 |
raise BadRequest('Server not in ACTIVE state.') |
|
185 |
|
|
186 |
if settings.TEST: |
|
187 |
console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} |
|
188 |
else: |
|
189 |
console_data = get_instance_console(vm) |
|
190 |
|
|
191 |
if console_data['kind'] != 'vnc': |
|
192 |
raise ServiceUnavailable('Could not create a console of requested type.') |
|
193 |
|
|
194 |
# Let vncauthproxy decide on the source port. |
|
195 |
# The alternative: static allocation, e.g. |
|
196 |
# sport = console_data['port'] - 1000 |
|
197 |
sport = 0 |
|
198 |
daddr = console_data['host'] |
|
199 |
dport = console_data['port'] |
|
200 |
password = random_password() |
|
201 |
|
|
202 |
try: |
|
203 |
if settings.TEST: |
|
204 |
fwd = {'source_port': 1234, 'status': 'OK'} |
|
205 |
else: |
|
206 |
fwd = request_vnc_forwarding(sport, daddr, dport, password) |
|
207 |
except Exception: |
|
208 |
raise ServiceUnavailable('Could not allocate VNC console port.') |
|
209 |
|
|
210 |
if fwd['status'] != "OK": |
|
211 |
raise ServiceUnavailable('Could not allocate VNC console.') |
|
212 |
|
|
213 |
console = { |
|
214 |
'type': 'vnc', |
|
215 |
'host': getfqdn(), |
|
216 |
'port': fwd['source_port'], |
|
217 |
'password': password} |
|
218 |
|
|
219 |
if request.serialization == 'xml': |
|
220 |
mimetype = 'application/xml' |
|
221 |
data = render_to_string('console.xml', {'console': console}) |
|
222 |
else: |
|
223 |
mimetype = 'application/json' |
|
224 |
data = json.dumps({'console': console}) |
|
225 |
|
|
226 |
return HttpResponse(data, mimetype=mimetype, status=200) |
/dev/null | ||
---|---|---|
1 |
[ |
|
2 |
{ |
|
3 |
"model": "db.Image", |
|
4 |
"pk": 1, |
|
5 |
"fields": { |
|
6 |
"name": "Debian Unstable", |
|
7 |
"created": "2011-02-06 00:00:00", |
|
8 |
"updated": "2011-02-06 00:00:00", |
|
9 |
"state": "ACTIVE" |
|
10 |
} |
|
11 |
}, |
|
12 |
{ |
|
13 |
"model": "db.Image", |
|
14 |
"pk": 2, |
|
15 |
"fields": { |
|
16 |
"name": "Red Hat Enterprise Linux", |
|
17 |
"created": "2011-02-06 00:00:00", |
|
18 |
"updated": "2011-02-06 00:00:00", |
|
19 |
"state": "ACTIVE" |
|
20 |
} |
|
21 |
}, |
|
22 |
{ |
|
23 |
"model": "db.Image", |
|
24 |
"pk": 3, |
|
25 |
"fields": { |
|
26 |
"name": "Ubuntu 10.10", |
|
27 |
"created": "2011-02-06 00:00:00", |
|
28 |
"updated": "2011-02-06 00:00:00", |
|
29 |
"state": "ACTIVE" |
|
30 |
} |
|
31 |
}, |
|
32 |
|
|
33 |
{ |
|
34 |
"model": "db.Flavor", |
|
35 |
"pk": 1, |
|
36 |
"fields": { |
|
37 |
"cpu": 1, |
|
38 |
"ram": 1024, |
|
39 |
"disk": 20 |
|
40 |
} |
|
41 |
}, |
|
42 |
{ |
|
43 |
"model": "db.Flavor", |
|
44 |
"pk": 2, |
|
45 |
"fields": { |
|
46 |
"cpu": 1, |
|
47 |
"ram": 1024, |
|
48 |
"disk": 40 |
|
49 |
} |
|
50 |
}, |
|
51 |
{ |
|
52 |
"model": "db.Flavor", |
|
53 |
"pk": 3, |
|
54 |
"fields": { |
|
55 |
"cpu": 1, |
|
56 |
"ram": 1024, |
|
57 |
"disk": 80 |
|
58 |
} |
|
59 |
} |
|
60 |
] |
b/api/flavors.py | ||
---|---|---|
7 | 7 |
from django.template.loader import render_to_string |
8 | 8 |
from django.utils import simplejson as json |
9 | 9 |
|
10 |
from synnefo.api.faults import ItemNotFound |
|
11 |
from synnefo.api.util import get_user, get_request_dict, api_method |
|
10 |
from synnefo.api.util import get_flavor, api_method |
|
12 | 11 |
from synnefo.db.models import Flavor |
13 | 12 |
|
14 | 13 |
|
... | ... | |
56 | 55 |
# badRequest (400), |
57 | 56 |
# itemNotFound (404), |
58 | 57 |
# overLimit (413) |
59 |
|
|
60 |
try: |
|
61 |
falvor_id = int(flavor_id) |
|
62 |
flavor = flavor_to_dict(Flavor.objects.get(id=flavor_id)) |
|
63 |
except Flavor.DoesNotExist: |
|
64 |
raise ItemNotFound |
|
58 |
|
|
59 |
flavor = get_flavor(flavor_id) |
|
60 |
flavordict = flavor_to_dict(flavor, detail=True) |
|
65 | 61 |
|
66 | 62 |
if request.serialization == 'xml': |
67 |
data = render_to_string('flavor.xml', {'flavor': flavor}) |
|
63 |
data = render_to_string('flavor.xml', {'flavor': flavordict})
|
|
68 | 64 |
else: |
69 |
data = json.dumps({'flavor': flavor}) |
|
65 |
data = json.dumps({'flavor': flavordict})
|
|
70 | 66 |
|
71 | 67 |
return HttpResponse(data, status=200) |
b/api/images.py | ||
---|---|---|
3 | 3 |
# |
4 | 4 |
|
5 | 5 |
from synnefo.api.common import method_not_allowed |
6 |
from synnefo.api.util import * |
|
7 |
from synnefo.db.models import Image, ImageMetadata, VirtualMachine |
|
6 |
from synnefo.api.faults import BadRequest, Unauthorized |
|
7 |
from synnefo.api.util import (isoformat, isoparse, get_user, get_vm, get_image, get_image_meta, |
|
8 |
get_request_dict, render_metadata, render_meta, api_method) |
|
9 |
from synnefo.db.models import Image, ImageMetadata |
|
8 | 10 |
|
9 | 11 |
from django.conf.urls.defaults import patterns |
10 | 12 |
from django.http import HttpResponse |
... | ... | |
173 | 175 |
|
174 | 176 |
image = get_image(image_id) |
175 | 177 |
if image.owner != get_user(): |
176 |
raise Unauthorized() |
|
178 |
raise Unauthorized('Image does not belong to user.')
|
|
177 | 179 |
image.delete() |
178 | 180 |
return HttpResponse(status=204) |
179 | 181 |
|
b/api/servers.py | ||
---|---|---|
2 | 2 |
# Copyright (c) 2010 Greek Research and Technology Network |
3 | 3 |
# |
4 | 4 |
|
5 |
from django.conf import settings |
|
5 |
import logging |
|
6 |
|
|
6 | 7 |
from django.conf.urls.defaults import patterns |
7 | 8 |
from django.http import HttpResponse |
8 | 9 |
from django.template.loader import render_to_string |
... | ... | |
10 | 11 |
|
11 | 12 |
from synnefo.api.actions import server_actions |
12 | 13 |
from synnefo.api.common import method_not_allowed |
13 |
from synnefo.api.faults import BadRequest, ItemNotFound |
|
14 |
from synnefo.api.util import * |
|
15 |
from synnefo.db.models import Image, Flavor, VirtualMachine, VirtualMachineMetadata |
|
14 |
from synnefo.api.faults import BadRequest, ItemNotFound, ServiceUnavailable |
|
15 |
from synnefo.api.util import (isoformat, isoparse, random_password, |
|
16 |
get_user, get_vm, get_vm_meta, get_image, get_flavor, |
|
17 |
get_request_dict, render_metadata, render_meta, api_method) |
|
18 |
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata |
|
19 |
from synnefo.logic.backend import create_instance, delete_instance |
|
16 | 20 |
from synnefo.logic.utils import get_rsapi_state |
17 |
from synnefo.util.rapi import GanetiRapiClient, GanetiApiError |
|
18 |
from synnefo.logic import backend |
|
19 |
|
|
20 |
import logging |
|
21 |
|
|
21 |
from synnefo.util.rapi import GanetiApiError |
|
22 | 22 |
|
23 |
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO) |
|
24 | 23 |
|
25 | 24 |
urlpatterns = patterns('synnefo.api.servers', |
26 | 25 |
(r'^(?:/|.json|.xml)?$', 'demux'), |
... | ... | |
153 | 152 |
name = server['name'] |
154 | 153 |
metadata = server.get('metadata', {}) |
155 | 154 |
assert isinstance(metadata, dict) |
156 |
sourceimage = Image.objects.get(id=server['imageRef'])
|
|
157 |
flavor = Flavor.objects.get(id=server['flavorRef'])
|
|
155 |
image_id = server['imageRef']
|
|
156 |
flavor_id = server['flavorRef']
|
|
158 | 157 |
except (KeyError, AssertionError): |
159 | 158 |
raise BadRequest('Malformed request.') |
160 |
except Image.DoesNotExist: |
|
161 |
raise ItemNotFound |
|
162 |
except Flavor.DoesNotExist: |
|
163 |
raise ItemNotFound |
|
164 | 159 |
|
165 |
vm = VirtualMachine( |
|
160 |
image = get_image(image_id) |
|
161 |
flavor = get_flavor(flavor_id) |
|
162 |
|
|
163 |
# We must save the VM instance now, so that it gets a valid vm.backend_id. |
|
164 |
vm = VirtualMachine.objects.create( |
|
166 | 165 |
name=name, |
167 | 166 |
owner=get_user(), |
168 |
sourceimage=sourceimage,
|
|
167 |
sourceimage=image, |
|
169 | 168 |
ipfour='0.0.0.0', |
170 | 169 |
ipsix='::1', |
171 | 170 |
flavor=flavor) |
172 |
|
|
173 |
# Pick a random password for the VM. |
|
174 |
# FIXME: This must be passed to the Ganeti OS provider via CreateInstance() |
|
175 |
passwd = random_password() |
|
176 |
|
|
177 |
# We *must* save the VM instance now, |
|
178 |
# so that it gets a vm.id and vm.backend_id is valid. |
|
179 |
vm.save() |
|
171 |
|
|
172 |
password = random_password() |
|
180 | 173 |
|
181 | 174 |
try: |
182 |
jobId = rapi.CreateInstance( |
|
183 |
mode='create', |
|
184 |
name=vm.backend_id, |
|
185 |
disk_template='plain', |
|
186 |
disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now |
|
187 |
nics=[{}], |
|
188 |
os='debootstrap+default', #TODO: select OS from imageRef |
|
189 |
ip_check=False, |
|
190 |
name_check=False, |
|
191 |
pnode=rapi.GetNodes()[0], #TODO: verify if this is necessary |
|
192 |
dry_run=settings.TEST, |
|
193 |
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram)) |
|
175 |
create_instance(vm, flavor, password) |
|
194 | 176 |
except GanetiApiError: |
195 | 177 |
vm.delete() |
196 | 178 |
raise ServiceUnavailable('Could not create server.') |
... | ... | |
198 | 180 |
for key, val in metadata.items(): |
199 | 181 |
VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) |
200 | 182 |
|
201 |
logging.info('created vm with %s cpus, %s ram and %s storage' % (flavor.cpu, flavor.ram, flavor.disk)) |
|
183 |
logging.info('created vm with %s cpus, %s ram and %s storage', |
|
184 |
flavor.cpu, flavor.ram, flavor.disk) |
|
202 | 185 |
|
203 | 186 |
server = vm_to_dict(vm, detail=True) |
204 | 187 |
server['status'] = 'BUILD' |
205 |
server['adminPass'] = passwd |
|
188 |
server['adminPass'] = password
|
|
206 | 189 |
return render_server(request, server, status=202) |
207 | 190 |
|
208 | 191 |
@api_method('GET') |
... | ... | |
235 | 218 |
|
236 | 219 |
try: |
237 | 220 |
name = req['server']['name'] |
238 |
except KeyError:
|
|
221 |
except (TypeError, KeyError):
|
|
239 | 222 |
raise BadRequest('Malformed request.') |
240 | 223 |
|
241 | 224 |
vm = get_vm(server_id) |
... | ... | |
256 | 239 |
# overLimit (413) |
257 | 240 |
|
258 | 241 |
vm = get_vm(server_id) |
259 |
backend.start_action(vm, 'DESTROY') |
|
260 |
rapi.DeleteInstance(vm.backend_id) |
|
242 |
delete_instance(vm) |
|
261 | 243 |
return HttpResponse(status=204) |
262 | 244 |
|
263 | 245 |
@api_method('POST') |
b/api/templates/console.xml | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
<console xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" type="{{ console.type }}" host="{{ console.host }}" port="{{ console.port }}" password="{{ console.password }}"> |
|
3 |
</console> |
/dev/null | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
<vnc xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" host="{{ vnc.host }}" port="{{ vnc.port }}" password="{{ vnc.password }}"> |
|
3 |
</vnc> |
b/api/tests.py | ||
---|---|---|
721 | 721 |
response = self.client.post(path, data, content_type='application/json') |
722 | 722 |
self.assertEqual(response.status_code, 200) |
723 | 723 |
reply = json.loads(response.content) |
724 |
self.assertEqual(reply.keys(), ['vnc']) |
|
725 |
self.assertEqual(set(reply['vnc'].keys()), set(['host', 'port', 'password'])) |
|
726 |
|
|
724 |
self.assertEqual(reply.keys(), ['console']) |
|
725 |
console = reply['console'] |
|
726 |
self.assertEqual(console['type'], 'vnc') |
|
727 |
self.assertEqual(set(console.keys()), set(['type', 'host', 'port', 'password'])) |
b/api/util.py | ||
---|---|---|
10 | 10 |
from traceback import format_exc |
11 | 11 |
from wsgiref.handlers import format_date_time |
12 | 12 |
|
13 |
import datetime |
|
14 |
import dateutil.parser |
|
15 |
import logging |
|
16 |
|
|
13 | 17 |
from django.conf import settings |
14 | 18 |
from django.http import HttpResponse |
15 | 19 |
from django.template.loader import render_to_string |
16 | 20 |
from django.utils import simplejson as json |
17 | 21 |
|
18 |
from synnefo.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable |
|
19 |
from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata |
|
20 |
|
|
21 |
import datetime |
|
22 |
import dateutil.parser |
|
23 |
import logging |
|
22 |
from synnefo.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable, Unauthorized |
|
23 |
from synnefo.db.models import (SynnefoUser, Flavor, Image, ImageMetadata, |
|
24 |
VirtualMachine, VirtualMachineMetadata) |
|
24 | 25 |
|
25 | 26 |
|
26 | 27 |
class UTC(tzinfo): |
... | ... | |
35 | 36 |
|
36 | 37 |
|
37 | 38 |
def isoformat(d): |
38 |
"""Return an ISO8601 date string that includes a timezon.""" |
|
39 |
"""Return an ISO8601 date string that includes a timezone."""
|
|
39 | 40 |
|
40 | 41 |
return d.replace(tzinfo=UTC()).isoformat() |
41 | 42 |
|
... | ... | |
70 | 71 |
try: |
71 | 72 |
return SynnefoUser.objects.all()[0] |
72 | 73 |
except IndexError: |
73 |
raise Unauthorized |
|
74 |
raise Unauthorized('No users found.')
|
|
74 | 75 |
|
75 | 76 |
def get_vm(server_id): |
76 | 77 |
"""Return a VirtualMachine instance or raise ItemNotFound.""" |
... | ... | |
110 | 111 |
except ImageMetadata.DoesNotExist: |
111 | 112 |
raise ItemNotFound('Metadata key not found.') |
112 | 113 |
|
114 |
def get_flavor(flavor_id): |
|
115 |
"""Return a Flavor instance or raise ItemNotFound.""" |
|
116 |
|
|
117 |
try: |
|
118 |
flavor_id = int(flavor_id) |
|
119 |
return Flavor.objects.get(id=flavor_id) |
|
120 |
except Flavor.DoesNotExist: |
|
121 |
raise ItemNotFound('Flavor not found.') |
|
113 | 122 |
|
114 | 123 |
def get_request_dict(request): |
115 | 124 |
"""Returns data sent by the client as a python dict.""" |
... | ... | |
168 | 177 |
def request_serialization(request, atom_allowed=False): |
169 | 178 |
"""Return the serialization format requested. |
170 | 179 |
|
171 |
Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True.
|
|
180 |
Valid formats are 'json', 'xml' and 'atom' if `atom_allowed` is True. |
|
172 | 181 |
""" |
173 | 182 |
|
174 | 183 |
path = request.path |
... | ... | |
209 | 218 |
except Fault, fault: |
210 | 219 |
return render_fault(request, fault) |
211 | 220 |
except BaseException, e: |
212 |
logging.exception('Unexpected error: %s' % e)
|
|
213 |
fault = ServiceUnavailable('Unexpected error') |
|
221 |
logging.exception('Unexpected error: %s', e)
|
|
222 |
fault = ServiceUnavailable('Unexpected error.')
|
|
214 | 223 |
return render_fault(request, fault) |
215 | 224 |
return wrapper |
216 | 225 |
return decorator |
b/logic/backend.py | ||
---|---|---|
4 | 4 |
# Copyright 2010 Greek Research and Technology Network |
5 | 5 |
# |
6 | 6 |
|
7 |
from django.conf import settings |
|
7 | 8 |
from synnefo.db.models import VirtualMachine |
8 | 9 |
from synnefo.logic import utils |
10 |
from synnefo.util.rapi import GanetiRapiClient |
|
11 |
|
|
12 |
|
|
13 |
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO) |
|
14 |
|
|
9 | 15 |
|
10 | 16 |
def process_backend_msg(vm, jobid, opcode, status, logmsg): |
11 | 17 |
"""Process a job progress notification from the backend. |
... | ... | |
61 | 67 |
elif action == "START": |
62 | 68 |
vm.suspended = False |
63 | 69 |
vm.save() |
70 |
|
|
71 |
def create_instance(vm, flavor, password): |
|
72 |
# FIXME: `password` must be passed to the Ganeti OS provider via CreateInstance() |
|
73 |
return rapi.CreateInstance( |
|
74 |
mode='create', |
|
75 |
name='verigak-8', |
|
76 |
disk_template='plain', |
|
77 |
disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now |
|
78 |
nics=[{}], |
|
79 |
os='debootstrap+default', #TODO: select OS from imageRef |
|
80 |
ip_check=False, |
|
81 |
name_check=False, |
|
82 |
pnode=rapi.GetNodes()[0], #TODO: verify if this is necessary |
|
83 |
dry_run=settings.TEST, |
|
84 |
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram)) |
|
85 |
|
|
86 |
def delete_instance(vm): |
|
87 |
start_action(vm, 'DESTROY') |
|
88 |
rapi.DeleteInstance(vm.backend_id) |
|
89 |
|
|
90 |
def reboot_instance(vm, reboot_type): |
|
91 |
assert reboot_type in ('soft', 'hard') |
|
92 |
rapi.RebootInstance(vm.backend_id, reboot_type) |
|
93 |
|
|
94 |
def startup_instance(vm): |
|
95 |
start_action(vm, 'START') |
|
96 |
rapi.StartupInstance(vm.backend_id) |
|
97 |
|
|
98 |
def shutdown_instance(vm): |
|
99 |
start_action(vm, 'STOP') |
|
100 |
rapi.ShutdownInstance(vm.backend_id) |
|
101 |
|
|
102 |
def get_instance_console(vm): |
|
103 |
return rapi.GetInstanceConsole(vm.backend_id) |
b/tools/cloud | ||
---|---|---|
244 | 244 |
|
245 | 245 |
def execute(self, server_id): |
246 | 246 |
path = '/api/%s/servers/%d/action' % (self.api, int(server_id)) |
247 |
body = json.dumps({'console':{'type':'vnc'}})
|
|
247 |
body = json.dumps({'console': {'type': 'vnc'}})
|
|
248 | 248 |
reply = self.http_cmd('POST', path, body, 200) |
249 |
print_dict(reply['vnc'])
|
|
249 |
print_dict(reply['console'])
|
|
250 | 250 |
|
251 | 251 |
|
252 | 252 |
@command_name('lsaddr') |
Also available in: Unified diff