Revision aa197ee4
b/api/actions.py | ||
---|---|---|
25 | 25 |
'''Decorator for functions implementing server actions. |
26 | 26 |
`name` is the key in the dict passed by the client. |
27 | 27 |
''' |
28 |
|
|
28 |
|
|
29 | 29 |
def decorator(func): |
30 | 30 |
server_actions[name] = func |
31 | 31 |
return func |
... | ... | |
53 | 53 |
# itemNotFound (404), |
54 | 54 |
# buildInProgress (409), |
55 | 55 |
# overLimit (413) |
56 |
|
|
56 |
|
|
57 | 57 |
try: |
58 | 58 |
password = args['adminPass'] |
59 | 59 |
except KeyError: |
... | ... | |
72 | 72 |
# itemNotFound (404), |
73 | 73 |
# buildInProgress (409), |
74 | 74 |
# overLimit (413) |
75 |
|
|
75 |
|
|
76 | 76 |
reboot_type = args.get('type', '') |
77 | 77 |
if reboot_type not in ('SOFT', 'HARD'): |
78 | 78 |
raise BadRequest('Malformed Request.') |
... | ... | |
84 | 84 |
# Normal Response Code: 202 |
85 | 85 |
# Error Response Codes: serviceUnavailable (503), |
86 | 86 |
# itemNotFound (404) |
87 |
|
|
87 |
|
|
88 | 88 |
if args: |
89 | 89 |
raise BadRequest('Malformed Request.') |
90 | 90 |
startup_instance(vm) |
... | ... | |
95 | 95 |
# Normal Response Code: 202 |
96 | 96 |
# Error Response Codes: serviceUnavailable (503), |
97 | 97 |
# itemNotFound (404) |
98 |
|
|
98 |
|
|
99 | 99 |
if args: |
100 | 100 |
raise BadRequest('Malformed Request.') |
101 | 101 |
shutdown_instance(vm) |
... | ... | |
129 | 129 |
# serverCapacityUnavailable (503), |
130 | 130 |
# overLimit (413), |
131 | 131 |
# resizeNotAllowed (403) |
132 |
|
|
132 |
|
|
133 | 133 |
raise ServiceUnavailable('Resize not supported.') |
134 | 134 |
|
135 | 135 |
@server_action('confirmResize') |
... | ... | |
145 | 145 |
# serverCapacityUnavailable (503), |
146 | 146 |
# overLimit (413), |
147 | 147 |
# resizeNotAllowed (403) |
148 |
|
|
148 |
|
|
149 | 149 |
raise ServiceUnavailable('Resize not supported.') |
150 | 150 |
|
151 | 151 |
@server_action('revertResize') |
... | ... | |
184 | 184 |
# itemNotFound (404), |
185 | 185 |
# buildInProgress (409), |
186 | 186 |
# overLimit (413) |
187 |
|
|
187 |
|
|
188 | 188 |
console_type = args.get('type', '') |
189 | 189 |
if console_type != 'vnc': |
190 | 190 |
raise BadRequest('Type can only be "vnc".') |
... | ... | |
192 | 192 |
# Use RAPI to get VNC console information for this instance |
193 | 193 |
if get_rsapi_state(vm) != 'ACTIVE': |
194 | 194 |
raise BadRequest('Server not in ACTIVE state.') |
195 |
|
|
195 |
|
|
196 | 196 |
if settings.TEST: |
197 | 197 |
console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} |
198 | 198 |
else: |
199 | 199 |
console_data = get_instance_console(vm) |
200 |
|
|
200 |
|
|
201 | 201 |
if console_data['kind'] != 'vnc': |
202 | 202 |
raise ServiceUnavailable('Could not create a console of requested type.') |
203 |
|
|
203 |
|
|
204 | 204 |
# Let vncauthproxy decide on the source port. |
205 | 205 |
# The alternative: static allocation, e.g. |
206 | 206 |
# sport = console_data['port'] - 1000 |
... | ... | |
208 | 208 |
daddr = console_data['host'] |
209 | 209 |
dport = console_data['port'] |
210 | 210 |
password = random_password() |
211 |
|
|
211 |
|
|
212 | 212 |
try: |
213 | 213 |
if settings.TEST: |
214 | 214 |
fwd = {'source_port': 1234, 'status': 'OK'} |
... | ... | |
219 | 219 |
|
220 | 220 |
if fwd['status'] != "OK": |
221 | 221 |
raise ServiceUnavailable('Could not allocate VNC console.') |
222 |
|
|
222 |
|
|
223 | 223 |
console = { |
224 | 224 |
'type': 'vnc', |
225 | 225 |
'host': getfqdn(), |
226 | 226 |
'port': fwd['source_port'], |
227 | 227 |
'password': password} |
228 |
|
|
228 |
|
|
229 | 229 |
if request.serialization == 'xml': |
230 | 230 |
mimetype = 'application/xml' |
231 | 231 |
data = render_to_string('console.xml', {'console': console}) |
232 | 232 |
else: |
233 | 233 |
mimetype = 'application/json' |
234 | 234 |
data = json.dumps({'console': console}) |
235 |
|
|
235 |
|
|
236 | 236 |
return HttpResponse(data, mimetype=mimetype, status=200) |
237 | 237 |
|
238 | 238 |
|
... | ... | |
246 | 246 |
# badMediaType(415), |
247 | 247 |
# itemNotFound (404), |
248 | 248 |
# overLimit (413) |
249 |
|
|
249 |
|
|
250 | 250 |
server_id = args.get('serverRef', None) |
251 | 251 |
if not server_id: |
252 | 252 |
raise BadRequest('Malformed Request.') |
... | ... | |
265 | 265 |
# badMediaType(415), |
266 | 266 |
# itemNotFound (404), |
267 | 267 |
# overLimit (413) |
268 |
|
|
268 |
|
|
269 | 269 |
server_id = args.get('serverRef', None) |
270 | 270 |
if not server_id: |
271 | 271 |
raise BadRequest('Malformed Request.') |
b/api/fixtures/api_test_data.json | ||
---|---|---|
5 | 5 |
"fields": { |
6 | 6 |
"cpu": 1, |
7 | 7 |
"ram": 1024, |
8 |
"disk": 20
|
|
8 |
"disk": 20 |
|
9 | 9 |
} |
10 | 10 |
}, |
11 | 11 |
{ |
... | ... | |
14 | 14 |
"fields": { |
15 | 15 |
"cpu": 1, |
16 | 16 |
"ram": 1024, |
17 |
"disk": 40
|
|
17 |
"disk": 40 |
|
18 | 18 |
} |
19 | 19 |
}, |
20 | 20 |
{ |
... | ... | |
23 | 23 |
"fields": { |
24 | 24 |
"cpu": 1, |
25 | 25 |
"ram": 1024, |
26 |
"disk": 80
|
|
26 |
"disk": 80 |
|
27 | 27 |
} |
28 | 28 |
}, |
29 | 29 |
{ |
... | ... | |
32 | 32 |
"fields": { |
33 | 33 |
"cpu": 1, |
34 | 34 |
"ram": 2048, |
35 |
"disk": 20
|
|
35 |
"disk": 20 |
|
36 | 36 |
} |
37 | 37 |
}, |
38 | 38 |
{ |
... | ... | |
41 | 41 |
"fields": { |
42 | 42 |
"cpu": 1, |
43 | 43 |
"ram": 2048, |
44 |
"disk": 40
|
|
44 |
"disk": 40 |
|
45 | 45 |
} |
46 | 46 |
}, |
47 | 47 |
{ |
... | ... | |
50 | 50 |
"fields": { |
51 | 51 |
"cpu": 1, |
52 | 52 |
"ram": 2048, |
53 |
"disk": 80
|
|
53 |
"disk": 80 |
|
54 | 54 |
} |
55 | 55 |
}, |
56 | 56 |
{ |
... | ... | |
59 | 59 |
"fields": { |
60 | 60 |
"cpu": 1, |
61 | 61 |
"ram": 4096, |
62 |
"disk": 20
|
|
62 |
"disk": 20 |
|
63 | 63 |
} |
64 | 64 |
}, |
65 | 65 |
{ |
... | ... | |
68 | 68 |
"fields": { |
69 | 69 |
"cpu": 1, |
70 | 70 |
"ram": 4096, |
71 |
"disk": 40
|
|
71 |
"disk": 40 |
|
72 | 72 |
} |
73 | 73 |
}, |
74 | 74 |
{ |
... | ... | |
77 | 77 |
"fields": { |
78 | 78 |
"cpu": 1, |
79 | 79 |
"ram": 4096, |
80 |
"disk": 80
|
|
80 |
"disk": 80 |
|
81 | 81 |
} |
82 | 82 |
}, |
83 | 83 |
{ |
... | ... | |
86 | 86 |
"fields": { |
87 | 87 |
"cpu": 2, |
88 | 88 |
"ram": 1024, |
89 |
"disk": 20
|
|
89 |
"disk": 20 |
|
90 | 90 |
} |
91 | 91 |
}, |
92 | 92 |
{ |
... | ... | |
95 | 95 |
"fields": { |
96 | 96 |
"cpu": 2, |
97 | 97 |
"ram": 1024, |
98 |
"disk": 40
|
|
98 |
"disk": 40 |
|
99 | 99 |
} |
100 | 100 |
}, |
101 | 101 |
{ |
... | ... | |
104 | 104 |
"fields": { |
105 | 105 |
"cpu": 2, |
106 | 106 |
"ram": 1024, |
107 |
"disk": 80
|
|
107 |
"disk": 80 |
|
108 | 108 |
} |
109 | 109 |
}, |
110 | 110 |
{ |
... | ... | |
113 | 113 |
"fields": { |
114 | 114 |
"cpu": 2, |
115 | 115 |
"ram": 2048, |
116 |
"disk": 20
|
|
116 |
"disk": 20 |
|
117 | 117 |
} |
118 | 118 |
}, |
119 | 119 |
{ |
... | ... | |
122 | 122 |
"fields": { |
123 | 123 |
"cpu": 2, |
124 | 124 |
"ram": 2048, |
125 |
"disk": 40
|
|
125 |
"disk": 40 |
|
126 | 126 |
} |
127 | 127 |
}, |
128 | 128 |
{ |
... | ... | |
131 | 131 |
"fields": { |
132 | 132 |
"cpu": 2, |
133 | 133 |
"ram": 2048, |
134 |
"disk": 80
|
|
134 |
"disk": 80 |
|
135 | 135 |
} |
136 | 136 |
}, |
137 | 137 |
{ |
... | ... | |
140 | 140 |
"fields": { |
141 | 141 |
"cpu": 2, |
142 | 142 |
"ram": 4096, |
143 |
"disk": 20
|
|
143 |
"disk": 20 |
|
144 | 144 |
} |
145 | 145 |
}, |
146 | 146 |
{ |
... | ... | |
149 | 149 |
"fields": { |
150 | 150 |
"cpu": 2, |
151 | 151 |
"ram": 4096, |
152 |
"disk": 40
|
|
152 |
"disk": 40 |
|
153 | 153 |
} |
154 | 154 |
}, |
155 | 155 |
{ |
... | ... | |
158 | 158 |
"fields": { |
159 | 159 |
"cpu": 2, |
160 | 160 |
"ram": 4096, |
161 |
"disk": 80
|
|
161 |
"disk": 80 |
|
162 | 162 |
} |
163 | 163 |
}, |
164 | 164 |
{ |
... | ... | |
167 | 167 |
"fields": { |
168 | 168 |
"cpu": 4, |
169 | 169 |
"ram": 1024, |
170 |
"disk": 20
|
|
170 |
"disk": 20 |
|
171 | 171 |
} |
172 | 172 |
}, |
173 | 173 |
{ |
... | ... | |
176 | 176 |
"fields": { |
177 | 177 |
"cpu": 4, |
178 | 178 |
"ram": 1024, |
179 |
"disk": 40
|
|
179 |
"disk": 40 |
|
180 | 180 |
} |
181 | 181 |
}, |
182 | 182 |
{ |
... | ... | |
185 | 185 |
"fields": { |
186 | 186 |
"cpu": 4, |
187 | 187 |
"ram": 1024, |
188 |
"disk": 80
|
|
188 |
"disk": 80 |
|
189 | 189 |
} |
190 | 190 |
}, |
191 | 191 |
{ |
... | ... | |
194 | 194 |
"fields": { |
195 | 195 |
"cpu": 4, |
196 | 196 |
"ram": 2048, |
197 |
"disk": 20
|
|
197 |
"disk": 20 |
|
198 | 198 |
} |
199 | 199 |
}, |
200 | 200 |
{ |
... | ... | |
203 | 203 |
"fields": { |
204 | 204 |
"cpu": 4, |
205 | 205 |
"ram": 2048, |
206 |
"disk": 40
|
|
206 |
"disk": 40 |
|
207 | 207 |
} |
208 | 208 |
}, |
209 | 209 |
{ |
... | ... | |
212 | 212 |
"fields": { |
213 | 213 |
"cpu": 4, |
214 | 214 |
"ram": 2048, |
215 |
"disk": 80
|
|
215 |
"disk": 80 |
|
216 | 216 |
} |
217 | 217 |
}, |
218 | 218 |
{ |
... | ... | |
221 | 221 |
"fields": { |
222 | 222 |
"cpu": 4, |
223 | 223 |
"ram": 4096, |
224 |
"disk": 20
|
|
224 |
"disk": 20 |
|
225 | 225 |
} |
226 | 226 |
}, |
227 | 227 |
{ |
... | ... | |
230 | 230 |
"fields": { |
231 | 231 |
"cpu": 4, |
232 | 232 |
"ram": 4096, |
233 |
"disk": 40
|
|
233 |
"disk": 40 |
|
234 | 234 |
} |
235 | 235 |
}, |
236 | 236 |
{ |
... | ... | |
239 | 239 |
"fields": { |
240 | 240 |
"cpu": 4, |
241 | 241 |
"ram": 4096, |
242 |
"disk": 80
|
|
242 |
"disk": 80 |
|
243 | 243 |
} |
244 | 244 |
}, |
245 | 245 |
{ |
... | ... | |
333 | 333 |
"sourcevm": 1001 |
334 | 334 |
} |
335 | 335 |
} |
336 |
] |
|
336 |
] |
b/api/flavors.py | ||
---|---|---|
35 | 35 |
# unauthorized (401), |
36 | 36 |
# badRequest (400), |
37 | 37 |
# overLimit (413) |
38 |
|
|
38 |
|
|
39 | 39 |
all_flavors = Flavor.objects.all() |
40 | 40 |
flavors = [flavor_to_dict(flavor, detail) for flavor in all_flavors] |
41 |
|
|
41 |
|
|
42 | 42 |
if request.serialization == 'xml': |
43 | 43 |
data = render_to_string('list_flavors.xml', {'flavors': flavors, 'detail': detail}) |
44 | 44 |
else: |
45 | 45 |
data = json.dumps({'flavors': {'values': flavors}}) |
46 |
|
|
46 |
|
|
47 | 47 |
return HttpResponse(data, status=200) |
48 | 48 |
|
49 | 49 |
@api_method('GET') |
... | ... | |
55 | 55 |
# badRequest (400), |
56 | 56 |
# itemNotFound (404), |
57 | 57 |
# overLimit (413) |
58 |
|
|
58 |
|
|
59 | 59 |
flavor = get_flavor(flavor_id) |
60 | 60 |
flavordict = flavor_to_dict(flavor, detail=True) |
61 |
|
|
61 |
|
|
62 | 62 |
if request.serialization == 'xml': |
63 | 63 |
data = render_to_string('flavor.xml', {'flavor': flavordict}) |
64 | 64 |
else: |
65 | 65 |
data = json.dumps({'flavor': flavordict}) |
66 |
|
|
66 |
|
|
67 | 67 |
return HttpResponse(data, status=200) |
b/api/images.py | ||
---|---|---|
66 | 66 |
d['progress'] = 100 if image.state == 'ACTIVE' else 0 |
67 | 67 |
if image.sourcevm: |
68 | 68 |
d['serverRef'] = image.sourcevm.id |
69 |
|
|
69 |
|
|
70 | 70 |
metadata = {} |
71 | 71 |
for meta in ImageMetadata.objects.filter(image=image): |
72 | 72 |
metadata[meta.meta_key] = meta.meta_value |
73 |
|
|
73 |
|
|
74 | 74 |
if metadata: |
75 | 75 |
d['metadata'] = {'values': metadata} |
76 |
|
|
76 |
|
|
77 | 77 |
return d |
78 | 78 |
|
79 | 79 |
def metadata_to_dict(image): |
... | ... | |
89 | 89 |
# unauthorized (401), |
90 | 90 |
# badRequest (400), |
91 | 91 |
# overLimit (413) |
92 |
|
|
92 |
|
|
93 | 93 |
since = isoparse(request.GET.get('changes-since')) |
94 |
|
|
94 |
|
|
95 | 95 |
if since: |
96 | 96 |
avail_images = Image.objects.filter(owner=request.user, updated__gte=since) |
97 | 97 |
if not avail_images: |
98 | 98 |
return HttpResponse(status=304) |
99 | 99 |
else: |
100 | 100 |
avail_images = Image.objects.filter(owner=request.user) |
101 |
|
|
101 |
|
|
102 | 102 |
images = [image_to_dict(image, detail) for image in avail_images] |
103 |
|
|
103 |
|
|
104 | 104 |
if request.serialization == 'xml': |
105 | 105 |
data = render_to_string('list_images.xml', {'images': images, 'detail': detail}) |
106 | 106 |
else: |
107 | 107 |
data = json.dumps({'images': {'values': images}}) |
108 |
|
|
108 |
|
|
109 | 109 |
return HttpResponse(data, status=200) |
110 | 110 |
|
111 | 111 |
@api_method('POST') |
... | ... | |
122 | 122 |
# resizeNotAllowed (403), |
123 | 123 |
# backupOrResizeInProgress (409), |
124 | 124 |
# overLimit (413) |
125 |
|
|
125 |
|
|
126 | 126 |
req = get_request_dict(request) |
127 |
|
|
127 |
|
|
128 | 128 |
try: |
129 | 129 |
d = req['image'] |
130 | 130 |
server_id = d['serverRef'] |
131 | 131 |
name = d['name'] |
132 | 132 |
except (KeyError, ValueError): |
133 | 133 |
raise BadRequest('Malformed request.') |
134 |
|
|
134 |
|
|
135 | 135 |
owner = request.user |
136 | 136 |
vm = get_vm(server_id, owner) |
137 | 137 |
image = Image.objects.create(name=name, owner=owner, sourcevm=vm) |
138 |
|
|
138 |
|
|
139 | 139 |
imagedict = image_to_dict(image) |
140 | 140 |
if request.serialization == 'xml': |
141 | 141 |
data = render_to_string('image.xml', {'image': imagedict}) |
142 | 142 |
else: |
143 | 143 |
data = json.dumps({'image': imagedict}) |
144 |
|
|
144 |
|
|
145 | 145 |
return HttpResponse(data, status=202) |
146 | 146 |
|
147 | 147 |
@api_method('GET') |
... | ... | |
153 | 153 |
# badRequest (400), |
154 | 154 |
# itemNotFound (404), |
155 | 155 |
# overLimit (413) |
156 |
|
|
156 |
|
|
157 | 157 |
image = get_image(image_id, request.user) |
158 | 158 |
imagedict = image_to_dict(image) |
159 |
|
|
159 |
|
|
160 | 160 |
if request.serialization == 'xml': |
161 | 161 |
data = render_to_string('image.xml', {'image': imagedict}) |
162 | 162 |
else: |
163 | 163 |
data = json.dumps({'image': imagedict}) |
164 |
|
|
164 |
|
|
165 | 165 |
return HttpResponse(data, status=200) |
166 | 166 |
|
167 | 167 |
@api_method('DELETE') |
... | ... | |
172 | 172 |
# unauthorized (401), |
173 | 173 |
# itemNotFound (404), |
174 | 174 |
# overLimit (413) |
175 |
|
|
175 |
|
|
176 | 176 |
image = get_image(image_id, request.user) |
177 | 177 |
image.delete() |
178 | 178 |
return HttpResponse(status=204) |
... | ... | |
231 | 231 |
# itemNotFound (404), |
232 | 232 |
# badRequest (400), |
233 | 233 |
# overLimit (413) |
234 |
|
|
234 |
|
|
235 | 235 |
image = get_image(image_id, request.user) |
236 | 236 |
meta = get_image_meta(image, key) |
237 | 237 |
return render_meta(request, meta, status=200) |
... | ... | |
274 | 274 |
# buildInProgress (409), |
275 | 275 |
# badMediaType(415), |
276 | 276 |
# overLimit (413), |
277 |
|
|
277 |
|
|
278 | 278 |
image = get_image(image_id, request.user) |
279 | 279 |
meta = get_image_meta(image, key) |
280 | 280 |
meta.delete() |
b/api/middleware.py | ||
---|---|---|
75 | 75 |
#caching of results |
76 | 76 |
response['Vary'] = self.auth_token |
77 | 77 |
return response |
78 |
|
|
78 |
|
b/api/networks.py | ||
---|---|---|
59 | 59 |
# unauthorized (401), |
60 | 60 |
# badRequest (400), |
61 | 61 |
# overLimit (413) |
62 |
|
|
62 |
|
|
63 | 63 |
since = isoparse(request.GET.get('changes-since')) |
64 |
|
|
64 |
|
|
65 | 65 |
if since: |
66 | 66 |
user_networks = Network.objects.filter(owner=request.user, updated__gte=since) |
67 | 67 |
if not user_networks: |
68 | 68 |
return HttpResponse(status=304) |
69 | 69 |
else: |
70 | 70 |
user_networks = Network.objects.filter(owner=request.user) |
71 |
|
|
71 |
|
|
72 | 72 |
networks = [network_to_dict(network, detail) for network in user_networks] |
73 |
|
|
73 |
|
|
74 | 74 |
if request.serialization == 'xml': |
75 | 75 |
data = render_to_string('list_networks.xml', {'networks': networks, 'detail': detail}) |
76 | 76 |
else: |
77 | 77 |
data = json.dumps({'networks': {'values': networks}}) |
78 |
|
|
78 |
|
|
79 | 79 |
return HttpResponse(data, status=200) |
80 | 80 |
|
81 | 81 |
@api_method('POST') |
... | ... | |
87 | 87 |
# badMediaType(415), |
88 | 88 |
# badRequest (400), |
89 | 89 |
# overLimit (413) |
90 |
|
|
90 |
|
|
91 | 91 |
req = get_request_dict(request) |
92 |
|
|
92 |
|
|
93 | 93 |
try: |
94 | 94 |
d = req['network'] |
95 | 95 |
name = d['name'] |
96 | 96 |
except (KeyError, ValueError): |
97 | 97 |
raise BadRequest('Malformed request.') |
98 |
|
|
98 |
|
|
99 | 99 |
network = Network.objects.create(name=name, owner=request.user) |
100 | 100 |
networkdict = network_to_dict(network) |
101 | 101 |
return render_network(request, networkdict, status=202) |
... | ... | |
109 | 109 |
# badRequest (400), |
110 | 110 |
# itemNotFound (404), |
111 | 111 |
# overLimit (413) |
112 |
|
|
112 |
|
|
113 | 113 |
net = get_network(network_id, request.user) |
114 | 114 |
netdict = network_to_dict(net) |
115 | 115 |
return render_network(request, netdict) |
... | ... | |
124 | 124 |
# badMediaType(415), |
125 | 125 |
# itemNotFound (404), |
126 | 126 |
# overLimit (413) |
127 |
|
|
127 |
|
|
128 | 128 |
req = get_request_dict(request) |
129 | 129 |
|
130 | 130 |
try: |
... | ... | |
146 | 146 |
# itemNotFound (404), |
147 | 147 |
# unauthorized (401), |
148 | 148 |
# overLimit (413) |
149 |
|
|
149 |
|
|
150 | 150 |
net = get_network(network_id, request.user) |
151 | 151 |
net.delete() |
152 | 152 |
return HttpResponse(status=204) |
... | ... | |
157 | 157 |
req = get_request_dict(request) |
158 | 158 |
if len(req) != 1: |
159 | 159 |
raise BadRequest('Malformed request.') |
160 |
|
|
160 |
|
|
161 | 161 |
key = req.keys()[0] |
162 | 162 |
val = req[key] |
163 |
|
|
163 |
|
|
164 | 164 |
try: |
165 | 165 |
assert isinstance(val, dict) |
166 | 166 |
return network_actions[key](request, net, req[key]) |
b/api/servers.py | ||
---|---|---|
88 | 88 |
d['created'] = isoformat(vm.created) |
89 | 89 |
d['flavorRef'] = vm.flavor.id |
90 | 90 |
d['imageRef'] = vm.sourceimage.id |
91 |
|
|
91 |
|
|
92 | 92 |
metadata = metadata_to_dict(vm) |
93 | 93 |
if metadata: |
94 | 94 |
d['metadata'] = {'values': metadata} |
95 |
|
|
95 |
|
|
96 | 96 |
addresses = [address_to_dict(vm.ipfour, vm.ipsix)] |
97 | 97 |
addresses.extend({'id': str(network.id), 'values': []} for network in vm.network_set.all()) |
98 | 98 |
d['addresses'] = {'values': addresses} |
... | ... | |
105 | 105 |
else: |
106 | 106 |
data = json.dumps({'server': server}) |
107 | 107 |
return HttpResponse(data, status=status) |
108 |
|
|
108 |
|
|
109 | 109 |
|
110 | 110 |
@api_method('GET') |
111 | 111 |
def list_servers(request, detail=False): |
... | ... | |
115 | 115 |
# unauthorized (401), |
116 | 116 |
# badRequest (400), |
117 | 117 |
# overLimit (413) |
118 |
|
|
118 |
|
|
119 | 119 |
since = isoparse(request.GET.get('changes-since')) |
120 |
|
|
120 |
|
|
121 | 121 |
if since: |
122 | 122 |
user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since) |
123 | 123 |
if not user_vms: |
... | ... | |
125 | 125 |
else: |
126 | 126 |
user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False) |
127 | 127 |
servers = [vm_to_dict(server, detail) for server in user_vms] |
128 |
|
|
128 |
|
|
129 | 129 |
if request.serialization == 'xml': |
130 | 130 |
data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail}) |
131 | 131 |
else: |
132 | 132 |
data = json.dumps({'servers': {'values': servers}}) |
133 |
|
|
133 |
|
|
134 | 134 |
return HttpResponse(data, status=200) |
135 | 135 |
|
136 | 136 |
@api_method('POST') |
... | ... | |
144 | 144 |
# badRequest (400), |
145 | 145 |
# serverCapacityUnavailable (503), |
146 | 146 |
# overLimit (413) |
147 |
|
|
147 |
|
|
148 | 148 |
req = get_request_dict(request) |
149 |
|
|
149 |
|
|
150 | 150 |
try: |
151 | 151 |
server = req['server'] |
152 | 152 |
name = server['name'] |
... | ... | |
156 | 156 |
flavor_id = server['flavorRef'] |
157 | 157 |
except (KeyError, AssertionError): |
158 | 158 |
raise BadRequest('Malformed request.') |
159 |
|
|
159 |
|
|
160 | 160 |
image = get_image(image_id, request.user) |
161 | 161 |
flavor = get_flavor(flavor_id) |
162 |
|
|
162 |
|
|
163 | 163 |
# We must save the VM instance now, so that it gets a valid vm.backend_id. |
164 | 164 |
vm = VirtualMachine.objects.create( |
165 | 165 |
name=name, |
... | ... | |
168 | 168 |
ipfour='0.0.0.0', |
169 | 169 |
ipsix='::1', |
170 | 170 |
flavor=flavor) |
171 |
|
|
171 |
|
|
172 | 172 |
password = random_password() |
173 |
|
|
173 |
|
|
174 | 174 |
try: |
175 | 175 |
create_instance(vm, flavor, password) |
176 | 176 |
except GanetiApiError: |
177 | 177 |
vm.delete() |
178 | 178 |
raise ServiceUnavailable('Could not create server.') |
179 |
|
|
179 |
|
|
180 | 180 |
for key, val in metadata.items(): |
181 | 181 |
VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) |
182 |
|
|
182 |
|
|
183 | 183 |
logging.info('created vm with %s cpus, %s ram and %s storage', |
184 | 184 |
flavor.cpu, flavor.ram, flavor.disk) |
185 |
|
|
185 |
|
|
186 | 186 |
server = vm_to_dict(vm, detail=True) |
187 | 187 |
server['status'] = 'BUILD' |
188 | 188 |
server['adminPass'] = password |
... | ... | |
197 | 197 |
# badRequest (400), |
198 | 198 |
# itemNotFound (404), |
199 | 199 |
# overLimit (413) |
200 |
|
|
200 |
|
|
201 | 201 |
vm = get_vm(server_id, request.user) |
202 | 202 |
server = vm_to_dict(vm, detail=True) |
203 | 203 |
return render_server(request, server) |
... | ... | |
213 | 213 |
# itemNotFound (404), |
214 | 214 |
# buildInProgress (409), |
215 | 215 |
# overLimit (413) |
216 |
|
|
216 |
|
|
217 | 217 |
req = get_request_dict(request) |
218 |
|
|
218 |
|
|
219 | 219 |
try: |
220 | 220 |
name = req['server']['name'] |
221 | 221 |
except (TypeError, KeyError): |
222 | 222 |
raise BadRequest('Malformed request.') |
223 |
|
|
223 |
|
|
224 | 224 |
vm = get_vm(server_id, request.user) |
225 | 225 |
vm.name = name |
226 | 226 |
vm.save() |
227 |
|
|
227 |
|
|
228 | 228 |
return HttpResponse(status=204) |
229 | 229 |
|
230 | 230 |
@api_method('DELETE') |
... | ... | |
237 | 237 |
# unauthorized (401), |
238 | 238 |
# buildInProgress (409), |
239 | 239 |
# overLimit (413) |
240 |
|
|
240 |
|
|
241 | 241 |
vm = get_vm(server_id, request.user) |
242 | 242 |
delete_instance(vm) |
243 | 243 |
return HttpResponse(status=204) |
... | ... | |
248 | 248 |
req = get_request_dict(request) |
249 | 249 |
if len(req) != 1: |
250 | 250 |
raise BadRequest('Malformed request.') |
251 |
|
|
251 |
|
|
252 | 252 |
key = req.keys()[0] |
253 | 253 |
val = req[key] |
254 |
|
|
254 |
|
|
255 | 255 |
try: |
256 | 256 |
assert isinstance(val, dict) |
257 | 257 |
return server_actions[key](request, vm, req[key]) |
... | ... | |
268 | 268 |
# unauthorized (401), |
269 | 269 |
# badRequest (400), |
270 | 270 |
# overLimit (413) |
271 |
|
|
271 |
|
|
272 | 272 |
vm = get_vm(server_id, request.user) |
273 | 273 |
addresses = [address_to_dict(vm.ipfour, vm.ipsix)] |
274 |
|
|
274 |
|
|
275 | 275 |
if request.serialization == 'xml': |
276 | 276 |
data = render_to_string('list_addresses.xml', {'addresses': addresses}) |
277 | 277 |
else: |
278 | 278 |
data = json.dumps({'addresses': {'values': addresses}}) |
279 |
|
|
279 |
|
|
280 | 280 |
return HttpResponse(data, status=200) |
281 | 281 |
|
282 | 282 |
@api_method('GET') |
... | ... | |
288 | 288 |
# badRequest (400), |
289 | 289 |
# itemNotFound (404), |
290 | 290 |
# overLimit (413) |
291 |
|
|
291 |
|
|
292 | 292 |
vm = get_vm(server_id, request.user) |
293 | 293 |
if network_id != 'public': |
294 | 294 |
raise ItemNotFound('Unknown network.') |
295 |
|
|
295 |
|
|
296 | 296 |
address = address_to_dict(vm.ipfour, vm.ipsix) |
297 |
|
|
297 |
|
|
298 | 298 |
if request.serialization == 'xml': |
299 | 299 |
data = render_to_string('address.xml', {'address': address}) |
300 | 300 |
else: |
301 | 301 |
data = json.dumps({'network': address}) |
302 |
|
|
302 |
|
|
303 | 303 |
return HttpResponse(data, status=200) |
304 | 304 |
|
305 | 305 |
@api_method('GET') |
... | ... | |
333 | 333 |
assert isinstance(metadata, dict) |
334 | 334 |
except (KeyError, AssertionError): |
335 | 335 |
raise BadRequest('Malformed request.') |
336 |
|
|
336 |
|
|
337 | 337 |
updated = {} |
338 |
|
|
338 |
|
|
339 | 339 |
for key, val in metadata.items(): |
340 | 340 |
try: |
341 | 341 |
meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm) |
... | ... | |
344 | 344 |
updated[key] = val |
345 | 345 |
except VirtualMachineMetadata.DoesNotExist: |
346 | 346 |
pass # Ignore non-existent metadata |
347 |
|
|
347 |
|
|
348 | 348 |
return render_metadata(request, updated, status=201) |
349 | 349 |
|
350 | 350 |
@api_method('GET') |
... | ... | |
356 | 356 |
# itemNotFound (404), |
357 | 357 |
# badRequest (400), |
358 | 358 |
# overLimit (413) |
359 |
|
|
359 |
|
|
360 | 360 |
vm = get_vm(server_id, request.user) |
361 | 361 |
meta = get_vm_meta(vm, key) |
362 | 362 |
return render_meta(request, meta, status=200) |
... | ... | |
382 | 382 |
assert key in metadict |
383 | 383 |
except (KeyError, AssertionError): |
384 | 384 |
raise BadRequest('Malformed request.') |
385 |
|
|
385 |
|
|
386 | 386 |
meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm) |
387 | 387 |
meta.meta_value = metadict[key] |
388 | 388 |
meta.save() |
... | ... | |
399 | 399 |
# buildInProgress (409), |
400 | 400 |
# badMediaType(415), |
401 | 401 |
# overLimit (413), |
402 |
|
|
402 |
|
|
403 | 403 |
vm = get_vm(server_id, request.user) |
404 | 404 |
meta = get_vm_meta(vm, key) |
405 | 405 |
meta.delete() |
b/api/templates/list_flavors.xml | ||
---|---|---|
1 | 1 |
{% spaceless %} |
2 | 2 |
<?xml version="1.0" encoding="UTF-8"?> |
3 | 3 |
<flavors xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom"> |
4 |
{% for flavor in flavors %}
|
|
4 |
{% for flavor in flavors %} |
|
5 | 5 |
<flavor id="{{ flavor.id}}" name="{{ flavor.name }}"{% if detail %} ram="{{ flavor.ram }}" cpu="{{ flavor.cpu }}" disk="{{ flavor.disk }}"{% endif %}> |
6 | 6 |
</flavor> |
7 | 7 |
{% endfor %} |
b/api/templates/server.xml | ||
---|---|---|
20 | 20 |
{% for key, val in server.metadata.values.items %}<meta key="{{ key }}">{{ val }}</meta>{% endfor %} |
21 | 21 |
</metadata> |
22 | 22 |
{% endif %} |
23 |
|
|
23 |
|
|
24 | 24 |
<addresses> |
25 | 25 |
{% for network in server.addresses.values %} |
26 | 26 |
<network id="{{ network.id }}"> |
b/api/templates/version_details.atom | ||
---|---|---|
9 | 9 |
<uri>http://ocean.grnet.gr/</uri> |
10 | 10 |
</author> |
11 | 11 |
<link rel="self" href="http://ocean.grnet.gr/"/> |
12 |
|
|
12 |
|
|
13 | 13 |
<entry> |
14 | 14 |
<id>http://servers.api.openstack.org/{{ version.id }}/</id> |
15 | 15 |
<title type="text">Version {{ version.id }}</title> |
... | ... | |
17 | 17 |
{% for link in version.links %} |
18 | 18 |
<link rel="{{ link.rel }}" {% if link.type %}type="{{ link.type }}" {% endif %}href="{{ link.href }}"/> |
19 | 19 |
{% endfor %} |
20 |
<content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content>
|
|
20 |
<content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content> |
|
21 | 21 |
</entry> |
22 | 22 |
</feed> |
23 | 23 |
{% endspaceless %} |
b/api/templates/versions_list.atom | ||
---|---|---|
9 | 9 |
<uri>http://ocean.grnet.gr/</uri> |
10 | 10 |
</author> |
11 | 11 |
<link rel="self" href="http://ocean.grnet.gr/"/> |
12 |
|
|
12 |
|
|
13 | 13 |
{% for version in versions %} |
14 | 14 |
<entry> |
15 | 15 |
<id>http://servers.api.openstack.org/{{ version.id }}/</id> |
b/api/tests.py | ||
---|---|---|
40 | 40 |
|
41 | 41 |
def test_api_version(self): |
42 | 42 |
"""Check API version.""" |
43 |
|
|
43 |
|
|
44 | 44 |
response = self.client.get('/api/v1.1/') |
45 | 45 |
self.assertEqual(response.status_code, 200) |
46 | 46 |
api_version = json.loads(response.content)['version'] |
... | ... | |
49 | 49 |
|
50 | 50 |
def test_server_list(self): |
51 | 51 |
"""Test if the expected list of servers is returned.""" |
52 |
|
|
52 |
|
|
53 | 53 |
response = self.client.get('/api/v1.1/servers') |
54 | 54 |
vms_from_api = json.loads(response.content)['servers']['values'] |
55 | 55 |
vms_from_db = VirtualMachine.objects.filter(deleted=False) |
... | ... | |
62 | 62 |
|
63 | 63 |
def test_server_details(self): |
64 | 64 |
"""Test if the expected server is returned.""" |
65 |
|
|
65 |
|
|
66 | 66 |
response = self.client.get('/api/v1.1/servers/%d' % self.test_server_id) |
67 | 67 |
vm_from_api = json.loads(response.content)['server'] |
68 | 68 |
vm_from_db = VirtualMachine.objects.get(id=self.test_server_id) |
... | ... | |
76 | 76 |
|
77 | 77 |
def test_servers_details(self): |
78 | 78 |
"""Test if the servers details are returned.""" |
79 |
|
|
79 |
|
|
80 | 80 |
response = self.client.get('/api/v1.1/servers/detail') |
81 | 81 |
|
82 | 82 |
# Make sure both DB and API responses are sorted by id, |
... | ... | |
110 | 110 |
|
111 | 111 |
def test_wrong_server(self): |
112 | 112 |
"""Test 404 response if server does not exist.""" |
113 |
|
|
113 |
|
|
114 | 114 |
response = self.client.get('/api/v1.1/servers/%d' % self.test_wrong_server_id) |
115 | 115 |
self.assertEqual(response.status_code, 404) |
116 | 116 |
|
117 | 117 |
def test_create_server_empty(self): |
118 | 118 |
"""Test if the create server call returns a 400 badRequest if |
119 | 119 |
no attributes are specified.""" |
120 |
|
|
120 |
|
|
121 | 121 |
response = self.client.post('/api/v1.1/servers', {}) |
122 | 122 |
self.assertEqual(response.status_code, 400) |
123 | 123 |
|
124 | 124 |
def test_create_server(self): |
125 | 125 |
"""Test if the create server call returns the expected response |
126 | 126 |
if a valid request has been speficied.""" |
127 |
|
|
127 |
|
|
128 | 128 |
request = { |
129 | 129 |
"server": { |
130 | 130 |
"name": "new-server-test", |
... | ... | |
145 | 145 |
|
146 | 146 |
def test_server_polling(self): |
147 | 147 |
"""Test if the server polling works as expected.""" |
148 |
|
|
148 |
|
|
149 | 149 |
response = self.client.get('/api/v1.1/servers/detail') |
150 | 150 |
vms_from_api_initial = json.loads(response.content)['servers']['values'] |
151 | 151 |
ts = mktime(parsedate(response['Date'])) |
... | ... | |
165 | 165 |
"personality": [] |
166 | 166 |
} |
167 | 167 |
} |
168 |
|
|
168 |
|
|
169 | 169 |
path = '/api/v1.1/servers' |
170 | 170 |
response = self.client.post(path, json.dumps(request), content_type='application/json') |
171 | 171 |
self.assertEqual(response.status_code, 202) |
... | ... | |
178 | 178 |
|
179 | 179 |
def test_reboot_server(self): |
180 | 180 |
"""Test if the specified server is rebooted.""" |
181 |
|
|
182 | 181 |
request = {'reboot': {'type': 'HARD'}} |
183 | 182 |
path = '/api/v1.1/servers/%d/action' % self.test_server_id |
184 | 183 |
response = self.client.post(path, json.dumps(request), content_type='application/json') |
... | ... | |
190 | 189 |
|
191 | 190 |
def test_shutdown_server(self): |
192 | 191 |
"""Test if the specified server is shutdown.""" |
193 |
|
|
192 |
|
|
194 | 193 |
request = {'shutdown': {}} |
195 | 194 |
path = '/api/v1.1/servers/%d/action' % self.test_server_id |
196 | 195 |
response = self.client.post(path, json.dumps(request), content_type='application/json') |
... | ... | |
202 | 201 |
|
203 | 202 |
def test_start_server(self): |
204 | 203 |
"""Test if the specified server is started.""" |
205 |
|
|
204 |
|
|
206 | 205 |
request = {'start': {}} |
207 | 206 |
path = '/api/v1.1/servers/%d/action' % self.test_server_id |
208 | 207 |
response = self.client.post(path, json.dumps(request), content_type='application/json') |
... | ... | |
222 | 221 |
|
223 | 222 |
def test_flavor_list(self): |
224 | 223 |
"""Test if the expected list of flavors is returned by.""" |
225 |
|
|
224 |
|
|
226 | 225 |
response = self.client.get('/api/v1.1/flavors') |
227 | 226 |
flavors_from_api = json.loads(response.content)['flavors']['values'] |
228 | 227 |
flavors_from_db = Flavor.objects.all() |
... | ... | |
235 | 234 |
|
236 | 235 |
def test_flavors_details(self): |
237 | 236 |
"""Test if the flavors details are returned.""" |
238 |
|
|
237 |
|
|
239 | 238 |
response = self.client.get('/api/v1.1/flavors/detail') |
240 | 239 |
flavors_from_db = Flavor.objects.all() |
241 | 240 |
flavors_from_api = json.loads(response.content)['flavors']['values'] |
... | ... | |
264 | 263 |
|
265 | 264 |
def test_flavor_details(self): |
266 | 265 |
"""Test if the expected flavor is returned.""" |
267 |
|
|
266 |
|
|
268 | 267 |
response = self.client.get('/api/v1.1/flavors/%d' % self.test_flavor_id) |
269 | 268 |
flavor_from_api = json.loads(response.content)['flavor'] |
270 | 269 |
flavor_from_db = Flavor.objects.get(id=self.test_flavor_id) |
... | ... | |
277 | 276 |
|
278 | 277 |
def test_wrong_flavor(self): |
279 | 278 |
"""Test 404 result when requesting a flavor that does not exist.""" |
280 |
|
|
279 |
|
|
281 | 280 |
response = self.client.get('/api/v1.1/flavors/%d' % self.test_wrong_flavor_id) |
282 | 281 |
self.assertTrue(response.status_code in [404, 503]) |
283 | 282 |
|
284 | 283 |
def test_image_list(self): |
285 | 284 |
"""Test if the expected list of images is returned by the API.""" |
286 |
|
|
285 |
|
|
287 | 286 |
response = self.client.get('/api/v1.1/images') |
288 | 287 |
images_from_api = json.loads(response.content)['images']['values'] |
289 | 288 |
images_from_db = Image.objects.all() |
... | ... | |
296 | 295 |
|
297 | 296 |
def test_wrong_image(self): |
298 | 297 |
"""Test 404 result if a non existent image is requested.""" |
299 |
|
|
298 |
|
|
300 | 299 |
response = self.client.get('/api/v1.1/images/%d' % self.test_wrong_image_id) |
301 | 300 |
self.assertEqual(response.status_code, 404) |
302 | 301 |
|
303 | 302 |
def test_server_metadata(self): |
304 | 303 |
"""Test server's metadata (add, edit).""" |
305 |
|
|
304 |
|
|
306 | 305 |
key = 'name' |
307 | 306 |
request = {'meta': {key: 'a fancy name'}} |
308 |
|
|
307 |
|
|
309 | 308 |
path = '/api/v1.1/servers/%d/meta/%s' % (self.test_server_id, key) |
310 | 309 |
response = self.client.put(path, json.dumps(request), content_type='application/json') |
311 | 310 |
self.assertEqual(response.status_code, 201) |
... | ... | |
375 | 374 |
self.callable = callable |
376 | 375 |
self.args = args |
377 | 376 |
self.kwargs = kwargs |
378 |
|
|
377 |
|
|
379 | 378 |
def __enter__(self): |
380 | 379 |
self.value = self.callable(*self.args, **self.kwargs) |
381 | 380 |
return self.value |
382 |
|
|
381 |
|
|
383 | 382 |
def __exit__(self, type, value, tb): |
384 | 383 |
assert self.value == self.callable(*self.args, **self.kwargs) |
385 | 384 |
|
... | ... | |
392 | 391 |
SERVER_METADATA = 0 |
393 | 392 |
IMAGE_METADATA = 0 |
394 | 393 |
NETWORKS = 0 |
395 |
|
|
394 |
|
|
396 | 395 |
def setUp(self): |
397 | 396 |
self.client = AaiClient() |
398 | 397 |
create_users(self.USERS) |
... | ... | |
402 | 401 |
create_servers(self.SERVERS) |
403 | 402 |
create_server_metadata(self.SERVER_METADATA) |
404 | 403 |
create_networks(self.NETWORKS) |
405 |
|
|
404 |
|
|
406 | 405 |
def assertFault(self, response, status_code, name): |
407 | 406 |
self.assertEqual(response.status_code, status_code) |
408 | 407 |
fault = json.loads(response.content) |
409 | 408 |
self.assertEqual(fault.keys(), [name]) |
410 |
|
|
409 |
|
|
411 | 410 |
def assertBadRequest(self, response): |
412 | 411 |
self.assertFault(response, 400, 'badRequest') |
413 | 412 |
|
414 | 413 |
def assertItemNotFound(self, response): |
415 | 414 |
self.assertFault(response, 404, 'itemNotFound') |
416 |
|
|
417 |
|
|
415 |
|
|
416 |
|
|
418 | 417 |
def list_images(self, detail=False): |
419 | 418 |
path = '/api/v1.1/images' |
420 | 419 |
if detail: |
... | ... | |
425 | 424 |
self.assertEqual(reply.keys(), ['images']) |
426 | 425 |
self.assertEqual(reply['images'].keys(), ['values']) |
427 | 426 |
return reply['images']['values'] |
428 |
|
|
427 |
|
|
429 | 428 |
def list_metadata(self, path): |
430 | 429 |
response = self.client.get(path) |
431 | 430 |
self.assertTrue(response.status_code in (200, 203)) |
... | ... | |
433 | 432 |
self.assertEqual(reply.keys(), ['metadata']) |
434 | 433 |
self.assertEqual(reply['metadata'].keys(), ['values']) |
435 | 434 |
return reply['metadata']['values'] |
436 |
|
|
435 |
|
|
437 | 436 |
def list_server_metadata(self, server_id): |
438 | 437 |
path = '/api/v1.1/servers/%d/meta' % server_id |
439 | 438 |
return self.list_metadata(path) |
440 |
|
|
439 |
|
|
441 | 440 |
def list_image_metadata(self, image_id): |
442 | 441 |
path = '/api/v1.1/images/%d/meta' % image_id |
443 | 442 |
return self.list_metadata(path) |
444 |
|
|
443 |
|
|
445 | 444 |
def update_metadata(self, path, metadata): |
446 | 445 |
data = json.dumps({'metadata': metadata}) |
447 | 446 |
response = self.client.post(path, data, content_type='application/json') |
... | ... | |
449 | 448 |
reply = json.loads(response.content) |
450 | 449 |
self.assertEqual(reply.keys(), ['metadata']) |
451 | 450 |
return reply['metadata'] |
452 |
|
|
451 |
|
|
453 | 452 |
def update_server_metadata(self, server_id, metadata): |
454 | 453 |
path = '/api/v1.1/servers/%d/meta' % server_id |
455 | 454 |
return self.update_metadata(path, metadata) |
456 |
|
|
455 |
|
|
457 | 456 |
def update_image_metadata(self, image_id, metadata): |
458 | 457 |
path = '/api/v1.1/images/%d/meta' % image_id |
459 | 458 |
return self.update_metadata(path, metadata) |
460 |
|
|
459 |
|
|
461 | 460 |
def create_server_meta(self, server_id, meta): |
462 | 461 |
key = meta.keys()[0] |
463 | 462 |
path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key) |
... | ... | |
468 | 467 |
self.assertEqual(reply.keys(), ['meta']) |
469 | 468 |
response_meta = reply['meta'] |
470 | 469 |
self.assertEqual(response_meta, meta) |
471 |
|
|
470 |
|
|
472 | 471 |
def get_all_server_metadata(self): |
473 | 472 |
metadata = defaultdict(dict) |
474 | 473 |
for m in VirtualMachineMetadata.objects.all(): |
475 | 474 |
metadata[m.vm.id][m.meta_key] = m.meta_value |
476 | 475 |
return metadata |
477 |
|
|
476 |
|
|
478 | 477 |
def get_all_image_metadata(self): |
479 | 478 |
metadata = defaultdict(dict) |
480 | 479 |
for m in ImageMetadata.objects.all(): |
481 | 480 |
metadata[m.image.id][m.meta_key] = m.meta_value |
482 | 481 |
return metadata |
483 |
|
|
482 |
|
|
484 | 483 |
def list_networks(self, detail=False): |
485 | 484 |
path = '/api/v1.1/networks' |
486 | 485 |
if detail: |
... | ... | |
491 | 490 |
self.assertEqual(reply.keys(), ['networks']) |
492 | 491 |
self.assertEqual(reply['networks'].keys(), ['values']) |
493 | 492 |
return reply['networks']['values'] |
494 |
|
|
493 |
|
|
495 | 494 |
def create_network(self, name): |
496 | 495 |
path = '/api/v1.1/networks' |
497 | 496 |
data = json.dumps({'network': {'name': name}}) |
... | ... | |
500 | 499 |
reply = json.loads(response.content) |
501 | 500 |
self.assertEqual(reply.keys(), ['network']) |
502 | 501 |
return reply |
503 |
|
|
502 |
|
|
504 | 503 |
def get_network_details(self, network_id): |
505 | 504 |
path = '/api/v1.1/networks/%d' % network_id |
506 | 505 |
response = self.client.get(path) |
... | ... | |
508 | 507 |
reply = json.loads(response.content) |
509 | 508 |
self.assertEqual(reply.keys(), ['network']) |
510 | 509 |
return reply['network'] |
511 |
|
|
510 |
|
|
512 | 511 |
def update_network_name(self, network_id, new_name): |
513 | 512 |
path = '/api/v1.1/networks/%d' % network_id |
514 | 513 |
data = json.dumps({'network': {'name': new_name}}) |
515 | 514 |
response = self.client.put(path, data, content_type='application/json') |
516 | 515 |
self.assertEqual(response.status_code, 204) |
517 |
|
|
516 |
|
|
518 | 517 |
def delete_network(self, network_id): |
519 | 518 |
path = '/api/v1.1/networks/%d' % network_id |
520 | 519 |
response = self.client.delete(path) |
521 | 520 |
self.assertEqual(response.status_code, 204) |
522 |
|
|
521 |
|
|
523 | 522 |
def add_to_network(self, network_id, server_id): |
524 | 523 |
path = '/api/v1.1/networks/%d/action' % network_id |
525 | 524 |
data = json.dumps({'add': {'serverRef': server_id}}) |
526 | 525 |
response = self.client.post(path, data, content_type='application/json') |
527 | 526 |
self.assertEqual(response.status_code, 202) |
528 |
|
|
527 |
|
|
529 | 528 |
def remove_from_network(self, network_id, server_id): |
530 | 529 |
path = '/api/v1.1/networks/%d/action' % network_id |
531 | 530 |
data = json.dumps({'remove': {'serverRef': server_id}}) |
... | ... | |
535 | 534 |
|
536 | 535 |
def popdict(l, **kwargs): |
537 | 536 |
"""Pops a dict from list `l` based on the predicates given as `kwargs`.""" |
538 |
|
|
537 |
|
|
539 | 538 |
for i in range(len(l)): |
540 | 539 |
item = l[i] |
541 | 540 |
match = True |
... | ... | |
551 | 550 |
|
552 | 551 |
class ListImages(BaseTestCase): |
553 | 552 |
IMAGES = 10 |
554 |
|
|
553 |
|
|
555 | 554 |
def test_list_images(self): |
556 | 555 |
images = self.list_images() |
557 | 556 |
keys = set(['id', 'name']) |
... | ... | |
562 | 561 |
self.assertEqual(image['id'], img.id) |
563 | 562 |
self.assertEqual(image['name'], img.name) |
564 | 563 |
self.assertEqual(images, []) |
565 |
|
|
564 |
|
|
566 | 565 |
def test_list_images_detail(self): |
567 | 566 |
images = self.list_images(detail=True) |
568 | 567 |
keys = set(['id', 'name', 'updated', 'created', 'status', 'progress']) |
... | ... | |
580 | 579 |
class ListServerMetadata(BaseTestCase): |
581 | 580 |
SERVERS = 5 |
582 | 581 |
SERVER_METADATA = 100 |
583 |
|
|
582 |
|
|
584 | 583 |
def test_list_metadata(self): |
585 | 584 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
586 | 585 |
for vm in VirtualMachine.objects.all(): |
587 | 586 |
response_metadata = self.list_server_metadata(vm.id) |
588 | 587 |
self.assertEqual(response_metadata, metadata[vm.id]) |
589 |
|
|
588 |
|
|
590 | 589 |
def test_invalid_server(self): |
591 | 590 |
with AssertInvariant(self.get_all_server_metadata): |
592 | 591 |
response = self.client.get('/api/v1.1/servers/0/meta') |
... | ... | |
595 | 594 |
|
596 | 595 |
class UpdateServerMetadata(BaseTestCase): |
597 | 596 |
SERVER_METADATA = 10 |
598 |
|
|
597 |
|
|
599 | 598 |
def test_update_metadata(self): |
600 | 599 |
metadata = self.get_all_server_metadata() |
601 | 600 |
server_id = choice(metadata.keys()) |
... | ... | |
606 | 605 |
self.assertEqual(response_metadata, new_metadata) |
607 | 606 |
metadata[server_id].update(new_metadata) |
608 | 607 |
self.assertEqual(metadata, self.get_all_server_metadata()) |
609 |
|
|
608 |
|
|
610 | 609 |
def test_does_not_create(self): |
611 | 610 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
612 | 611 |
server_id = choice(metadata.keys()) |
613 | 612 |
new_metadata = {'Foo': 'Bar'} |
614 | 613 |
response_metadata = self.update_server_metadata(server_id, new_metadata) |
615 | 614 |
self.assertEqual(response_metadata, {}) |
616 |
|
|
615 |
|
|
617 | 616 |
def test_invalid_data(self): |
618 | 617 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
619 | 618 |
server_id = choice(metadata.keys()) |
620 | 619 |
path = '/api/v1.1/servers/%d/meta' % server_id |
621 | 620 |
response = self.client.post(path, 'metadata', content_type='application/json') |
622 | 621 |
self.assertBadRequest(response) |
623 |
|
|
622 |
|
|
624 | 623 |
def test_invalid_server(self): |
625 | 624 |
with AssertInvariant(self.get_all_server_metadata): |
626 | 625 |
path = '/api/v1.1/servers/0/meta' |
... | ... | |
632 | 631 |
class GetServerMetadataItem(BaseTestCase): |
633 | 632 |
SERVERS = 5 |
634 | 633 |
SERVER_METADATA = 100 |
635 |
|
|
634 |
|
|
636 | 635 |
def test_get_metadata_item(self): |
637 | 636 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
638 | 637 |
server_id = choice(metadata.keys()) |
... | ... | |
642 | 641 |
self.assertTrue(response.status_code in (200, 203)) |
643 | 642 |
reply = json.loads(response.content) |
644 | 643 |
self.assertEqual(reply['meta'], {key: metadata[server_id][key]}) |
645 |
|
|
644 |
|
|
646 | 645 |
def test_invalid_key(self): |
647 | 646 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
648 | 647 |
server_id = choice(metadata.keys()) |
649 | 648 |
response = self.client.get('/api/v1.1/servers/%d/meta/foo' % server_id) |
650 | 649 |
self.assertItemNotFound(response) |
651 |
|
|
650 |
|
|
652 | 651 |
def test_invalid_server(self): |
653 | 652 |
with AssertInvariant(self.get_all_server_metadata): |
654 | 653 |
response = self.client.get('/api/v1.1/servers/0/meta/foo') |
... | ... | |
657 | 656 |
|
658 | 657 |
class CreateServerMetadataItem(BaseTestCase): |
659 | 658 |
SERVER_METADATA = 10 |
660 |
|
|
659 |
|
|
661 | 660 |
def test_create_metadata(self): |
662 | 661 |
metadata = self.get_all_server_metadata() |
663 | 662 |
server_id = choice(metadata.keys()) |
... | ... | |
665 | 664 |
self.create_server_meta(server_id, meta) |
666 | 665 |
metadata[server_id].update(meta) |
667 | 666 |
self.assertEqual(metadata, self.get_all_server_metadata()) |
668 |
|
|
667 |
|
|
669 | 668 |
def test_update_metadata(self): |
670 | 669 |
metadata = self.get_all_server_metadata() |
671 | 670 |
server_id = choice(metadata.keys()) |
... | ... | |
674 | 673 |
self.create_server_meta(server_id, meta) |
675 | 674 |
metadata[server_id].update(meta) |
676 | 675 |
self.assertEqual(metadata, self.get_all_server_metadata()) |
677 |
|
|
676 |
|
|
678 | 677 |
def test_invalid_server(self): |
679 | 678 |
with AssertInvariant(self.get_all_server_metadata): |
680 | 679 |
path = '/api/v1.1/servers/0/meta/foo' |
681 | 680 |
data = json.dumps({'meta': {'foo': 'bar'}}) |
682 | 681 |
response = self.client.put(path, data, content_type='application/json') |
683 | 682 |
self.assertItemNotFound(response) |
684 |
|
|
683 |
|
|
685 | 684 |
def test_invalid_key(self): |
686 | 685 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
687 | 686 |
server_id = choice(metadata.keys()) |
... | ... | |
689 | 688 |
data = json.dumps({'meta': {'foo': 'bar'}}) |
690 | 689 |
response = self.client.put(path, data, content_type='application/json') |
691 | 690 |
self.assertBadRequest(response) |
692 |
|
|
691 |
|
|
693 | 692 |
def test_invalid_data(self): |
694 | 693 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
695 | 694 |
server_id = choice(metadata.keys()) |
... | ... | |
700 | 699 |
|
701 | 700 |
class DeleteServerMetadataItem(BaseTestCase): |
702 | 701 |
SERVER_METADATA = 10 |
703 |
|
|
702 |
|
|
704 | 703 |
def test_delete_metadata(self): |
705 | 704 |
metadata = self.get_all_server_metadata() |
706 | 705 |
server_id = choice(metadata.keys()) |
... | ... | |
710 | 709 |
self.assertEqual(response.status_code, 204) |
711 | 710 |
metadata[server_id].pop(key) |
712 | 711 |
self.assertEqual(metadata, self.get_all_server_metadata()) |
713 |
|
|
712 |
|
|
714 | 713 |
def test_invalid_server(self): |
715 | 714 |
with AssertInvariant(self.get_all_server_metadata): |
716 | 715 |
response = self.client.delete('/api/v1.1/servers/9/meta/Key1') |
717 | 716 |
self.assertItemNotFound(response) |
718 |
|
|
717 |
|
|
719 | 718 |
def test_invalid_key(self): |
720 | 719 |
with AssertInvariant(self.get_all_server_metadata) as metadata: |
721 | 720 |
server_id = choice(metadata.keys()) |
Also available in: Unified diff