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, call
35 from unittest import TestCase
36 from itertools import product
37 from json import dumps
39 from kamaki.clients import ClientError, cyclades
41 img_ref = "1m4g3-r3f3r3nc3"
44 vm_recv = dict(server=dict(
46 updated="2013-03-01T10:04:00.637152+00:00",
50 created="2013-03-01T10:04:00.087324+00:00",
52 adminPass="n0n3sh@11p@55",
56 metadata=dict(os="debian", users="root")))
57 vm_list = dict(servers=[
58 dict(name='n1', id=1),
59 dict(name='n2', id=2)])
60 net_send = dict(network=dict(dhcp=False, name='someNet'))
61 net_recv = dict(network=dict(
63 updated="2013-03-05T15:04:51.758780+00:00",
65 created="2013-03-05T15:04:51.758728+00:00",
71 cidr="192.168.1.0/24",
74 attachments=[dict(name='att1'), dict(name='att2')]))
75 net_list = dict(networks=[
76 dict(id=1, name='n1'),
77 dict(id=2, name='n2'),
78 dict(id=3, name='n3')])
79 firewalls = dict(attachments=[
80 dict(firewallProfile='50m3_pr0f1L3', otherStuff='57uff')])
84 """FR stands for Fake Response"""
91 rest_pkg = 'kamaki.clients.cyclades.CycladesRestClient'
92 cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
95 class CycladesRestClient(TestCase):
97 """Set up a Cyclades thorough test"""
99 self.url = 'http://cyclades.example.com'
100 self.token = 'cyc14d3s70k3n'
101 self.client = cyclades.CycladesRestClient(self.url, self.token)
106 @patch('%s.get' % rest_pkg, return_value=FR())
107 def test_networks_get(self, get):
113 (srv_id, command, success, kwargs) = args
114 self.client.networks_get(*args[:3], **kwargs)
115 srv_str = '/%s' % srv_id if srv_id else ''
116 cmd_str = '/%s' % command if command else ''
117 self.assertEqual(get.mock_calls[-1], call(
118 '/networks%s%s' % (srv_str, cmd_str),
122 @patch('%s.delete' % rest_pkg, return_value=FR())
123 def test_networks_delete(self, delete):
129 (srv_id, command, success, kwargs) = args
130 self.client.networks_delete(*args[:3], **kwargs)
131 srv_str = '/%s' % srv_id if srv_id else ''
132 cmd_str = '/%s' % command if command else ''
133 self.assertEqual(delete.mock_calls[-1], call(
134 '/networks%s%s' % (srv_str, cmd_str),
138 @patch('%s.set_header' % rest_pkg)
139 @patch('%s.post' % rest_pkg, return_value=FR())
140 def test_networks_post(self, post, SH):
144 (None, [dict(json="data"), dict(data="json")]),
147 (srv_id, command, json_data, success, kwargs) = args
148 self.client.networks_post(*args[:4], **kwargs)
149 vm_str = '/%s' % srv_id if srv_id else ''
150 cmd_str = '/%s' % command if command else ''
152 json_data = dumps(json_data)
153 self.assertEqual(SH.mock_calls[-2:], [
154 call('Content-Type', 'application/json'),
155 call('Content-Length', len(json_data))])
156 self.assertEqual(post.mock_calls[-1], call(
157 '/networks%s%s' % (vm_str, cmd_str),
158 data=json_data, success=success,
161 @patch('%s.set_header' % rest_pkg)
162 @patch('%s.put' % rest_pkg, return_value=FR())
163 def test_networks_put(self, put, SH):
167 (None, [dict(json="data"), dict(data="json")]),
170 (srv_id, command, json_data, success, kwargs) = args
171 self.client.networks_put(*args[:4], **kwargs)
172 vm_str = '/%s' % srv_id if srv_id else ''
173 cmd_str = '/%s' % command if command else ''
175 json_data = dumps(json_data)
176 self.assertEqual(SH.mock_calls[-2:], [
177 call('Content-Type', 'application/json'),
178 call('Content-Length', len(json_data))])
179 self.assertEqual(put.mock_calls[-1], call(
180 '/networks%s%s' % (vm_str, cmd_str),
181 data=json_data, success=success,
184 @patch('%s.get' % rest_pkg, return_value=FR())
185 def test_floating_ip_pools_get(self, get):
189 success, kwargs = args
190 r = self.client.floating_ip_pools_get(success, **kwargs)
191 self.assertTrue(isinstance(r, FR))
192 self.assertEqual(get.mock_calls[-1], call(
193 '/os-floating-ip-pools', success=success, **kwargs))
195 @patch('%s.get' % rest_pkg, return_value=FR())
196 def test_floating_ips_get(self, get):
201 fip, success, kwargs = args
202 r = self.client.floating_ips_get(fip, success, **kwargs)
203 self.assertTrue(isinstance(r, FR))
204 expected = '' if not fip else '/%s' % fip
205 self.assertEqual(get.mock_calls[-1], call(
206 '/os-floating-ips%s' % expected, success=success, **kwargs))
208 @patch('%s.set_header' % rest_pkg)
209 @patch('%s.post' % rest_pkg, return_value=FR())
210 def test_floating_ips_post(self, post, SH):
212 (None, [dict(json="data"), dict(data="json")]),
216 json_data, fip, success, kwargs = args
217 self.client.floating_ips_post(*args[:3], **kwargs)
219 json_data = dumps(json_data)
220 self.assertEqual(SH.mock_calls[-2:], [
221 call('Content-Type', 'application/json'),
222 call('Content-Length', len(json_data))])
223 expected = '' if not fip else '/%s' % fip
224 self.assertEqual(post.mock_calls[-1], call(
225 '/os-floating-ips%s' % expected,
226 data=json_data, success=success,
229 @patch('%s.delete' % rest_pkg, return_value=FR())
230 def test_floating_ips_delete(self, delete):
235 fip, success, kwargs = args
236 r = self.client.floating_ips_delete(fip, success, **kwargs)
237 self.assertTrue(isinstance(r, FR))
238 self.assertEqual(delete.mock_calls[-1], call(
239 '/os-floating-ips/%s' % fip, success=success, **kwargs))
242 class CycladesNetworkClient(TestCase):
244 """Set up a ComputesRest thorough test"""
246 self.url = 'http://network.example.com'
247 self.token = 'n2tw0rk70k3n'
248 self.client = cyclades.CycladesNetworkClient(self.url, self.token)
254 @patch('kamaki.clients.Client.get', return_value=FR)
255 def test_list_networks(self, get):
256 FR.json = dict(networks='ret val')
257 for detail in (True, None):
258 self.assertEqual(self.client.list_networks(detail), 'ret val')
259 path = '/networks/detail' if detail else '/networks'
260 self.assertEqual(get.mock_calls[-1], call(path, success=200))
263 'kamaki.clients.network.rest_api.NetworkRestClient.networks_post',
265 def test_create_network(self, networks_post):
266 for name, shared in product((None, 'net name'), (None, True)):
267 FR.json = dict(network='ret val')
270 self.client.create_network(type, name=name, shared=shared),
272 req = dict(type=type, admin_state_up=True)
276 req['shared'] = shared
277 expargs = dict(json_data=dict(network=req), success=201)
278 self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
281 'kamaki.clients.network.rest_api.NetworkRestClient.ports_post',
283 def test_create_port(self, ports_post):
284 network_id, device_id, FR.json = 'netid', 'devid', dict(port='ret v')
285 for name, sec_grp, fixed_ips in product(
289 dict(subnet_id='sid', ip_address='ipa'),
290 dict(subnet_id='sid'), dict(ip_address='ipa'),
294 diff = set(['subnet_id', 'ip_address']).difference(fixed_ips)
297 ValueError, self.client.create_port,
298 network_id, device_id,
300 security_groups=sec_grp,
304 self.client.create_port(
305 network_id, device_id,
306 name=name, security_groups=sec_grp, fixed_ips=fixed_ips),
308 req = dict(network_id=network_id, device_id=device_id)
310 req['security_groups'] = sec_grp
314 req['fixed_ips'] = fixed_ips
315 expargs = dict(json_data=dict(port=req), success=201)
316 self.assertEqual(ports_post.mock_calls[-1], call(**expargs))
319 class CycladesClient(TestCase):
321 def assert_dicts_are_equal(self, d1, d2):
322 for k, v in d1.items():
323 self.assertTrue(k in d2)
324 if isinstance(v, dict):
325 self.assert_dicts_are_equal(v, d2[k])
327 self.assertEqual(unicode(v), unicode(d2[k]))
329 """Set up a Cyclades thorough test"""
331 self.url = 'http://cyclades.example.com'
332 self.token = 'cyc14d3s70k3n'
333 self.client = cyclades.CycladesClient(self.url, self.token)
339 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
340 def test_shutdown_server(self, SP):
341 vm_id = vm_recv['server']['id']
342 self.client.shutdown_server(vm_id)
343 SP.assert_called_once_with(
344 vm_id, json_data=dict(shutdown=dict()), success=202)
346 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
347 def test_start_server(self, SP):
348 vm_id = vm_recv['server']['id']
349 self.client.start_server(vm_id)
350 SP.assert_called_once_with(
351 vm_id, json_data=dict(start=dict()), success=202)
353 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
354 def test_get_server_console(self, SP):
355 cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
357 vm_id = vm_recv['server']['id']
358 r = self.client.get_server_console(vm_id)
359 SP.assert_called_once_with(
360 vm_id, json_data=dict(console=dict(type='vnc')), success=200)
361 self.assert_dicts_are_equal(r, cnsl['console'])
363 def test_get_firewall_profile(self):
364 vm_id = vm_recv['server']['id']
365 v = firewalls['attachments'][0]['firewallProfile']
367 cyclades.CycladesClient, 'get_server_details',
368 return_value=firewalls) as GSD:
369 r = self.client.get_firewall_profile(vm_id)
370 GSD.assert_called_once_with(vm_id)
371 self.assertEqual(r, v)
373 cyclades.CycladesClient, 'get_server_details',
374 return_value=dict()):
377 self.client.get_firewall_profile,
380 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
381 def test_set_firewall_profile(self, SP):
382 vm_id = vm_recv['server']['id']
383 v = firewalls['attachments'][0]['firewallProfile']
384 self.client.set_firewall_profile(vm_id, v)
385 SP.assert_called_once_with(vm_id, json_data=dict(
386 firewallProfile=dict(profile=v)), success=202)
388 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
389 def test_create_network(self, NP):
390 net_name = net_send['network']['name']
393 cidr='192.168.0.0/24',
394 gateway='192.168.0.1',
397 test_args = dict(full_args)
398 test_args.update(dict(empty=None, full=None))
399 net_exp = dict(dhcp=False, name=net_name, type='MAC_FILTERED')
400 for arg, val in test_args.items():
401 kwargs = {} if arg == 'empty' else full_args if (
402 arg == 'full') else {arg: val}
403 expected = dict(network=dict(net_exp))
404 expected['network'].update(kwargs)
405 r = self.client.create_network(net_name, **kwargs)
408 call(json_data=expected, success=202))
409 self.assert_dicts_are_equal(r, net_recv['network'])
411 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
412 def test_connect_server(self, NP):
413 vm_id = vm_recv['server']['id']
414 net_id = net_recv['network']['id']
415 self.client.connect_server(vm_id, net_id)
416 NP.assert_called_once_with(
418 json_data=dict(add=dict(serverRef=vm_id)))
420 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
421 def test_disconnect_server(self, NP):
422 net_id, vm_id = net_recv['network']['id'], vm_recv['server']['id']
423 nic_id = 'nic-%s-%s' % (net_id, vm_id)
425 dict(id=nic_id, network_id=net_id),
426 dict(id='another-nic-id', network_id='another-net-id'),
427 dict(id=nic_id * 2, network_id=net_id * 2)]
429 cyclades.CycladesClient,
431 return_value=vm_nics) as LSN:
432 r = self.client.disconnect_server(vm_id, nic_id)
433 LSN.assert_called_once_with(vm_id)
434 NP.assert_called_once_with(
436 json_data=dict(remove=dict(attachment=nic_id)))
437 self.assertEqual(r, 1)
439 @patch('%s.servers_ips_get' % cyclades_pkg, return_value=FR())
440 def test_list_server_nics(self, SG):
441 vm_id = vm_recv['server']['id']
442 nics = dict(attachments=[dict(id='nic1'), dict(id='nic2')])
444 r = self.client.list_server_nics(vm_id)
445 SG.assert_called_once_with(vm_id)
446 expected = nics['attachments']
447 for i in range(len(r)):
448 self.assert_dicts_are_equal(r[i], expected[i])
449 self.assertEqual(i + 1, len(r))
451 @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
452 def test_list_networks(self, NG):
454 expected = net_list['networks']
455 for detail in ('', 'detail'):
456 r = self.client.list_networks(detail=True if detail else False)
457 self.assertEqual(NG.mock_calls[-1], call(command=detail))
458 for i, net in enumerate(expected):
459 self.assert_dicts_are_equal(r[i], net)
460 self.assertEqual(i + 1, len(r))
462 @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
463 def test_list_network_nics(self, NG):
464 net_id = net_recv['network']['id']
466 r = self.client.list_network_nics(net_id)
467 NG.assert_called_once_with(network_id=net_id)
468 expected = net_recv['network']['attachments']
469 for i in range(len(r)):
470 self.assert_dicts_are_equal(r[i], expected[i])
472 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
473 def test_disconnect_network_nics(self, NP):
474 net_id = net_recv['network']['id']
475 nics = ['nic1', 'nic2', 'nic3']
477 cyclades.CycladesClient,
479 return_value=nics) as LNN:
480 self.client.disconnect_network_nics(net_id)
481 LNN.assert_called_once_with(net_id)
482 for i in range(len(nics)):
483 expected = call(net_id, 'action', json_data=dict(
484 remove=dict(attachment=nics[i])))
485 self.assertEqual(expected, NP.mock_calls[i])
487 @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
488 def test_get_network_details(self, NG):
490 net_id = net_recv['network']['id']
491 r = self.client.get_network_details(net_id)
492 NG.assert_called_once_with(network_id=net_id)
493 self.assert_dicts_are_equal(r, net_recv['network'])
495 @patch('%s.networks_put' % cyclades_pkg, return_value=FR())
496 def test_update_network_name(self, NP):
497 net_id = net_recv['network']['id']
498 new_name = '%s_new' % net_id
499 self.client.update_network_name(net_id, new_name)
500 NP.assert_called_once_with(
502 json_data=dict(network=dict(name=new_name)))
504 def test_delete_network(self):
505 net_id = net_recv['network']['id']
507 cyclades.CycladesClient, 'networks_delete',
508 return_value=FR()) as ND:
509 self.client.delete_network(net_id)
510 ND.assert_called_once_with(net_id)
512 cyclades.CycladesClient, 'networks_delete',
513 side_effect=ClientError('A 421 Error', 421)):
515 self.client.delete_network(421)
516 except ClientError as err:
517 self.assertEqual(err.status, 421)
518 self.assertEqual(err.details, [
519 'Network may be still connected to at least one server'])
521 @patch('%s.floating_ip_pools_get' % cyclades_pkg, return_value=FR())
522 def test_get_floating_ip_pools(self, get):
523 r = self.client.get_floating_ip_pools()
524 self.assert_dicts_are_equal(r, FR.json)
525 self.assertEqual(get.mock_calls[-1], call())
527 @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
528 def test_get_floating_ips(self, get):
529 r = self.client.get_floating_ips()
530 self.assert_dicts_are_equal(r, FR.json)
531 self.assertEqual(get.mock_calls[-1], call())
533 @patch('%s.floating_ips_post' % cyclades_pkg, return_value=FR())
534 def test_alloc_floating_ip(self, post):
535 FR.json = dict(floating_ip=dict(
543 (None, 'Iwannanip')):
544 r = self.client.alloc_floating_ip(*args)
546 self.assert_dicts_are_equal(r, FR.json['floating_ip'])
549 json_data['pool'] = pool
551 json_data['address'] = address
552 self.assertEqual(post.mock_calls[-1], call(json_data))
554 @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
555 def test_get_floating_ip(self, get):
556 FR.json = dict(floating_ip=dict(
562 self.assertRaises(AssertionError, self.client.get_floating_ip, None)
564 r = self.client.get_floating_ip(fip)
565 self.assert_dicts_are_equal(r, FR.json['floating_ip'])
566 self.assertEqual(get.mock_calls[-1], call(fip))
568 @patch('%s.floating_ips_delete' % cyclades_pkg, return_value=FR())
569 def test_delete_floating_ip(self, delete):
570 self.assertRaises(AssertionError, self.client.delete_floating_ip, None)
572 r = self.client.delete_floating_ip(fip)
573 self.assert_dicts_are_equal(r, FR.headers)
574 self.assertEqual(delete.mock_calls[-1], call(fip))
576 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
577 def test_attach_floating_ip(self, spost):
578 vmid, addr = 42, 'anIpAddress'
580 ValueError: ['not a server id', addr],
581 TypeError: [None, addr],
582 AssertionError: [vmid, None],
583 AssertionError: [vmid, '']}.items():
585 err, self.client.attach_floating_ip, *args)
586 r = self.client.attach_floating_ip(vmid, addr)
587 self.assert_dicts_are_equal(r, FR.headers)
588 expected = dict(addFloatingIp=dict(address=addr))
589 self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
591 @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
592 def test_detach_floating_ip(self, spost):
593 vmid, addr = 42, 'anIpAddress'
595 ValueError: ['not a server id', addr],
596 TypeError: [None, addr],
597 AssertionError: [vmid, None],
598 AssertionError: [vmid, '']}.items():
600 err, self.client.detach_floating_ip, *args)
601 r = self.client.detach_floating_ip(vmid, addr)
602 self.assert_dicts_are_equal(r, FR.headers)
603 expected = dict(removeFloatingIp=dict(address=addr))
604 self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
607 if __name__ == '__main__':
609 from kamaki.clients.test import runTestCase
611 if not argv[1:] or argv[1] == 'CycladesClient':
613 runTestCase(CycladesNetworkClient, 'Cyclades Client', argv[2:])
614 if not argv[1:] or argv[1] == 'CycladesNetworkClient':
616 runTestCase(CycladesNetworkClient, 'CycladesNetwork Client', argv[2:])
617 if not argv[1:] or argv[1] == 'CycladesRestClient':
619 runTestCase(CycladesRestClient, 'CycladesRest Client', argv[2:])
621 print('TestCase %s not found' % argv[1])