1 # Copyright 2013 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from mock import patch, Mock, call
35 from unittest import TestCase
36 from json import loads
38 from kamaki.clients import Client, ClientError
39 from kamaki.clients.cyclades import CycladesClient
40 from kamaki.clients.cyclades_rest_api import CycladesClientApi
43 compute_pkg_pkg = 'kamaki.clients.connection.kamakicon.KamakiHTTPConnection'
44 compute_pkg = 'kamaki.clients.cyclades.CycladesClient'
46 img_ref = "1m4g3-r3f3r3nc3"
49 vm_send = dict(server=dict(
53 metadata=dict(os="debian", users="root")))
54 vm_recv = dict(server=dict(
56 updated="2013-03-01T10:04:00.637152+00:00",
60 created="2013-03-01T10:04:00.087324+00:00",
62 adminPass="n0n3sh@11p@55",
66 metadata=dict(values=dict(os="debian", users="root"))))
67 img_recv = dict(image=dict(
69 updated="2013-02-26T11:10:14+00:00",
71 created="2013-02-26T11:03:29+00:00",
74 metadata=dict(values=dict(
75 partition_table="msdos",
83 description="Debian 6.0.7 (Squeeze) Base System"))))
84 vm_list = dict(servers=dict(values=[
85 dict(name='n1', id=1),
86 dict(name='n2', id=2)]))
87 flavor_list = dict(flavors=dict(values=[
88 dict(id=41, name="C1R1024D20"),
89 dict(id=42, name="C1R1024D40"),
90 dict(id=43, name="C1R1028D20")]))
91 img_list = dict(images=dict(values=[
92 dict(name="maelstrom", id="0fb03e45-7d5a-4515-bd4e-e6bbf6457f06"),
93 dict(name="edx_saas", id="1357163d-5fd8-488e-a117-48734c526206"),
94 dict(name="Debian_Wheezy_Base", id="1f8454f0-8e3e-4b6c-ab8e-5236b728dffe"),
95 dict(name="CentOS", id="21894b48-c805-4568-ac8b-7d4bb8eb533d"),
96 dict(name="Ubuntu Desktop", id="37bc522c-c479-4085-bfb9-464f9b9e2e31"),
97 dict(name="Ubuntu 12.10", id="3a24fef9-1a8c-47d1-8f11-e07bd5e544fd"),
98 dict(name="Debian Base", id="40ace203-6254-4e17-a5cb-518d55418a7d"),
99 dict(name="ubuntu_bundled", id="5336e265-5c7c-4127-95cb-2bf832a79903")]))
100 net_send = dict(network=dict(dhcp=False, name='someNet'))
101 net_recv = dict(network=dict(
103 updated="2013-03-05T15:04:51.758780+00:00",
105 created="2013-03-05T15:04:51.758728+00:00",
111 cidr="192.168.1.0/24",
114 attachments=dict(values=[dict(name='att1'), dict(name='att2')])))
115 net_list = dict(networks=dict(values=[
116 dict(id=1, name='n1'),
117 dict(id=2, name='n2'),
118 dict(id=3, name='n3')]))
122 """FR stands for Fake Response"""
133 class Cyclades(TestCase):
135 def assert_dicts_are_equal(self, d1, d2):
136 for k, v in d1.items():
137 self.assertTrue(k in d2)
138 if isinstance(v, dict):
139 self.assert_dicts_are_equal(v, d2[k])
141 self.assertEqual(unicode(v), unicode(d2[k]))
143 """Set up a Cyclades thorough test"""
145 self.url = 'http://cyclades.example.com'
146 self.token = 'cyc14d3s70k3n'
147 self.client = CycladesClient(self.url, self.token)
148 from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
149 self.C = KamakiHTTPConnection
156 '%s.get_image_details' % compute_pkg,
157 return_value=img_recv['image'])
158 def test_create_server(self, GID):
160 CycladesClient, 'servers_post',
161 side_effect=ClientError(
162 'REQUEST ENTITY TOO LARGE',
166 self.client.create_server,
167 vm_name, fid, img_ref)
168 self.assertEqual(GID.mock_calls[-1], call(img_ref))
171 CycladesClient, 'servers_post',
172 return_value=FR()) as post:
173 r = self.client.create_server(vm_name, fid, img_ref)
174 self.assertEqual(r, FR.json['server'])
175 self.assertEqual(GID.mock_calls[-1], call(img_ref))
176 self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
177 prsn = 'Personality string (does not work with real servers)'
178 self.client.create_server(vm_name, fid, img_ref, prsn)
179 expected = dict(server=dict(vm_send['server']))
180 expected['server']['personality'] = prsn
181 self.assertEqual(post.mock_calls[-1], call(json_data=expected))
183 @patch('%s.servers_get' % compute_pkg, return_value=FR())
184 def test_list_servers(self, SG):
186 for detail in (False, True):
187 r = self.client.list_servers(detail)
188 for i, vm in enumerate(vm_list['servers']['values']):
189 self.assert_dicts_are_equal(r[i], vm)
190 self.assertEqual(i + 1, len(r))
191 self.assertEqual(SG.mock_calls[-1], call(
193 command='detail' if detail else ''))
195 @patch('%s.servers_get' % compute_pkg, return_value=FR())
196 def test_get_server_details(self, SG):
197 vm_id = vm_recv['server']['id']
198 r = self.client.get_server_details(vm_id)
199 self.assert_dicts_are_equal(r, vm_recv['server'])
200 self.assertEqual(SG.mock_calls[-1], call(vm_id))
202 @patch('%s.servers_put' % compute_pkg, return_value=FR())
203 def test_update_server_name(self, SP):
204 vm_id = vm_recv['server']['id']
205 new_name = vm_name + '_new'
206 self.client.update_server_name(vm_id, new_name)
207 self.assertEqual(SP.mock_calls[-1], call(vm_id, json_data=dict(
208 server=dict(name=new_name))))
210 @patch('%s.servers_post' % compute_pkg, return_value=FR())
211 def test_reboot_server(self, SP):
212 vm_id = vm_recv['server']['id']
213 for hard in (None, True):
214 self.client.reboot_server(vm_id, hard=hard)
215 self.assertEqual(SP.mock_calls[-1], call(
217 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
219 @patch('%s.servers_put' % compute_pkg, return_value=FR())
220 def test_create_server_metadata(self, SP):
221 vm_id = vm_recv['server']['id']
222 metadata = dict(m1='v1', m2='v2', m3='v3')
223 FR.json = dict(meta=vm_recv['server'])
224 for k, v in metadata.items():
225 r = self.client.create_server_metadata(vm_id, k, v)
226 self.assert_dicts_are_equal(r, vm_recv['server'])
227 self.assertEqual(SP.mock_calls[-1], call(
228 vm_id, 'meta/%s' % k,
229 json_data=dict(meta={k: v}), success=201))
231 @patch('%s.servers_get' % compute_pkg, return_value=FR())
232 def test_get_server_metadata(self, SG):
233 vm_id = vm_recv['server']['id']
234 metadata = dict(m1='v1', m2='v2', m3='v3')
235 FR.json = dict(metadata=dict(values=metadata))
236 r = self.client.get_server_metadata(vm_id)
237 self.assertEqual(SG.mock_calls[-1], call(vm_id, '/meta'))
238 self.assert_dicts_are_equal(r, metadata)
240 for k, v in metadata.items():
241 FR.json = dict(meta={k: v})
242 r = self.client.get_server_metadata(vm_id, k)
243 self.assert_dicts_are_equal(r, {k: v})
244 self.assertEqual(SG.mock_calls[-1], call(vm_id, '/meta/%s' % k))
247 @patch('%s.servers_post' % compute_pkg, return_value=FR())
248 def test_update_server_metadata(self, servers_post):
249 vm_id = vm_recv['server']['id']
250 metadata = dict(m1='v1', m2='v2', m3='v3')
251 FR.json = dict(metadata=metadata)
252 r = self.client.update_server_metadata(vm_id, **metadata)
253 self.assert_dicts_are_equal(r, metadata)
254 (called_id, cmd) = servers_post.call_args[0]
255 self.assertEqual(called_id, vm_id)
256 self.assertEqual(cmd, 'meta')
257 data = servers_post.call_args[1]['json_data']
258 self.assert_dicts_are_equal(data, dict(metadata=metadata))
260 @patch('%s.servers_delete' % compute_pkg, return_value=FR())
261 def test_delete_server_metadata(self, servers_delete):
262 vm_id = vm_recv['server']['id']
264 self.client.delete_server_metadata(vm_id, key)
265 self.assertEqual((vm_id, 'meta/' + key), servers_delete.call_args[0])
267 @patch('%s.perform_request' % compute_pkg, return_value=FR())
268 def test_list_flavors(self, PR):
269 FR.json = flavor_list
270 r = self.client.list_flavors()
271 self.assertEqual(self.client.http_client.url, self.url)
272 self.assertEqual(self.client.http_client.path, '/flavors')
273 (method, data, a_headers, a_params) = PR.call_args[0]
274 self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
275 r = self.client.list_flavors(detail=True)
276 self.assertEqual(self.client.http_client.url, self.url)
277 self.assertEqual(self.client.http_client.path, '/flavors/detail')
279 @patch('%s.perform_request' % compute_pkg, return_value=FR())
280 def test_get_flavor_details(self, PR):
281 FR.json = dict(flavor=flavor_list['flavors'])
282 r = self.client.get_flavor_details(fid)
283 self.assertEqual(self.client.http_client.url, self.url)
284 self.assertEqual(self.client.http_client.path, '/flavors/%s' % fid)
285 self.assert_dicts_are_equal(r, flavor_list['flavors'])
287 @patch('%s.perform_request' % compute_pkg, return_value=FR())
288 def test_list_images(self, PR):
290 r = self.client.list_images()
291 self.assertEqual(self.client.http_client.url, self.url)
292 self.assertEqual(self.client.http_client.path, '/images')
293 expected = img_list['images']['values']
294 for i in range(len(r)):
295 self.assert_dicts_are_equal(expected[i], r[i])
296 self.client.list_images(detail=True)
297 self.assertEqual(self.client.http_client.url, self.url)
298 self.assertEqual(self.client.http_client.path, '/images/detail')
300 @patch('%s.perform_request' % compute_pkg, return_value=FR())
301 def test_get_image_details(self, PR):
303 r = self.client.get_image_details(img_ref)
304 self.assertEqual(self.client.http_client.url, self.url)
305 self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
306 self.assert_dicts_are_equal(r, img_recv['image'])
308 @patch('%s.images_get' % compute_pkg, return_value=FR())
309 def test_get_image_metadata(self, IG):
310 FR.json = dict(metadata=dict(values=img_recv['image']))
311 r = self.client.get_image_metadata(img_ref)
312 self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta'))
313 self.assert_dicts_are_equal(img_recv['image'], r)
314 FR.json = dict(meta=img_recv['image'])
316 self.client.get_image_metadata(img_ref, key)
317 self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta/%s' % key))
319 @patch('%s.perform_request' % compute_pkg, return_value=FR())
320 def test_shutdown_server(self, PR):
321 vm_id = vm_recv['server']['id']
323 self.client.shutdown_server(vm_id)
324 self.assertEqual(self.client.http_client.url, self.url)
326 self.client.http_client.path,
327 '/servers/%s/action' % vm_id)
330 ('post', '{"shutdown": {}}', {}, {}))
332 @patch('%s.perform_request' % compute_pkg, return_value=FR())
333 def test_start_server(self, PR):
334 vm_id = vm_recv['server']['id']
336 self.client.start_server(vm_id)
337 self.assertEqual(self.client.http_client.url, self.url)
339 self.client.http_client.path,
340 '/servers/%s/action' % vm_id)
341 self.assertEqual(PR.call_args[0], ('post', '{"start": {}}', {}, {}))
343 @patch('%s.perform_request' % compute_pkg, return_value=FR())
344 def test_get_server_console(self, PR):
345 cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
347 vm_id = vm_recv['server']['id']
348 r = self.client.get_server_console(vm_id)
349 self.assertEqual(self.client.http_client.url, self.url)
351 self.client.http_client.path,
352 '/servers/%s/action' % vm_id)
353 self.assert_dicts_are_equal(cnsl['console'], r)
356 ('post', '{"console": {"type": "vnc"}}', {}, {}))
358 def test_get_firewall_profile(self):
359 vm_id = vm_recv['server']['id']
361 ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
364 'get_server_details',
365 return_value=ret) as GSD:
366 r = self.client.get_firewall_profile(vm_id)
367 self.assertEqual(r, v)
368 self.assertEqual(GSD.call_args[0], (vm_id,))
369 ret['attachments']['values'][0].pop('firewallProfile')
372 self.client.get_firewall_profile,
375 @patch('%s.perform_request' % compute_pkg, return_value=FR())
376 def test_set_firewall_profile(self, PR):
377 vm_id = vm_recv['server']['id']
380 self.client.set_firewall_profile(vm_id, v)
381 self.assertEqual(self.client.http_client.url, self.url)
383 self.client.http_client.path,
384 '/servers/%s/action' % vm_id)
385 self.assertEqual(PR.call_args[0], (
387 '{"firewallProfile": {"profile": "%s"}}' % v,
391 @patch('%s.perform_request' % compute_pkg, return_value=FR())
392 def test_get_server_stats(self, PR):
393 vm_id = vm_recv['server']['id']
394 stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
395 FR.json = dict(stats=stats)
396 r = self.client.get_server_stats(vm_id)
397 self.assertEqual(self.client.http_client.url, self.url)
399 self.client.http_client.path,
400 '/servers/%s/stats' % vm_id)
401 self.assert_dicts_are_equal(stats, r)
403 @patch('%s.perform_request' % compute_pkg, return_value=FR())
404 def test_create_network(self, PR):
405 net_name = net_send['network']['name']
409 cidr='192.168.0.0/24',
410 gateway='192.168.0.1',
413 test_args = dict(full_args)
414 test_args.update(dict(empty=None, full=None))
415 for arg, val in test_args.items():
416 kwargs = {} if arg == 'empty' else full_args if (
417 arg == 'full') else {arg: val}
418 r = self.client.create_network(net_name, **kwargs)
419 self.assertEqual(self.client.http_client.url, self.url)
421 self.client.http_client.path,
423 self.assert_dicts_are_equal(r, net_recv['network'])
424 data = PR.call_args[0][1]
425 expected = dict(network=dict(net_send['network']))
426 expected['network'].update(kwargs)
427 self.assert_dicts_are_equal(loads(data), expected)
429 @patch('%s.perform_request' % compute_pkg, return_value=FR())
430 def test_connect_server(self, PR):
431 vm_id = vm_recv['server']['id']
432 net_id = net_recv['network']['id']
434 self.client.connect_server(vm_id, net_id)
435 self.assertEqual(self.client.http_client.url, self.url)
437 self.client.http_client.path,
438 '/networks/%s/action' % net_id)
441 ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
443 @patch('%s.networks_post' % compute_pkg, return_value=FR())
444 def test_disconnect_server(self, NP):
445 vm_id = vm_recv['server']['id']
446 net_id = net_recv['network']['id']
447 nic_id = 'nic-%s-%s' % (net_id, vm_id)
449 dict(id=nic_id, network_id=net_id),
450 dict(id='another-nic-id', network_id='another-net-id'),
451 dict(id=nic_id * 2, network_id=net_id * 2)]
455 return_value=vm_nics) as LSN:
456 r = self.client.disconnect_server(vm_id, nic_id)
457 self.assertEqual(r, 1)
458 self.assertEqual(LSN.call_args[0], (vm_id,))
459 self.assertEqual(NP.call_args[0], (net_id, 'action'))
462 dict(json_data=dict(remove=dict(attachment=nic_id))))
464 @patch('%s.perform_request' % compute_pkg, return_value=FR())
465 def test_list_server_nics(self, PR):
466 vm_id = vm_recv['server']['id']
467 nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
469 r = self.client.list_server_nics(vm_id)
470 self.assertEqual(self.client.http_client.url, self.url)
472 self.client.http_client.path,
473 '/servers/%s/ips' % vm_id)
474 expected = nics['addresses']['values']
475 for i in range(len(r)):
476 self.assert_dicts_are_equal(r[i], expected[i])
478 @patch('%s.perform_request' % compute_pkg, return_value=FR())
479 def test_list_networks(self, PR):
481 r = self.client.list_networks()
482 self.assertEqual(self.client.http_client.url, self.url)
483 self.assertEqual(self.client.http_client.path, '/networks')
484 expected = net_list['networks']['values']
485 for i in range(len(r)):
486 self.assert_dicts_are_equal(expected[i], r[i])
487 self.client.list_networks(detail=True)
488 self.assertEqual(self.client.http_client.url, self.url)
489 self.assertEqual(self.client.http_client.path, '/networks/detail')
491 @patch('%s.perform_request' % compute_pkg, return_value=FR())
492 def test_list_network_nics(self, PR):
493 net_id = net_recv['network']['id']
495 r = self.client.list_network_nics(net_id)
496 self.assertEqual(self.client.http_client.url, self.url)
498 self.client.http_client.path,
499 '/networks/%s' % net_id)
500 expected = net_recv['network']['attachments']['values']
501 for i in range(len(r)):
502 self.assert_dicts_are_equal(r[i], expected[i])
504 @patch('%s.networks_post' % compute_pkg, return_value=FR())
505 def test_disconnect_network_nics(self, NP):
506 net_id = net_recv['network']['id']
507 nics = ['nic1', 'nic2', 'nic3']
511 return_value=nics) as lnn:
512 self.client.disconnect_network_nics(net_id)
513 lnn.assert_called_once_with(net_id)
514 for i in range(len(nics)):
515 expected = call(net_id, 'action', json_data=dict(
516 remove=dict(attachment=nics[i])))
517 self.assertEqual(expected, NP.mock_calls[i])
519 @patch('%s.perform_request' % compute_pkg, return_value=FR())
520 def test_get_network_details(self, PR):
522 net_id = net_recv['network']['id']
523 r = self.client.get_network_details(net_id)
524 self.assertEqual(self.client.http_client.url, self.url)
526 self.client.http_client.path,
527 '/networks/%s' % net_id)
528 self.assert_dicts_are_equal(r, net_recv['network'])
530 @patch('%s.perform_request' % compute_pkg, return_value=FR())
531 def test_update_network_name(self, PR):
532 net_id = net_recv['network']['id']
533 new_name = '%s_new' % net_id
535 self.client.update_network_name(net_id, new_name)
536 self.assertEqual(self.client.http_client.url, self.url)
537 self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
538 (method, data, a_headers, a_params) = PR.call_args[0]
539 self.assert_dicts_are_equal(
540 dict(network=dict(name=new_name)),
543 @patch('%s.perform_request' % compute_pkg, return_value=FR())
544 def test_delete_server(self, PR):
545 vm_id = vm_recv['server']['id']
547 self.client.delete_server(vm_id)
548 self.assertEqual(self.client.http_client.url, self.url)
549 self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
551 @patch('%s.perform_request' % compute_pkg, return_value=FR())
552 def test_delete_image(self, PR):
554 self.client.delete_image(img_ref)
555 self.assertEqual(self.client.http_client.url, self.url)
556 self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
558 @patch('%s.perform_request' % compute_pkg, return_value=FR())
559 def test_delete_network(self, PR):
560 net_id = net_recv['network']['id']
562 self.client.delete_network(net_id)
563 self.assertEqual(self.client.http_client.url, self.url)
564 self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
566 @patch('%s.perform_request' % compute_pkg, return_value=FR())
567 def test_create_image_metadata(self, PR):
568 metadata = dict(m1='v1', m2='v2', m3='v3')
569 FR.json = dict(meta=img_recv['image'])
572 self.client.create_image_metadata,
573 img_ref, 'key', 'value')
575 for k, v in metadata.items():
576 r = self.client.create_image_metadata(img_ref, k, v)
577 self.assertEqual(self.client.http_client.url, self.url)
579 self.client.http_client.path,
580 '/images/%s/meta/%s' % (img_ref, k))
581 (method, data, a_headers, a_params) = PR.call_args[0]
582 self.assertEqual(dict(meta={k: v}), loads(data))
583 self.assert_dicts_are_equal(r, img_recv['image'])
585 @patch('%s.images_post' % compute_pkg, return_value=FR())
586 def test_update_image_metadata(self, images_post):
587 metadata = dict(m1='v1', m2='v2', m3='v3')
588 FR.json = dict(metadata=metadata)
589 r = self.client.update_image_metadata(img_ref, **metadata)
590 self.assert_dicts_are_equal(r, metadata)
591 (called_id, cmd) = images_post.call_args[0]
592 self.assertEqual(called_id, img_ref)
593 self.assertEqual(cmd, 'meta')
594 data = images_post.call_args[1]['json_data']
595 self.assert_dicts_are_equal(data, dict(metadata=metadata))
597 @patch('%s.images_delete' % compute_pkg, return_value=FR())
598 def test_delete_image_metadata(self, images_delete):
600 self.client.delete_image_metadata(img_ref, key)
602 (img_ref, '/meta/' + key),
603 images_delete.call_args[0])
606 if __name__ == '__main__':
608 from kamaki.clients.test import runTestCase
609 runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])