Revision f533f224
b/api/actions.py | ||
---|---|---|
12 | 12 |
from synnefo.api.faults import BadRequest, ServiceUnavailable |
13 | 13 |
from synnefo.api.util import random_password, get_vm |
14 | 14 |
from synnefo.util.vapclient import request_forwarding as request_vnc_forwarding |
15 |
from synnefo.logic.backend import (reboot_instance, startup_instance, shutdown_instance, |
|
16 |
get_instance_console) |
|
15 |
from synnefo.logic import backend |
|
17 | 16 |
from synnefo.logic.utils import get_rsapi_state |
18 | 17 |
|
19 | 18 |
|
... | ... | |
76 | 75 |
reboot_type = args.get('type', '') |
77 | 76 |
if reboot_type not in ('SOFT', 'HARD'): |
78 | 77 |
raise BadRequest('Malformed Request.') |
79 |
reboot_instance(vm, reboot_type.lower()) |
|
78 |
backend.reboot_instance(vm, reboot_type.lower())
|
|
80 | 79 |
return HttpResponse(status=202) |
81 | 80 |
|
82 | 81 |
@server_action('start') |
... | ... | |
87 | 86 |
|
88 | 87 |
if args: |
89 | 88 |
raise BadRequest('Malformed Request.') |
90 |
startup_instance(vm) |
|
89 |
backend.startup_instance(vm)
|
|
91 | 90 |
return HttpResponse(status=202) |
92 | 91 |
|
93 | 92 |
@server_action('shutdown') |
... | ... | |
98 | 97 |
|
99 | 98 |
if args: |
100 | 99 |
raise BadRequest('Malformed Request.') |
101 |
shutdown_instance(vm) |
|
100 |
backend.shutdown_instance(vm)
|
|
102 | 101 |
return HttpResponse(status=202) |
103 | 102 |
|
104 | 103 |
@server_action('rebuild') |
... | ... | |
196 | 195 |
if settings.TEST: |
197 | 196 |
console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} |
198 | 197 |
else: |
199 |
console_data = get_instance_console(vm) |
|
198 |
console_data = backend.get_instance_console(vm)
|
|
200 | 199 |
|
201 | 200 |
if console_data['kind'] != 'vnc': |
202 |
raise ServiceUnavailable('Could not create a console of requested type.') |
|
201 |
message = 'Could not create a console of requested type.' |
|
202 |
raise ServiceUnavailable(message) |
|
203 | 203 |
|
204 | 204 |
# Let vncauthproxy decide on the source port. |
205 | 205 |
# The alternative: static allocation, e.g. |
... | ... | |
251 | 251 |
if not server_id: |
252 | 252 |
raise BadRequest('Malformed Request.') |
253 | 253 |
vm = get_vm(server_id, request.user) |
254 |
net.machines.add(vm) |
|
254 |
backend.connect_to_network(vm, net) |
|
255 |
vm.save() |
|
255 | 256 |
net.save() |
256 | 257 |
return HttpResponse(status=202) |
257 | 258 |
|
... | ... | |
270 | 271 |
if not server_id: |
271 | 272 |
raise BadRequest('Malformed Request.') |
272 | 273 |
vm = get_vm(server_id, request.user) |
273 |
net.machines.remove(vm) |
|
274 |
backend.disconnect_from_network(vm, net) |
|
275 |
vm.save() |
|
274 | 276 |
net.save() |
275 | 277 |
return HttpResponse(status=202) |
b/api/faults.py | ||
---|---|---|
28 | 28 |
class BuildInProgress(Fault): |
29 | 29 |
code = 409 |
30 | 30 |
|
31 |
class OverLimit(Fault): |
|
32 |
code = 413 |
|
33 |
|
|
31 | 34 |
class ServiceUnavailable(Fault): |
32 | 35 |
code = 503 |
b/api/fixtures/api_test_data.json | ||
---|---|---|
264 | 264 |
"charged": "2011-02-06 00:00:00", |
265 | 265 |
"sourceimage": 1, |
266 | 266 |
"hostid": "HAL-9000", |
267 |
"ipfour": "192.168.2.1", |
|
268 |
"ipsix": "::1", |
|
269 | 267 |
"flavor": 1, |
270 | 268 |
"operstate": "STOPPED" |
271 | 269 |
} |
... | ... | |
281 | 279 |
"charged": "2011-02-10 00:00:00", |
282 | 280 |
"sourceimage": 1, |
283 | 281 |
"hostid": "HAL-9000", |
284 |
"ipfour": "192.168.2.2", |
|
285 |
"ipsix": "::2", |
|
286 | 282 |
"flavor": 1, |
287 | 283 |
"operstate": "BUILD" |
288 | 284 |
} |
... | ... | |
298 | 294 |
"charged": "2011-02-10 00:00:00", |
299 | 295 |
"sourceimage": 1, |
300 | 296 |
"hostid": "HAL-9000", |
301 |
"ipfour": "192.168.2.3", |
|
302 |
"ipsix": "::3", |
|
303 | 297 |
"flavor": 1, |
304 | 298 |
"operstate": "STARTED" |
305 | 299 |
} |
... | ... | |
315 | 309 |
"charged": "2011-02-10 00:00:00", |
316 | 310 |
"sourceimage": 1, |
317 | 311 |
"hostid": "HAL-9000", |
318 |
"ipfour": "192.168.2.4", |
|
319 |
"ipsix": "::4", |
|
320 | 312 |
"flavor": 1, |
321 | 313 |
"operstate": "STARTED" |
322 | 314 |
} |
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.util import get_flavor, api_method
|
|
10 |
from synnefo.api import util
|
|
11 | 11 |
from synnefo.db.models import Flavor |
12 | 12 |
|
13 | 13 |
|
... | ... | |
27 | 27 |
return d |
28 | 28 |
|
29 | 29 |
|
30 |
@api_method('GET') |
|
30 |
@util.api_method('GET')
|
|
31 | 31 |
def list_flavors(request, detail=False): |
32 | 32 |
# Normal Response Codes: 200, 203 |
33 | 33 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
40 | 40 |
flavors = [flavor_to_dict(flavor, detail) for flavor in all_flavors] |
41 | 41 |
|
42 | 42 |
if request.serialization == 'xml': |
43 |
data = render_to_string('list_flavors.xml', {'flavors': flavors, 'detail': detail}) |
|
43 |
data = render_to_string('list_flavors.xml', { |
|
44 |
'flavors': flavors, |
|
45 |
'detail': detail}) |
|
44 | 46 |
else: |
45 | 47 |
data = json.dumps({'flavors': {'values': flavors}}) |
46 | 48 |
|
47 | 49 |
return HttpResponse(data, status=200) |
48 | 50 |
|
49 |
@api_method('GET') |
|
51 |
@util.api_method('GET')
|
|
50 | 52 |
def get_flavor_details(request, flavor_id): |
51 | 53 |
# Normal Response Codes: 200, 203 |
52 | 54 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
56 | 58 |
# itemNotFound (404), |
57 | 59 |
# overLimit (413) |
58 | 60 |
|
59 |
flavor = get_flavor(flavor_id) |
|
61 |
flavor = util.get_flavor(flavor_id)
|
|
60 | 62 |
flavordict = flavor_to_dict(flavor, detail=True) |
61 | 63 |
|
62 | 64 |
if request.serialization == 'xml': |
b/api/images.py | ||
---|---|---|
2 | 2 |
# Copyright (c) 2010 Greek Research and Technology Network |
3 | 3 |
# |
4 | 4 |
|
5 |
from synnefo.api.common import method_not_allowed |
|
6 |
from synnefo.api.faults import BadRequest |
|
7 |
from synnefo.api.util import (isoformat, isoparse, 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 |
|
10 |
|
|
11 | 5 |
from django.conf.urls.defaults import patterns |
12 | 6 |
from django.http import HttpResponse |
13 | 7 |
from django.template.loader import render_to_string |
14 | 8 |
from django.utils import simplejson as json |
15 | 9 |
|
10 |
from synnefo.api import util |
|
11 |
from synnefo.api.common import method_not_allowed |
|
12 |
from synnefo.api.faults import BadRequest |
|
13 |
from synnefo.db.models import Image, ImageMetadata |
|
14 |
|
|
16 | 15 |
|
17 | 16 |
urlpatterns = patterns('synnefo.api.images', |
18 | 17 |
(r'^(?:/|.json|.xml)?$', 'demux'), |
... | ... | |
60 | 59 |
def image_to_dict(image, detail=True): |
61 | 60 |
d = {'id': image.id, 'name': image.name} |
62 | 61 |
if detail: |
63 |
d['updated'] = isoformat(image.updated) |
|
64 |
d['created'] = isoformat(image.created) |
|
62 |
d['updated'] = util.isoformat(image.updated)
|
|
63 |
d['created'] = util.isoformat(image.created)
|
|
65 | 64 |
d['status'] = image.state |
66 | 65 |
d['progress'] = 100 if image.state == 'ACTIVE' else 0 |
67 | 66 |
if image.sourcevm: |
... | ... | |
81 | 80 |
return dict((meta.meta_key, meta.meta_value) for meta in image_meta) |
82 | 81 |
|
83 | 82 |
|
84 |
@api_method('GET') |
|
83 |
@util.api_method('GET')
|
|
85 | 84 |
def list_images(request, detail=False): |
86 | 85 |
# Normal Response Codes: 200, 203 |
87 | 86 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
90 | 89 |
# badRequest (400), |
91 | 90 |
# overLimit (413) |
92 | 91 |
|
93 |
since = isoparse(request.GET.get('changes-since')) |
|
92 |
since = util.isoparse(request.GET.get('changes-since'))
|
|
94 | 93 |
|
95 | 94 |
if since: |
96 |
avail_images = Image.objects.filter(owner=request.user, updated__gte=since) |
|
95 |
avail_images = Image.objects.filter(owner=request.user, |
|
96 |
updated__gte=since) |
|
97 | 97 |
if not avail_images: |
98 | 98 |
return HttpResponse(status=304) |
99 | 99 |
else: |
... | ... | |
102 | 102 |
images = [image_to_dict(image, detail) for image in avail_images] |
103 | 103 |
|
104 | 104 |
if request.serialization == 'xml': |
105 |
data = render_to_string('list_images.xml', {'images': images, 'detail': detail}) |
|
105 |
data = render_to_string('list_images.xml', { |
|
106 |
'images': images, |
|
107 |
'detail': detail}) |
|
106 | 108 |
else: |
107 | 109 |
data = json.dumps({'images': {'values': images}}) |
108 | 110 |
|
109 | 111 |
return HttpResponse(data, status=200) |
110 | 112 |
|
111 |
@api_method('POST') |
|
113 |
@util.api_method('POST')
|
|
112 | 114 |
def create_image(request): |
113 | 115 |
# Normal Response Code: 202 |
114 | 116 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
123 | 125 |
# backupOrResizeInProgress (409), |
124 | 126 |
# overLimit (413) |
125 | 127 |
|
126 |
req = get_request_dict(request) |
|
128 |
req = util.get_request_dict(request)
|
|
127 | 129 |
|
128 | 130 |
try: |
129 | 131 |
d = req['image'] |
... | ... | |
133 | 135 |
raise BadRequest('Malformed request.') |
134 | 136 |
|
135 | 137 |
owner = request.user |
136 |
vm = get_vm(server_id, owner) |
|
138 |
vm = util.get_vm(server_id, owner)
|
|
137 | 139 |
image = Image.objects.create(name=name, owner=owner, sourcevm=vm) |
138 | 140 |
|
139 | 141 |
imagedict = image_to_dict(image) |
... | ... | |
144 | 146 |
|
145 | 147 |
return HttpResponse(data, status=202) |
146 | 148 |
|
147 |
@api_method('GET') |
|
149 |
@util.api_method('GET')
|
|
148 | 150 |
def get_image_details(request, image_id): |
149 | 151 |
# Normal Response Codes: 200, 203 |
150 | 152 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
154 | 156 |
# itemNotFound (404), |
155 | 157 |
# overLimit (413) |
156 | 158 |
|
157 |
image = get_image(image_id, request.user) |
|
159 |
image = util.get_image(image_id, request.user)
|
|
158 | 160 |
imagedict = image_to_dict(image) |
159 | 161 |
|
160 | 162 |
if request.serialization == 'xml': |
... | ... | |
164 | 166 |
|
165 | 167 |
return HttpResponse(data, status=200) |
166 | 168 |
|
167 |
@api_method('DELETE') |
|
169 |
@util.api_method('DELETE')
|
|
168 | 170 |
def delete_image(request, image_id): |
169 | 171 |
# Normal Response Code: 204 |
170 | 172 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
173 | 175 |
# itemNotFound (404), |
174 | 176 |
# overLimit (413) |
175 | 177 |
|
176 |
image = get_image(image_id, request.user) |
|
178 |
image = util.get_image(image_id, request.user)
|
|
177 | 179 |
image.delete() |
178 | 180 |
return HttpResponse(status=204) |
179 | 181 |
|
180 |
@api_method('GET') |
|
182 |
@util.api_method('GET')
|
|
181 | 183 |
def list_metadata(request, image_id): |
182 | 184 |
# Normal Response Codes: 200, 203 |
183 | 185 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
186 | 188 |
# badRequest (400), |
187 | 189 |
# overLimit (413) |
188 | 190 |
|
189 |
image = get_image(image_id, request.user) |
|
191 |
image = util.get_image(image_id, request.user)
|
|
190 | 192 |
metadata = metadata_to_dict(image) |
191 |
return render_metadata(request, metadata, use_values=True, status=200) |
|
193 |
return util.render_metadata(request, metadata, use_values=True, status=200)
|
|
192 | 194 |
|
193 |
@api_method('POST') |
|
195 |
@util.api_method('POST')
|
|
194 | 196 |
def update_metadata(request, image_id): |
195 | 197 |
# Normal Response Code: 201 |
196 | 198 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
201 | 203 |
# badMediaType(415), |
202 | 204 |
# overLimit (413) |
203 | 205 |
|
204 |
image = get_image(image_id, request.user) |
|
205 |
req = get_request_dict(request) |
|
206 |
image = util.get_image(image_id, request.user)
|
|
207 |
req = util.get_request_dict(request)
|
|
206 | 208 |
try: |
207 | 209 |
metadata = req['metadata'] |
208 | 210 |
assert isinstance(metadata, dict) |
... | ... | |
219 | 221 |
updated[key] = val |
220 | 222 |
except ImageMetadata.DoesNotExist: |
221 | 223 |
pass # Ignore non-existent metadata |
224 |
|
|
225 |
if updated: |
|
226 |
image.save() |
|
227 |
|
|
228 |
return util.render_metadata(request, updated, status=201) |
|
222 | 229 |
|
223 |
return render_metadata(request, updated, status=201) |
|
224 |
|
|
225 |
@api_method('GET') |
|
230 |
@util.api_method('GET') |
|
226 | 231 |
def get_metadata_item(request, image_id, key): |
227 | 232 |
# Normal Response Codes: 200, 203 |
228 | 233 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
232 | 237 |
# badRequest (400), |
233 | 238 |
# overLimit (413) |
234 | 239 |
|
235 |
image = get_image(image_id, request.user) |
|
236 |
meta = get_image_meta(image, key) |
|
237 |
return render_meta(request, meta, status=200) |
|
240 |
image = util.get_image(image_id, request.user)
|
|
241 |
meta = util.get_image_meta(image, key)
|
|
242 |
return util.render_meta(request, meta, status=200)
|
|
238 | 243 |
|
239 |
@api_method('PUT') |
|
244 |
@util.api_method('PUT')
|
|
240 | 245 |
def create_metadata_item(request, image_id, key): |
241 | 246 |
# Normal Response Code: 201 |
242 | 247 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
248 | 253 |
# badMediaType(415), |
249 | 254 |
# overLimit (413) |
250 | 255 |
|
251 |
image = get_image(image_id, request.user) |
|
252 |
req = get_request_dict(request) |
|
256 |
image = util.get_image(image_id, request.user)
|
|
257 |
req = util.get_request_dict(request)
|
|
253 | 258 |
try: |
254 | 259 |
metadict = req['meta'] |
255 | 260 |
assert isinstance(metadict, dict) |
... | ... | |
257 | 262 |
assert key in metadict |
258 | 263 |
except (KeyError, AssertionError): |
259 | 264 |
raise BadRequest('Malformed request.') |
260 |
|
|
261 |
meta, created = ImageMetadata.objects.get_or_create(meta_key=key, image=image) |
|
265 |
|
|
266 |
meta, created = ImageMetadata.objects.get_or_create( |
|
267 |
meta_key=key, |
|
268 |
image=image) |
|
269 |
|
|
262 | 270 |
meta.meta_value = metadict[key] |
263 | 271 |
meta.save() |
264 |
return render_meta(request, meta, status=201) |
|
272 |
image.save() |
|
273 |
return util.render_meta(request, meta, status=201) |
|
265 | 274 |
|
266 |
@api_method('DELETE') |
|
275 |
@util.api_method('DELETE')
|
|
267 | 276 |
def delete_metadata_item(request, image_id, key): |
268 | 277 |
# Normal Response Code: 204 |
269 | 278 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
275 | 284 |
# badMediaType(415), |
276 | 285 |
# overLimit (413), |
277 | 286 |
|
278 |
image = get_image(image_id, request.user) |
|
279 |
meta = get_image_meta(image, key) |
|
287 |
image = util.get_image(image_id, request.user)
|
|
288 |
meta = util.get_image_meta(image, key)
|
|
280 | 289 |
meta.delete() |
290 |
image.save() |
|
281 | 291 |
return HttpResponse(status=204) |
b/api/networks.py | ||
---|---|---|
1 |
from synnefo.api.actions import network_actions |
|
2 |
from synnefo.api.common import method_not_allowed |
|
3 |
from synnefo.api.faults import BadRequest, Unauthorized |
|
4 |
from synnefo.api.util import (isoformat, isoparse, get_network, |
|
5 |
get_request_dict, api_method) |
|
6 |
from synnefo.db.models import Network |
|
7 |
|
|
8 | 1 |
from django.conf.urls.defaults import patterns |
2 |
from django.db.models import Q |
|
9 | 3 |
from django.http import HttpResponse |
4 |
from django.template.loader import render_to_string |
|
10 | 5 |
from django.utils import simplejson as json |
11 | 6 |
|
7 |
from synnefo.api import util |
|
8 |
from synnefo.api.actions import network_actions |
|
9 |
from synnefo.api.common import method_not_allowed |
|
10 |
from synnefo.api.faults import BadRequest, OverLimit, Unauthorized |
|
11 |
from synnefo.db.models import Network |
|
12 |
from synnefo.logic import backend |
|
13 |
|
|
12 | 14 |
|
13 | 15 |
urlpatterns = patterns('synnefo.api.networks', |
14 | 16 |
(r'^(?:/|.json|.xml)?$', 'demux'), |
... | ... | |
38 | 40 |
|
39 | 41 |
|
40 | 42 |
def network_to_dict(network, detail=True): |
41 |
d = {'id': network.id, 'name': network.name} |
|
43 |
network_id = str(network.id) if not network.public else 'public' |
|
44 |
d = {'id': network_id, 'name': network.name} |
|
42 | 45 |
if detail: |
46 |
d['updated'] = util.isoformat(network.updated) |
|
47 |
d['created'] = util.isoformat(network.created) |
|
43 | 48 |
d['servers'] = {'values': [vm.id for vm in network.machines.all()]} |
49 |
d['status'] = network.state |
|
44 | 50 |
return d |
45 | 51 |
|
46 | 52 |
def render_network(request, networkdict, status=200): |
... | ... | |
51 | 57 |
return HttpResponse(data, status=status) |
52 | 58 |
|
53 | 59 |
|
54 |
@api_method('GET') |
|
60 |
@util.api_method('GET')
|
|
55 | 61 |
def list_networks(request, detail=False): |
56 | 62 |
# Normal Response Codes: 200, 203 |
57 | 63 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
59 | 65 |
# unauthorized (401), |
60 | 66 |
# badRequest (400), |
61 | 67 |
# overLimit (413) |
62 |
|
|
63 |
since = isoparse(request.GET.get('changes-since')) |
|
64 |
|
|
68 |
|
|
69 |
owner = request.user |
|
70 |
since = util.isoparse(request.GET.get('changes-since')) |
|
71 |
user_networks = Network.objects.filter(Q(owner=owner) | Q(public=True)) |
|
72 |
|
|
65 | 73 |
if since: |
66 |
user_networks = Network.objects.filter(owner=request.user, updated__gte=since)
|
|
74 |
user_networks = user_networks.filter(updated__gte=since)
|
|
67 | 75 |
if not user_networks: |
68 | 76 |
return HttpResponse(status=304) |
69 | 77 |
else: |
70 |
user_networks = Network.objects.filter(owner=request.user)
|
|
71 |
|
|
78 |
user_networks = user_networks.filter(state='ACTIVE')
|
|
79 |
|
|
72 | 80 |
networks = [network_to_dict(network, detail) for network in user_networks] |
73 | 81 |
|
74 | 82 |
if request.serialization == 'xml': |
75 |
data = render_to_string('list_networks.xml', {'networks': networks, 'detail': detail}) |
|
83 |
data = render_to_string('list_networks.xml', { |
|
84 |
'networks': networks, |
|
85 |
'detail': detail}) |
|
76 | 86 |
else: |
77 | 87 |
data = json.dumps({'networks': {'values': networks}}) |
78 | 88 |
|
79 | 89 |
return HttpResponse(data, status=200) |
80 | 90 |
|
81 |
@api_method('POST') |
|
91 |
@util.api_method('POST')
|
|
82 | 92 |
def create_network(request): |
83 | 93 |
# Normal Response Code: 202 |
84 | 94 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
88 | 98 |
# badRequest (400), |
89 | 99 |
# overLimit (413) |
90 | 100 |
|
91 |
req = get_request_dict(request) |
|
101 |
req = util.get_request_dict(request)
|
|
92 | 102 |
|
93 | 103 |
try: |
94 | 104 |
d = req['network'] |
95 | 105 |
name = d['name'] |
96 | 106 |
except (KeyError, ValueError): |
97 | 107 |
raise BadRequest('Malformed request.') |
98 |
|
|
99 |
network = Network.objects.create(name=name, owner=request.user) |
|
108 |
|
|
109 |
network = backend.create_network(name, request.user) |
|
110 |
if not network: |
|
111 |
raise OverLimit('Maximum number of networks reached.') |
|
112 |
|
|
100 | 113 |
networkdict = network_to_dict(network) |
101 | 114 |
return render_network(request, networkdict, status=202) |
102 | 115 |
|
103 |
@api_method('GET') |
|
116 |
@util.api_method('GET')
|
|
104 | 117 |
def get_network_details(request, network_id): |
105 | 118 |
# Normal Response Codes: 200, 203 |
106 | 119 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
110 | 123 |
# itemNotFound (404), |
111 | 124 |
# overLimit (413) |
112 | 125 |
|
113 |
net = get_network(network_id, request.user) |
|
126 |
net = util.get_network(network_id, request.user)
|
|
114 | 127 |
netdict = network_to_dict(net) |
115 | 128 |
return render_network(request, netdict) |
116 | 129 |
|
117 |
@api_method('PUT') |
|
130 |
@util.api_method('PUT')
|
|
118 | 131 |
def update_network_name(request, network_id): |
119 | 132 |
# Normal Response Code: 204 |
120 | 133 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
125 | 138 |
# itemNotFound (404), |
126 | 139 |
# overLimit (413) |
127 | 140 |
|
128 |
req = get_request_dict(request) |
|
141 |
req = util.get_request_dict(request)
|
|
129 | 142 |
|
130 | 143 |
try: |
131 | 144 |
name = req['network']['name'] |
132 | 145 |
except (TypeError, KeyError): |
133 | 146 |
raise BadRequest('Malformed request.') |
134 | 147 |
|
135 |
net = get_network(network_id, request.user) |
|
148 |
net = util.get_network(network_id, request.user) |
|
149 |
if net.public: |
|
150 |
raise Unauthorized('Can not rename the public network.') |
|
136 | 151 |
net.name = name |
137 | 152 |
net.save() |
138 | 153 |
return HttpResponse(status=204) |
139 | 154 |
|
140 |
@api_method('DELETE') |
|
155 |
@util.api_method('DELETE')
|
|
141 | 156 |
def delete_network(request, network_id): |
142 | 157 |
# Normal Response Code: 204 |
143 | 158 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
146 | 161 |
# itemNotFound (404), |
147 | 162 |
# unauthorized (401), |
148 | 163 |
# overLimit (413) |
149 |
|
|
150 |
net = get_network(network_id, request.user) |
|
151 |
net.delete() |
|
164 |
|
|
165 |
net = util.get_network(network_id, request.user) |
|
166 |
if net.public: |
|
167 |
raise Unauthorized('Can not delete the public network.') |
|
168 |
backend.delete_network(net) |
|
152 | 169 |
return HttpResponse(status=204) |
153 | 170 |
|
154 |
@api_method('POST') |
|
171 |
@util.api_method('POST')
|
|
155 | 172 |
def network_action(request, network_id): |
156 |
net = get_network(network_id, request.user) |
|
157 |
req = get_request_dict(request) |
|
173 |
net = util.get_network(network_id, request.user) |
|
174 |
if net.public: |
|
175 |
raise Unauthorized('Can not modify the public network.') |
|
176 |
|
|
177 |
req = util.get_request_dict(request) |
|
158 | 178 |
if len(req) != 1: |
159 | 179 |
raise BadRequest('Malformed request.') |
160 | 180 |
|
b/api/servers.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 import util |
|
12 | 13 |
from synnefo.api.actions import server_actions |
13 | 14 |
from synnefo.api.common import method_not_allowed |
14 | 15 |
from synnefo.api.faults import BadRequest, ItemNotFound, ServiceUnavailable |
15 |
from synnefo.api.util import (isoformat, isoparse, random_password, |
|
16 |
get_vm, get_vm_meta, get_image, get_flavor, |
|
17 |
get_request_dict, render_metadata, render_meta, api_method) |
|
18 | 16 |
from synnefo.db.models import VirtualMachine, VirtualMachineMetadata |
19 | 17 |
from synnefo.logic.backend import create_instance, delete_instance |
20 | 18 |
from synnefo.logic.utils import get_rsapi_state |
... | ... | |
70 | 68 |
return method_not_allowed(request) |
71 | 69 |
|
72 | 70 |
|
73 |
def address_to_dict(ipfour, ipsix): |
|
74 |
return {'id': 'public', |
|
75 |
'values': [{'version': 4, 'addr': ipfour}, {'version': 6, 'addr': ipsix}]} |
|
71 |
def nic_to_dict(nic): |
|
72 |
network = nic.network |
|
73 |
network_id = str(network.id) if not network.public else 'public' |
|
74 |
d = {'id': network_id, 'name': network.name, 'mac': nic.mac} |
|
75 |
if nic.firewall_profile: |
|
76 |
d['firewallProfile'] = nic.firewall_profile |
|
77 |
if nic.ipv4 or nic.ipv6: |
|
78 |
d['values'] = [] |
|
79 |
if nic.ipv4: |
|
80 |
d['values'].append({'version': 4, 'addr': nic.ipv4}) |
|
81 |
if nic.ipv6: |
|
82 |
d['values'].append({'version': 6, 'addr': nic.ipv6}) |
|
83 |
return d |
|
76 | 84 |
|
77 | 85 |
def metadata_to_dict(vm): |
78 | 86 |
vm_meta = vm.virtualmachinemetadata_set.all() |
... | ... | |
84 | 92 |
d['status'] = get_rsapi_state(vm) |
85 | 93 |
d['progress'] = 100 if get_rsapi_state(vm) == 'ACTIVE' else 0 |
86 | 94 |
d['hostId'] = vm.hostid |
87 |
d['updated'] = isoformat(vm.updated) |
|
88 |
d['created'] = isoformat(vm.created) |
|
95 |
d['updated'] = util.isoformat(vm.updated)
|
|
96 |
d['created'] = util.isoformat(vm.created)
|
|
89 | 97 |
d['flavorRef'] = vm.flavor.id |
90 | 98 |
d['imageRef'] = vm.sourceimage.id |
91 | 99 |
|
... | ... | |
93 | 101 |
if metadata: |
94 | 102 |
d['metadata'] = {'values': metadata} |
95 | 103 |
|
96 |
addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
|
|
97 |
addresses.extend({'id': str(network.id), 'values': []} for network in vm.network_set.all())
|
|
98 |
d['addresses'] = {'values': addresses} |
|
104 |
addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
|
|
105 |
if addresses:
|
|
106 |
d['addresses'] = {'values': addresses}
|
|
99 | 107 |
return d |
100 | 108 |
|
101 | 109 |
|
102 | 110 |
def render_server(request, server, status=200): |
103 | 111 |
if request.serialization == 'xml': |
104 |
data = render_to_string('server.xml', {'server': server, 'is_root': True}) |
|
112 |
data = render_to_string('server.xml', { |
|
113 |
'server': server, |
|
114 |
'is_root': True}) |
|
105 | 115 |
else: |
106 | 116 |
data = json.dumps({'server': server}) |
107 | 117 |
return HttpResponse(data, status=status) |
108 | 118 |
|
109 | 119 |
|
110 |
@api_method('GET') |
|
120 |
@util.api_method('GET')
|
|
111 | 121 |
def list_servers(request, detail=False): |
112 | 122 |
# Normal Response Codes: 200, 203 |
113 | 123 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
116 | 126 |
# badRequest (400), |
117 | 127 |
# overLimit (413) |
118 | 128 |
|
119 |
since = isoparse(request.GET.get('changes-since')) |
|
129 |
since = util.isoparse(request.GET.get('changes-since'))
|
|
120 | 130 |
|
121 | 131 |
if since: |
122 |
user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since) |
|
132 |
user_vms = VirtualMachine.objects.filter(owner=request.user, |
|
133 |
updated__gte=since) |
|
123 | 134 |
if not user_vms: |
124 | 135 |
return HttpResponse(status=304) |
125 | 136 |
else: |
126 |
user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False) |
|
137 |
user_vms = VirtualMachine.objects.filter(owner=request.user, |
|
138 |
deleted=False) |
|
139 |
|
|
127 | 140 |
servers = [vm_to_dict(server, detail) for server in user_vms] |
128 | 141 |
|
129 | 142 |
if request.serialization == 'xml': |
130 |
data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail}) |
|
143 |
data = render_to_string('list_servers.xml', { |
|
144 |
'servers': servers, |
|
145 |
'detail': detail}) |
|
131 | 146 |
else: |
132 | 147 |
data = json.dumps({'servers': {'values': servers}}) |
133 | 148 |
|
134 | 149 |
return HttpResponse(data, status=200) |
135 | 150 |
|
136 |
@api_method('POST') |
|
151 |
@util.api_method('POST')
|
|
137 | 152 |
def create_server(request): |
138 | 153 |
# Normal Response Code: 202 |
139 | 154 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
145 | 160 |
# serverCapacityUnavailable (503), |
146 | 161 |
# overLimit (413) |
147 | 162 |
|
148 |
req = get_request_dict(request) |
|
149 |
|
|
163 |
req = util.get_request_dict(request) |
|
164 |
owner = request.user |
|
165 |
|
|
150 | 166 |
try: |
151 | 167 |
server = req['server'] |
152 | 168 |
name = server['name'] |
... | ... | |
156 | 172 |
flavor_id = server['flavorRef'] |
157 | 173 |
except (KeyError, AssertionError): |
158 | 174 |
raise BadRequest('Malformed request.') |
159 |
|
|
160 |
image = get_image(image_id, request.user) |
|
161 |
flavor = get_flavor(flavor_id) |
|
162 |
|
|
175 |
|
|
176 |
image = util.get_image(image_id, owner) |
|
177 |
flavor = util.get_flavor(flavor_id) |
|
178 |
password = util.random_password() |
|
179 |
|
|
163 | 180 |
# We must save the VM instance now, so that it gets a valid vm.backend_id. |
164 | 181 |
vm = VirtualMachine.objects.create( |
165 | 182 |
name=name, |
166 |
owner=request.user,
|
|
183 |
owner=owner,
|
|
167 | 184 |
sourceimage=image, |
168 |
ipfour='0.0.0.0', |
|
169 |
ipsix='::1', |
|
170 | 185 |
flavor=flavor) |
171 |
|
|
172 |
password = random_password() |
|
173 |
|
|
186 |
|
|
174 | 187 |
try: |
175 | 188 |
create_instance(vm, flavor, image, password) |
176 | 189 |
except GanetiApiError: |
... | ... | |
178 | 191 |
raise ServiceUnavailable('Could not create server.') |
179 | 192 |
|
180 | 193 |
for key, val in metadata.items(): |
181 |
VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) |
|
182 |
|
|
194 |
VirtualMachineMetadata.objects.create( |
|
195 |
meta_key=key, |
|
196 |
meta_value=val, |
|
197 |
vm=vm) |
|
198 |
|
|
183 | 199 |
logging.info('created vm with %s cpus, %s ram and %s storage', |
184 | 200 |
flavor.cpu, flavor.ram, flavor.disk) |
185 | 201 |
|
... | ... | |
188 | 204 |
server['adminPass'] = password |
189 | 205 |
return render_server(request, server, status=202) |
190 | 206 |
|
191 |
@api_method('GET') |
|
207 |
@util.api_method('GET')
|
|
192 | 208 |
def get_server_details(request, server_id): |
193 | 209 |
# Normal Response Codes: 200, 203 |
194 | 210 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
198 | 214 |
# itemNotFound (404), |
199 | 215 |
# overLimit (413) |
200 | 216 |
|
201 |
vm = get_vm(server_id, request.user) |
|
217 |
vm = util.get_vm(server_id, request.user)
|
|
202 | 218 |
server = vm_to_dict(vm, detail=True) |
203 | 219 |
return render_server(request, server) |
204 | 220 |
|
205 |
@api_method('PUT') |
|
221 |
@util.api_method('PUT')
|
|
206 | 222 |
def update_server_name(request, server_id): |
207 | 223 |
# Normal Response Code: 204 |
208 | 224 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
214 | 230 |
# buildInProgress (409), |
215 | 231 |
# overLimit (413) |
216 | 232 |
|
217 |
req = get_request_dict(request) |
|
233 |
req = util.get_request_dict(request)
|
|
218 | 234 |
|
219 | 235 |
try: |
220 | 236 |
name = req['server']['name'] |
221 | 237 |
except (TypeError, KeyError): |
222 | 238 |
raise BadRequest('Malformed request.') |
223 | 239 |
|
224 |
vm = get_vm(server_id, request.user) |
|
240 |
vm = util.get_vm(server_id, request.user)
|
|
225 | 241 |
vm.name = name |
226 | 242 |
vm.save() |
227 | 243 |
|
228 | 244 |
return HttpResponse(status=204) |
229 | 245 |
|
230 |
@api_method('DELETE') |
|
246 |
@util.api_method('DELETE')
|
|
231 | 247 |
def delete_server(request, server_id): |
232 | 248 |
# Normal Response Codes: 204 |
233 | 249 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
238 | 254 |
# buildInProgress (409), |
239 | 255 |
# overLimit (413) |
240 | 256 |
|
241 |
vm = get_vm(server_id, request.user) |
|
257 |
vm = util.get_vm(server_id, request.user)
|
|
242 | 258 |
delete_instance(vm) |
243 | 259 |
return HttpResponse(status=204) |
244 | 260 |
|
245 |
@api_method('POST') |
|
261 |
@util.api_method('POST')
|
|
246 | 262 |
def server_action(request, server_id): |
247 |
vm = get_vm(server_id, request.user) |
|
248 |
req = get_request_dict(request) |
|
263 |
vm = util.get_vm(server_id, request.user)
|
|
264 |
req = util.get_request_dict(request)
|
|
249 | 265 |
if len(req) != 1: |
250 | 266 |
raise BadRequest('Malformed request.') |
251 | 267 |
|
... | ... | |
260 | 276 |
except AssertionError: |
261 | 277 |
raise BadRequest('Invalid argument.') |
262 | 278 |
|
263 |
@api_method('GET') |
|
279 |
@util.api_method('GET')
|
|
264 | 280 |
def list_addresses(request, server_id): |
265 | 281 |
# Normal Response Codes: 200, 203 |
266 | 282 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
269 | 285 |
# badRequest (400), |
270 | 286 |
# overLimit (413) |
271 | 287 |
|
272 |
vm = get_vm(server_id, request.user) |
|
273 |
addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
|
|
274 |
|
|
288 |
vm = util.get_vm(server_id, request.user)
|
|
289 |
addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
|
|
290 |
|
|
275 | 291 |
if request.serialization == 'xml': |
276 | 292 |
data = render_to_string('list_addresses.xml', {'addresses': addresses}) |
277 | 293 |
else: |
... | ... | |
279 | 295 |
|
280 | 296 |
return HttpResponse(data, status=200) |
281 | 297 |
|
282 |
@api_method('GET') |
|
298 |
@util.api_method('GET')
|
|
283 | 299 |
def list_addresses_by_network(request, server_id, network_id): |
284 | 300 |
# Normal Response Codes: 200, 203 |
285 | 301 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
288 | 304 |
# badRequest (400), |
289 | 305 |
# itemNotFound (404), |
290 | 306 |
# overLimit (413) |
291 |
|
|
292 |
vm = get_vm(server_id, request.user)
|
|
293 |
if network_id != 'public':
|
|
294 |
raise ItemNotFound('Unknown network.')
|
|
295 |
|
|
296 |
address = address_to_dict(vm.ipfour, vm.ipsix)
|
|
297 |
|
|
307 |
|
|
308 |
owner = request.user
|
|
309 |
machine = util.get_vm(server_id, owner)
|
|
310 |
network = util.get_network(network_id, owner)
|
|
311 |
nic = util.get_nic(machine, network) |
|
312 |
address = nic_to_dict(nic)
|
|
313 |
|
|
298 | 314 |
if request.serialization == 'xml': |
299 | 315 |
data = render_to_string('address.xml', {'address': address}) |
300 | 316 |
else: |
... | ... | |
302 | 318 |
|
303 | 319 |
return HttpResponse(data, status=200) |
304 | 320 |
|
305 |
@api_method('GET') |
|
321 |
@util.api_method('GET')
|
|
306 | 322 |
def list_metadata(request, server_id): |
307 | 323 |
# Normal Response Codes: 200, 203 |
308 | 324 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
311 | 327 |
# badRequest (400), |
312 | 328 |
# overLimit (413) |
313 | 329 |
|
314 |
vm = get_vm(server_id, request.user) |
|
330 |
vm = util.get_vm(server_id, request.user)
|
|
315 | 331 |
metadata = metadata_to_dict(vm) |
316 |
return render_metadata(request, metadata, use_values=True, status=200) |
|
332 |
return util.render_metadata(request, metadata, use_values=True, status=200)
|
|
317 | 333 |
|
318 |
@api_method('POST') |
|
334 |
@util.api_method('POST')
|
|
319 | 335 |
def update_metadata(request, server_id): |
320 | 336 |
# Normal Response Code: 201 |
321 | 337 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
326 | 342 |
# badMediaType(415), |
327 | 343 |
# overLimit (413) |
328 | 344 |
|
329 |
vm = get_vm(server_id, request.user) |
|
330 |
req = get_request_dict(request) |
|
345 |
vm = util.get_vm(server_id, request.user)
|
|
346 |
req = util.get_request_dict(request)
|
|
331 | 347 |
try: |
332 | 348 |
metadata = req['metadata'] |
333 | 349 |
assert isinstance(metadata, dict) |
... | ... | |
344 | 360 |
updated[key] = val |
345 | 361 |
except VirtualMachineMetadata.DoesNotExist: |
346 | 362 |
pass # Ignore non-existent metadata |
363 |
|
|
364 |
if updated: |
|
365 |
vm.save() |
|
366 |
|
|
367 |
return util.render_metadata(request, updated, status=201) |
|
347 | 368 |
|
348 |
return render_metadata(request, updated, status=201) |
|
349 |
|
|
350 |
@api_method('GET') |
|
369 |
@util.api_method('GET') |
|
351 | 370 |
def get_metadata_item(request, server_id, key): |
352 | 371 |
# Normal Response Codes: 200, 203 |
353 | 372 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
357 | 376 |
# badRequest (400), |
358 | 377 |
# overLimit (413) |
359 | 378 |
|
360 |
vm = get_vm(server_id, request.user) |
|
361 |
meta = get_vm_meta(vm, key) |
|
362 |
return render_meta(request, meta, status=200) |
|
379 |
vm = util.get_vm(server_id, request.user)
|
|
380 |
meta = util.get_vm_meta(vm, key)
|
|
381 |
return util.render_meta(request, meta, status=200)
|
|
363 | 382 |
|
364 |
@api_method('PUT') |
|
383 |
@util.api_method('PUT')
|
|
365 | 384 |
def create_metadata_item(request, server_id, key): |
366 | 385 |
# Normal Response Code: 201 |
367 | 386 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
373 | 392 |
# badMediaType(415), |
374 | 393 |
# overLimit (413) |
375 | 394 |
|
376 |
vm = get_vm(server_id, request.user) |
|
377 |
req = get_request_dict(request) |
|
395 |
vm = util.get_vm(server_id, request.user)
|
|
396 |
req = util.get_request_dict(request)
|
|
378 | 397 |
try: |
379 | 398 |
metadict = req['meta'] |
380 | 399 |
assert isinstance(metadict, dict) |
... | ... | |
382 | 401 |
assert key in metadict |
383 | 402 |
except (KeyError, AssertionError): |
384 | 403 |
raise BadRequest('Malformed request.') |
385 |
|
|
386 |
meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm) |
|
404 |
|
|
405 |
meta, created = VirtualMachineMetadata.objects.get_or_create( |
|
406 |
meta_key=key, |
|
407 |
vm=vm) |
|
408 |
|
|
387 | 409 |
meta.meta_value = metadict[key] |
388 | 410 |
meta.save() |
389 |
return render_meta(request, meta, status=201) |
|
411 |
vm.save() |
|
412 |
return util.render_meta(request, meta, status=201) |
|
390 | 413 |
|
391 |
@api_method('DELETE') |
|
414 |
@util.api_method('DELETE')
|
|
392 | 415 |
def delete_metadata_item(request, server_id, key): |
393 | 416 |
# Normal Response Code: 204 |
394 | 417 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
400 | 423 |
# badMediaType(415), |
401 | 424 |
# overLimit (413), |
402 | 425 |
|
403 |
vm = get_vm(server_id, request.user) |
|
404 |
meta = get_vm_meta(vm, key) |
|
426 |
vm = util.get_vm(server_id, request.user)
|
|
427 |
meta = util.get_vm_meta(vm, key)
|
|
405 | 428 |
meta.delete() |
429 |
vm.save() |
|
406 | 430 |
return HttpResponse(status=204) |
b/api/templates/image.xml | ||
---|---|---|
1 | 1 |
{% spaceless %} |
2 | 2 |
<?xml version="1.0" encoding="UTF-8"?> |
3 | 3 |
<image xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" id="{{ image.id }}" name="{{ image.name }}" serverRef="{{ image.serverRef }}" updated="{{ image.updated }}" created="{{ image.created }}" status="{{ image.status }}" progress="{{ image.progress }}"> |
4 |
</image> |
|
4 |
|
|
5 | 5 |
{% if image.metadata %} |
6 | 6 |
<metadata> |
7 | 7 |
{% for key, val in image.metadata.values.items %} |
... | ... | |
9 | 9 |
{% endfor %} |
10 | 10 |
</metadata> |
11 | 11 |
{% endif %} |
12 |
|
|
13 |
</image> |
|
12 | 14 |
{% endspaceless %} |
b/api/templates/list_networks.xml | ||
---|---|---|
1 |
{% spaceless %} |
|
2 |
<?xml version="1.0" encoding="UTF-8"?> |
|
3 |
<networks xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom"> |
|
4 |
{% for network in networks %} |
|
5 |
<network id="{{ network.id }}" name="{{ network.name }}"{% if detail %} updated="{{ network.updated }}" created="{{ network.created }}"{% endif %}> |
|
6 |
|
|
7 |
{% if network.servers %} |
|
8 |
<servers> |
|
9 |
{% for server_id in network.servers.values %} |
|
10 |
<server id="{{ server_id }}"></server> |
|
11 |
{% endfor %} |
|
12 |
</servers> |
|
13 |
{% endif %} |
|
14 |
|
|
15 |
</network> |
|
16 |
{% endfor %} |
|
17 |
</networks> |
|
18 |
{% endspaceless %} |
b/api/templates/network.xml | ||
---|---|---|
1 |
{% spaceless %} |
|
2 |
<?xml version="1.0" encoding="UTF-8"?> |
|
3 |
<network xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" id="{{ network.id }}" name="{{ network.name }}" updated="{{ network.updated }}" created="{{ network.created }}"> |
|
4 |
|
|
5 |
<servers> |
|
6 |
{% for server_id in network.servers.values %} |
|
7 |
<server id="{{ server_id }}"></server> |
|
8 |
{% endfor %} |
|
9 |
</servers> |
|
10 |
|
|
11 |
</network> |
|
12 |
{% endspaceless %} |
b/api/tests.py | ||
---|---|---|
349 | 349 |
owner=choice(users), |
350 | 350 |
sourceimage=choice(images), |
351 | 351 |
hostid=str(i), |
352 |
ipfour='0.0.0.0', |
|
353 |
ipsix='::1', |
|
354 | 352 |
flavor=choice(flavors)) |
355 | 353 |
|
356 | 354 |
def create_server_metadata(n=1): |
... | ... | |
361 | 359 |
meta_value='Value %d' % (i + 1), |
362 | 360 |
vm = choice(servers)) |
363 | 361 |
|
364 |
def create_networks(n): |
|
365 |
users = SynnefoUser.objects.all() |
|
366 |
for i in range(n): |
|
367 |
Network.objects.create( |
|
368 |
name='Network%d' % (i + 1), |
|
369 |
owner=choice(users)) |
|
370 |
|
|
371 | 362 |
|
372 | 363 |
class AssertInvariant(object): |
373 | 364 |
def __init__(self, callable, *args, **kwargs): |
... | ... | |
390 | 381 |
SERVERS = 1 |
391 | 382 |
SERVER_METADATA = 0 |
392 | 383 |
IMAGE_METADATA = 0 |
393 |
NETWORKS = 0 |
|
394 | 384 |
|
395 | 385 |
def setUp(self): |
396 | 386 |
self.client = AaiClient() |
... | ... | |
400 | 390 |
create_image_metadata(self.IMAGE_METADATA) |
401 | 391 |
create_servers(self.SERVERS) |
402 | 392 |
create_server_metadata(self.SERVER_METADATA) |
403 |
create_networks(self.NETWORKS) |
|
404 | 393 |
|
405 | 394 |
def assertFault(self, response, status_code, name): |
406 | 395 |
self.assertEqual(response.status_code, status_code) |
... | ... | |
501 | 490 |
return reply |
502 | 491 |
|
503 | 492 |
def get_network_details(self, network_id): |
504 |
path = '/api/v1.1/networks/%d' % network_id
|
|
493 |
path = '/api/v1.1/networks/%s' % network_id
|
|
505 | 494 |
response = self.client.get(path) |
506 | 495 |
self.assertEqual(response.status_code, 200) |
507 | 496 |
reply = json.loads(response.content) |
... | ... | |
509 | 498 |
return reply['network'] |
510 | 499 |
|
511 | 500 |
def update_network_name(self, network_id, new_name): |
512 |
path = '/api/v1.1/networks/%d' % network_id
|
|
501 |
path = '/api/v1.1/networks/%s' % network_id
|
|
513 | 502 |
data = json.dumps({'network': {'name': new_name}}) |
514 | 503 |
response = self.client.put(path, data, content_type='application/json') |
515 | 504 |
self.assertEqual(response.status_code, 204) |
516 | 505 |
|
517 | 506 |
def delete_network(self, network_id): |
518 |
path = '/api/v1.1/networks/%d' % network_id
|
|
507 |
path = '/api/v1.1/networks/%s' % network_id
|
|
519 | 508 |
response = self.client.delete(path) |
520 | 509 |
self.assertEqual(response.status_code, 204) |
521 | 510 |
|
522 | 511 |
def add_to_network(self, network_id, server_id): |
523 |
path = '/api/v1.1/networks/%d/action' % network_id
|
|
512 |
path = '/api/v1.1/networks/%s/action' % network_id
|
|
524 | 513 |
data = json.dumps({'add': {'serverRef': server_id}}) |
525 | 514 |
response = self.client.post(path, data, content_type='application/json') |
526 | 515 |
self.assertEqual(response.status_code, 202) |
527 | 516 |
|
528 | 517 |
def remove_from_network(self, network_id, server_id): |
529 |
path = '/api/v1.1/networks/%d/action' % network_id
|
|
518 |
path = '/api/v1.1/networks/%s/action' % network_id
|
|
530 | 519 |
data = json.dumps({'remove': {'serverRef': server_id}}) |
531 | 520 |
response = self.client.post(path, data, content_type='application/json') |
532 | 521 |
self.assertEqual(response.status_code, 202) |
... | ... | |
843 | 832 |
|
844 | 833 |
class ListNetworks(BaseTestCase): |
845 | 834 |
SERVERS = 5 |
846 |
NETWORKS = 5 |
|
847 | 835 |
|
848 | 836 |
def setUp(self): |
849 | 837 |
BaseTestCase.setUp(self) |
838 |
|
|
839 |
for i in range(5): |
|
840 |
self.create_network('net%d' % i) |
|
841 |
|
|
850 | 842 |
machines = VirtualMachine.objects.all() |
851 | 843 |
for network in Network.objects.all(): |
852 | 844 |
n = randint(0, self.SERVERS) |
853 |
network.machines.add(*sample(machines, n))
|
|
854 |
network.save()
|
|
855 |
|
|
845 |
for machine in sample(machines, n):
|
|
846 |
machine.nics.create(network=network)
|
|
847 |
|
|
856 | 848 |
def test_list_networks(self): |
857 | 849 |
networks = self.list_networks() |
858 | 850 |
for net in Network.objects.all(): |
859 |
network = popdict(networks, id=net.id) |
|
851 |
net_id = str(net.id) if not net.public else 'public' |
|
852 |
network = popdict(networks, id=net_id) |
|
860 | 853 |
self.assertEqual(network['name'], net.name) |
861 | 854 |
self.assertEqual(networks, []) |
862 | 855 |
|
863 | 856 |
def test_list_networks_detail(self): |
864 | 857 |
networks = self.list_networks(detail=True) |
865 | 858 |
for net in Network.objects.all(): |
866 |
network = popdict(networks, id=net.id) |
|
859 |
net_id = str(net.id) if not net.public else 'public' |
|
860 |
network = popdict(networks, id=net_id) |
|
867 | 861 |
self.assertEqual(network['name'], net.name) |
868 | 862 |
machines = set(vm.id for vm in net.machines.all()) |
869 | 863 |
self.assertEqual(set(network['servers']['values']), machines) |
... | ... | |
872 | 866 |
|
873 | 867 |
class CreateNetwork(BaseTestCase): |
874 | 868 |
def test_create_network(self): |
875 |
self.assertEqual(self.list_networks(), [])
|
|
869 |
before = self.list_networks()
|
|
876 | 870 |
self.create_network('net') |
877 |
networks = self.list_networks() |
|
878 |
self.assertEqual(len(networks), 1) |
|
879 |
network = networks[0] |
|
880 |
self.assertEqual(network['name'], 'net') |
|
871 |
after = self.list_networks() |
|
872 |
self.assertEqual(len(after) - len(before), 1) |
|
873 |
found = False |
|
874 |
for network in after: |
|
875 |
if network['name'] == 'net': |
|
876 |
found = True |
|
877 |
break |
|
878 |
self.assertTrue(found) |
|
881 | 879 |
|
882 | 880 |
|
883 | 881 |
class GetNetworkDetails(BaseTestCase): |
884 | 882 |
SERVERS = 5 |
885 |
NETWORKS = 1 |
|
886 |
|
|
883 |
|
|
887 | 884 |
def test_get_network_details(self): |
885 |
name = 'net' |
|
886 |
self.create_network(name) |
|
887 |
|
|
888 | 888 |
servers = VirtualMachine.objects.all() |
889 |
network = Network.objects.all()[0]
|
|
889 |
network = Network.objects.all()[1]
|
|
890 | 890 |
|
891 | 891 |
net = self.get_network_details(network.id) |
892 |
self.assertEqual(net['name'], network.name)
|
|
892 |
self.assertEqual(net['name'], name) |
|
893 | 893 |
self.assertEqual(net['servers']['values'], []) |
894 | 894 |
|
895 | 895 |
server_id = choice(servers).id |
896 | 896 |
self.add_to_network(network.id, server_id) |
897 | 897 |
net = self.get_network_details(network.id) |
898 | 898 |
self.assertEqual(net['name'], network.name) |
899 |
self.assertEqual(net['servers']['values'], [server_id]) |
|
900 | 899 |
|
901 | 900 |
|
902 | 901 |
class UpdateNetworkName(BaseTestCase): |
903 |
NETWORKS = 5 |
|
904 |
|
|
905 | 902 |
def test_update_network_name(self): |
903 |
name = 'net' |
|
904 |
self.create_network(name) |
|
906 | 905 |
networks = self.list_networks(detail=True) |
907 |
network = choice(networks) |
|
906 |
priv = [net for net in networks if net['id'] != 'public'] |
|
907 |
network = choice(priv) |
|
908 | 908 |
network_id = network['id'] |
909 | 909 |
new_name = network['name'] + '_2' |
910 | 910 |
self.update_network_name(network_id, new_name) |
911 | 911 |
|
912 | 912 |
network['name'] = new_name |
913 |
self.assertEqual(self.get_network_details(network_id), network) |
|
913 |
del network['updated'] |
|
914 |
net = self.get_network_details(network_id) |
|
915 |
del net['updated'] |
|
916 |
self.assertEqual(net, network) |
|
914 | 917 |
|
915 | 918 |
|
916 | 919 |
class DeleteNetwork(BaseTestCase): |
917 |
NETWORKS = 5 |
|
918 |
|
|
919 | 920 |
def test_delete_network(self): |
921 |
for i in range(5): |
|
922 |
self.create_network('net%d' % i) |
|
923 |
|
|
920 | 924 |
networks = self.list_networks() |
921 |
network = choice(networks) |
|
925 |
priv = [net for net in networks if net['id'] != 'public'] |
|
926 |
network = choice(priv) |
|
922 | 927 |
network_id = network['id'] |
923 | 928 |
self.delete_network(network_id) |
929 |
|
|
930 |
net = self.get_network_details(network_id) |
|
931 |
self.assertEqual(net['status'], 'DELETED') |
|
924 | 932 |
|
925 |
response = self.client.get('/api/v1.1/networks/%d' % network_id) |
|
926 |
self.assertItemNotFound(response) |
|
927 |
|
|
928 |
networks.remove(network) |
|
929 |
self.assertEqual(self.list_networks(), networks) |
|
933 |
priv.remove(network) |
|
934 |
networks = self.list_networks() |
|
935 |
new_priv = [net for net in networks if net['id'] != 'public'] |
|
936 |
self.assertEqual(priv, new_priv) |
|
930 | 937 |
|
931 | 938 |
|
932 | 939 |
class NetworkActions(BaseTestCase): |
933 | 940 |
SERVERS = 20 |
934 |
NETWORKS = 1 |
|
935 | 941 |
|
936 | 942 |
def test_add_remove_server(self): |
943 |
self.create_network('net') |
|
944 |
|
|
937 | 945 |
server_ids = [vm.id for vm in VirtualMachine.objects.all()] |
938 |
network = self.list_networks(detail=True)[0]
|
|
946 |
network = self.list_networks(detail=True)[1]
|
|
939 | 947 |
network_id = network['id'] |
940 | 948 |
|
941 | 949 |
to_add = set(sample(server_ids, 10)) |
942 | 950 |
for server_id in to_add: |
943 | 951 |
self.add_to_network(network_id, server_id) |
944 |
net = self.get_network_details(network_id) |
|
945 |
self.assertTrue(server_id in net['servers']['values']) |
|
946 |
|
|
947 |
net = self.get_network_details(network_id) |
|
948 |
self.assertEqual(set(net['servers']['values']), to_add) |
|
949 |
|
|
952 |
|
|
950 | 953 |
to_remove = set(sample(to_add, 5)) |
951 | 954 |
for server_id in to_remove: |
952 | 955 |
self.remove_from_network(network_id, server_id) |
953 |
net = self.get_network_details(network_id) |
|
954 |
self.assertTrue(server_id not in net['servers']['values']) |
|
955 |
|
|
956 |
net = self.get_network_details(network_id) |
|
957 |
self.assertEqual(set(net['servers']['values']), to_add - to_remove) |
|
958 |
|
b/api/util.py | ||
---|---|---|
19 | 19 |
from django.template.loader import render_to_string |
20 | 20 |
from django.utils import simplejson as json |
21 | 21 |
|
22 |
from synnefo.api.faults import (Fault, BadRequest, BuildInProgress, ItemNotFound,
|
|
23 |
ServiceUnavailable, Unauthorized) |
|
22 |
from synnefo.api.faults import (Fault, BadRequest, BuildInProgress, |
|
23 |
ItemNotFound, ServiceUnavailable, Unauthorized)
|
|
24 | 24 |
from synnefo.db.models import (SynnefoUser, Flavor, Image, ImageMetadata, |
25 |
VirtualMachine, VirtualMachineMetadata, Network) |
|
25 |
VirtualMachine, VirtualMachineMetadata, |
|
26 |
Network, NetworkInterface) |
|
26 | 27 |
|
27 | 28 |
|
28 | 29 |
class UTC(tzinfo): |
... | ... | |
120 | 121 |
"""Return a Network instance or raise ItemNotFound.""" |
121 | 122 |
|
122 | 123 |
try: |
123 |
return Network.objects.get(id=network_id, owner=owner) |
|
124 |
if network_id == 'public': |
|
125 |
return Network.objects.get(public=True) |
|
126 |
else: |
|
127 |
network_id = int(network_id) |
|
128 |
return Network.objects.get(id=network_id, owner=owner) |
|
124 | 129 |
except ValueError: |
125 | 130 |
raise BadRequest('Invalid network ID.') |
126 | 131 |
except Network.DoesNotExist: |
127 | 132 |
raise ItemNotFound('Network not found.') |
128 | 133 |
|
134 |
def get_nic(machine, network): |
|
135 |
try: |
|
136 |
return NetworkInterface.objects.get(machine=machine, network=network) |
|
137 |
except NetworkInterface.DoesNotExist: |
|
138 |
raise ItemNotFound('Server not connected to this network.') |
|
139 |
|
|
129 | 140 |
|
130 | 141 |
def get_request_dict(request): |
131 | 142 |
"""Returns data sent by the client as a python dict.""" |
... | ... | |
154 | 165 |
if request.serialization == 'xml': |
155 | 166 |
data = render_to_string('metadata.xml', {'metadata': metadata}) |
156 | 167 |
else: |
157 |
d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata} |
|
168 |
if use_values: |
|
169 |
d = {'metadata': {'values': metadata}} |
|
170 |
else: |
|
171 |
d = {'metadata': metadata} |
|
158 | 172 |
data = json.dumps(d) |
159 | 173 |
return HttpResponse(data, status=status) |
160 | 174 |
|
... | ... | |
172 | 186 |
if request.serialization == 'xml': |
173 | 187 |
data = render_to_string('fault.xml', {'fault': fault}) |
174 | 188 |
else: |
175 |
d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}} |
|
189 |
d = {fault.name: { |
|
190 |
'code': fault.code, |
|
191 |
'message': fault.message, |
|
192 |
'details': fault.details}} |
|
176 | 193 |
data = json.dumps(d) |
177 | 194 |
|
178 | 195 |
resp = HttpResponse(data, status=fault.code) |
... | ... | |
213 | 230 |
@wraps(func) |
214 | 231 |
def wrapper(request, *args, **kwargs): |
215 | 232 |
try: |
216 |
request.serialization = request_serialization(request, atom_allowed) |
|
233 |
request.serialization = request_serialization( |
|
234 |
request, |
|
235 |
atom_allowed) |
|
217 | 236 |
if not request.user: |
218 | 237 |
raise Unauthorized('No user found.') |
219 | 238 |
if http_method and request.method != http_method: |
b/contrib/ganeti-hooks/kvm-vif-bridge | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
# This is an example of a Ganeti kvm ifup script that configures network |
|
4 |
# interfaces based on the initial deployment of the Okeanos project |
|
5 |
|
|
6 |
TAP_CONSTANT_MAC=cc:47:52:4e:45:54 # GRNET in hex :-) |
|
7 |
MAC2EUI64=/etc/ganeti/mac2eui64.py |
|
8 |
|
|
9 |
function routed_setup_ipv4 { |
|
10 |
# get the link's default gateway |
|
11 |
gw=$(ip route list table $LINK | sed -n 's/default via \([^ ]\+\).*/\1/p' | head -1) |
|
12 |
|
|
13 |
# mangle ARPs to come from the gw's IP |
|
14 |
arptables -D OUTPUT -o $INTERFACE --opcode request -j mangle >/dev/null 2>&1 |
|
15 |
arptables -A OUTPUT -o $INTERFACE --opcode request -j mangle --mangle-ip-s "$gw" |
|
16 |
|
|
17 |
# route interface to the proper routing table |
|
18 |
while ip rule del dev $INTERFACE; do :; done |
|
19 |
ip rule add dev $INTERFACE table $LINK |
|
20 |
|
|
21 |
# static route mapping IP -> INTERFACE |
|
22 |
ip route replace $IP table $LINK proto static dev $INTERFACE |
|
23 |
|
|
24 |
# Enable proxy ARP |
|
25 |
echo 1 > /proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp |
|
26 |
} |
|
27 |
|
|
28 |
function routed_setup_ipv6 { |
|
29 |
# Add a routing entry for the eui-64 |
|
30 |
prefix=$(ip -6 route list table $LINK | awk '/\/64/ {print $1; exit}') |
|
31 |
uplink=$(ip -6 route list table $LINK | sed -n 's/default via .* dev \([^ ]\+\).*/\1/p' | head -1) |
|
32 |
eui64=$($MAC2EUI64 $MAC $prefix) |
|
33 |
|
|
34 |
while ip -6 rule del dev $INTERFACE; do :; done |
|
35 |
ip -6 rule add dev $INTERFACE table $LINK |
|
36 |
ip -6 ro replace $eui64/128 dev $INTERFACE table $LINK |
|
37 |
ip -6 neigh add proxy $eui64 dev $uplink |
|
38 |
|
|
39 |
# disable proxy NDP since we're handling this on userspace |
|
40 |
# this should be the default, but better safe than sorry |
|
41 |
echo 0 > /proc/sys/net/ipv6/conf/$INTERFACE/proxy_ndp |
|
42 |
} |
|
43 |
|
|
44 |
# pick a firewall profile per NIC, based on tags (and apply it) |
|
45 |
function routed_setup_firewall { |
|
46 |
ifprefix="synnefo:network:$INTERFACE_INDEX:" |
|
47 |
for tag in $TAGS; do |
|
48 |
case ${tag#$ifprefix} in |
|
49 |
protected:1) |
|
50 |
chain=PROTECTED-1 |
|
51 |
;; |
|
52 |
protected:2) |
|
53 |
chain=PROTECTED-2 |
|
54 |
;; |
|
55 |
esac |
|
56 |
done |
|
57 |
|
|
58 |
iptables -D FORWARD -o $INTERFACE -j $chain 2>/dev/null |
|
59 |
ip6tables -D FORWARD -o $INTERFACE -j $chain 2>/dev/null |
|
60 |
if [ "x$chain" != "x" ]; then |
|
61 |
iptables -A FORWARD -o $INTERFACE -j $chain |
|
62 |
ip6tables -A FORWARD -o $INTERFACE -j $chain |
|
63 |
fi |
|
64 |
} |
|
65 |
|
|
66 |
function routed_setup_nfdhcpd { |
|
67 |
umask 022 |
|
68 |
cat >/var/run/ganeti-dhcpd/$INTERFACE <<EOF |
|
69 |
IP=$IP |
|
70 |
MAC=$MAC |
|
71 |
LINK=$LINK |
|
72 |
HOSTNAME=$INSTANCE |
|
73 |
TAGS="$TAGS" |
|
74 |
EOF |
|
75 |
} |
|
76 |
|
|
77 |
if [ "$MODE" = "routed" ]; then |
|
78 |
# special proxy-ARP/NDP routing mode |
|
79 |
|
|
80 |
# use a constant predefined MAC address for the tap |
|
81 |
ip link set $INTERFACE addr $TAP_CONSTANT_MAC |
|
82 |
# bring the tap up |
|
83 |
ifconfig $INTERFACE 0.0.0.0 up |
|
84 |
|
|
85 |
# Drop unicast BOOTP/DHCP packets |
|
86 |
iptables -D FORWARD -i $INTERFACE -p udp --dport 67 -j DROP 2>/dev/null |
|
87 |
iptables -A FORWARD -i $INTERFACE -p udp --dport 67 -j DROP |
|
88 |
|
|
89 |
routed_setup_ipv4 |
|
90 |
routed_setup_ipv6 |
|
91 |
routed_setup_firewall |
|
92 |
routed_setup_nfdhcpd |
|
93 |
elif [ "$MODE" = "bridged" ]; then |
Also available in: Unified diff