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.
33 from mock import patch, Mock, call
34 from unittest import TestCase
35 from json import loads
37 from kamaki.clients import Client, ClientError
38 from kamaki.clients.cyclades import CycladesClient
39 from kamaki.clients.cyclades_rest_api import CycladesClientApi
41 img_ref = "1m4g3-r3f3r3nc3"
44 vm_send = dict(server=dict(
48 metadata=dict(os="debian", users="root")))
49 vm_recv = dict(server=dict(
51 updated="2013-03-01T10:04:00.637152+00:00",
55 created="2013-03-01T10:04:00.087324+00:00",
57 adminPass="n0n3sh@11p@55",
61 metadata=dict(values=dict(os="debian", users="root"))))
62 img_recv = dict(image=dict(
64 updated="2013-02-26T11:10:14+00:00",
66 created="2013-02-26T11:03:29+00:00",
69 metadata=dict(values=dict(
70 partition_table="msdos",
78 description="Debian 6.0.7 (Squeeze) Base System"))))
79 vm_list = dict(servers=dict(values=[
80 dict(name='n1', id=1),
81 dict(name='n2', id=2)]))
82 flavor_list = dict(flavors=dict(values=[
83 dict(id=41, name="C1R1024D20"),
84 dict(id=42, name="C1R1024D40"),
85 dict(id=43, name="C1R1028D20")]))
86 img_list = dict(images=dict(values=[
87 dict(name="maelstrom", id="0fb03e45-7d5a-4515-bd4e-e6bbf6457f06"),
88 dict(name="edx_saas", id="1357163d-5fd8-488e-a117-48734c526206"),
89 dict(name="Debian_Wheezy_Base", id="1f8454f0-8e3e-4b6c-ab8e-5236b728dffe"),
90 dict(name="CentOS", id="21894b48-c805-4568-ac8b-7d4bb8eb533d"),
91 dict(name="Ubuntu Desktop", id="37bc522c-c479-4085-bfb9-464f9b9e2e31"),
92 dict(name="Ubuntu 12.10", id="3a24fef9-1a8c-47d1-8f11-e07bd5e544fd"),
93 dict(name="Debian Base", id="40ace203-6254-4e17-a5cb-518d55418a7d"),
94 dict(name="ubuntu_bundled", id="5336e265-5c7c-4127-95cb-2bf832a79903")]))
95 net_send = dict(network=dict(dhcp=False, name='someNet'))
96 net_recv = dict(network=dict(
98 updated="2013-03-05T15:04:51.758780+00:00",
100 created="2013-03-05T15:04:51.758728+00:00",
106 cidr="192.168.1.0/24",
109 attachments=dict(values=[dict(name='att1'), dict(name='att2')])))
110 net_list = dict(networks=dict(values=[
111 dict(id=1, name='n1'),
112 dict(id=2, name='n2'),
113 dict(id=3, name='n3')]))
117 """FR stands for Fake Response"""
127 khttp = 'kamaki.clients.connection.kamakicon.KamakiHTTPConnection'
128 cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
131 class Cyclades(TestCase):
133 def assert_dicts_are_equal(self, d1, d2):
134 for k, v in d1.items():
135 self.assertTrue(k in d2)
136 if isinstance(v, dict):
137 self.assert_dicts_are_equal(v, d2[k])
139 self.assertEqual(unicode(v), unicode(d2[k]))
141 """Set up a Cyclades thorough test"""
143 self.url = 'http://cyclades.example.com'
144 self.token = 'cyc14d3s70k3n'
145 self.client = CycladesClient(self.url, self.token)
146 from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
147 self.C = KamakiHTTPConnection
153 def test_create_server(self):
154 self.client.get_image_details = Mock(return_value=img_recv['image'])
155 with patch.object(Client, 'request', side_effect=ClientError(
156 'REQUEST ENTITY TOO LARGE',
160 self.client.create_server,
161 vm_name, fid, img_ref)
166 return_value=FR()) as perform_req:
169 self.client.create_server,
170 vm_name, fid, img_ref)
172 r = self.client.create_server(vm_name, fid, img_ref)
173 self.assertEqual(self.client.http_client.url, self.url)
174 self.assertEqual(self.client.http_client.path, '/servers')
175 (method, data, a_headers, a_params) = perform_req.call_args[0]
176 self.assert_dicts_are_equal(loads(data), vm_send)
177 self.assert_dicts_are_equal(r, vm_recv['server'])
178 prsn = 'Personality string (does not work with real servers)'
179 self.client.create_server(vm_name, fid, img_ref, prsn)
180 (method, data, a_headers, a_params) = perform_req.call_args[0]
182 self.assertTrue('personality' in data['server'])
183 self.assertEqual(prsn, data['server']['personality'])
185 def test_list_servers(self):
190 return_value=FR()) as perform_req:
191 r = self.client.list_servers()
192 self.assertEqual(self.client.http_client.url, self.url)
193 self.assertEqual(self.client.http_client.path, '/servers')
194 (method, data, a_headers, a_params) = perform_req.call_args[0]
195 self.assert_dicts_are_equal(dict(values=r), vm_list['servers'])
196 r = self.client.list_servers(detail=True)
197 self.assertEqual(self.client.http_client.url, self.url)
198 self.assertEqual(self.client.http_client.path, '/servers/detail')
202 return_value=FR()) as servers_get:
203 self.client.list_servers(changes_since=True)
204 self.assertTrue(servers_get.call_args[1]['changes_since'])
206 @patch('%s.perform_request' % khttp, return_value=FR())
207 def test_get_server_details(self, PR):
208 vm_id = vm_recv['server']['id']
209 r = self.client.get_server_details(vm_id)
210 self.assertEqual(self.client.http_client.url, self.url)
211 self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
212 self.assert_dicts_are_equal(r, vm_recv['server'])
214 @patch('%s.perform_request' % khttp, return_value=FR())
215 def test_update_server_name(self, PR):
216 vm_id = vm_recv['server']['id']
217 new_name = vm_name + '_new'
219 self.client.update_server_name(vm_id, new_name)
220 self.assertEqual(self.client.http_client.url, self.url)
221 self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
222 (method, data, a_headers, a_params) = PR.call_args[0]
223 self.assert_dicts_are_equal(
224 dict(server=dict(name=new_name)),
227 @patch('%s.perform_request' % khttp, return_value=FR())
228 def test_reboot_server(self, PR):
229 vm_id = vm_recv['server']['id']
231 self.client.reboot_server(vm_id)
232 self.assertEqual(self.client.http_client.url, self.url)
234 self.client.http_client.path,
235 '/servers/%s/action' % vm_id)
236 (method, data, a_headers, a_params) = PR.call_args[0]
237 self.assert_dicts_are_equal(
238 dict(reboot=dict(type='SOFT')),
241 @patch('%s.perform_request' % khttp, return_value=FR())
242 def test_create_server_metadata(self, PR):
243 vm_id = vm_recv['server']['id']
244 metadata = dict(m1='v1', m2='v2', m3='v3')
245 FR.json = dict(meta=vm_recv['server'])
248 self.client.create_server_metadata,
249 vm_id, 'key', 'value')
251 for k, v in metadata.items():
252 r = self.client.create_server_metadata(vm_id, k, v)
253 self.assertEqual(self.client.http_client.url, self.url)
255 self.client.http_client.path,
256 '/servers/%s/meta/%s' % (vm_id, k))
257 (method, data, a_headers, a_params) = PR.call_args[0]
258 self.assertEqual(dict(meta={k: v}), loads(data))
259 self.assert_dicts_are_equal(r, vm_recv['server'])
261 @patch('%s.perform_request' % khttp, return_value=FR())
262 def test_get_server_metadata(self, PR):
263 vm_id = vm_recv['server']['id']
264 metadata = dict(m1='v1', m2='v2', m3='v3')
265 FR.json = dict(metadata=dict(values=metadata))
266 r = self.client.get_server_metadata(vm_id)
267 self.assertEqual(self.client.http_client.url, self.url)
269 self.client.http_client.path,
270 '/servers/%s/meta' % vm_id)
271 self.assert_dicts_are_equal(r, metadata)
273 for k, v in metadata.items():
274 FR.json = dict(meta={k: v})
275 r = self.client.get_server_metadata(vm_id, k)
276 self.assertEqual(self.client.http_client.url, self.url)
278 self.client.http_client.path,
279 '/servers/%s/meta/%s' % (vm_id, k))
280 self.assert_dicts_are_equal(r, {k: v})
282 @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
283 def test_update_server_metadata(self, servers_post):
284 vm_id = vm_recv['server']['id']
285 metadata = dict(m1='v1', m2='v2', m3='v3')
286 FR.json = dict(metadata=metadata)
287 r = self.client.update_server_metadata(vm_id, **metadata)
288 self.assert_dicts_are_equal(r, metadata)
289 (called_id, cmd) = servers_post.call_args[0]
290 self.assertEqual(called_id, vm_id)
291 self.assertEqual(cmd, 'meta')
292 data = servers_post.call_args[1]['json_data']
293 self.assert_dicts_are_equal(data, dict(metadata=metadata))
295 @patch('%s.servers_delete' % cyclades_pkg, return_value=FR())
296 def test_delete_server_metadata(self, servers_delete):
297 vm_id = vm_recv['server']['id']
299 self.client.delete_server_metadata(vm_id, key)
300 self.assertEqual((vm_id, 'meta/' + key), servers_delete.call_args[0])
302 @patch('%s.perform_request' % khttp, return_value=FR())
303 def test_list_flavors(self, PR):
304 FR.json = flavor_list
305 r = self.client.list_flavors()
306 self.assertEqual(self.client.http_client.url, self.url)
307 self.assertEqual(self.client.http_client.path, '/flavors')
308 (method, data, a_headers, a_params) = PR.call_args[0]
309 self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
310 r = self.client.list_flavors(detail=True)
311 self.assertEqual(self.client.http_client.url, self.url)
312 self.assertEqual(self.client.http_client.path, '/flavors/detail')
314 @patch('%s.perform_request' % khttp, return_value=FR())
315 def test_get_flavor_details(self, PR):
316 FR.json = dict(flavor=flavor_list['flavors'])
317 r = self.client.get_flavor_details(fid)
318 self.assertEqual(self.client.http_client.url, self.url)
319 self.assertEqual(self.client.http_client.path, '/flavors/%s' % fid)
320 self.assert_dicts_are_equal(r, flavor_list['flavors'])
322 @patch('%s.perform_request' % khttp, return_value=FR())
323 def test_list_images(self, PR):
325 r = self.client.list_images()
326 self.assertEqual(self.client.http_client.url, self.url)
327 self.assertEqual(self.client.http_client.path, '/images')
328 expected = img_list['images']['values']
329 for i in range(len(r)):
330 self.assert_dicts_are_equal(expected[i], r[i])
331 self.client.list_images(detail=True)
332 self.assertEqual(self.client.http_client.url, self.url)
333 self.assertEqual(self.client.http_client.path, '/images/detail')
335 @patch('%s.perform_request' % khttp, return_value=FR())
336 def test_get_image_details(self, PR):
338 r = self.client.get_image_details(img_ref)
339 self.assertEqual(self.client.http_client.url, self.url)
340 self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
341 self.assert_dicts_are_equal(r, img_recv['image'])
343 @patch('%s.images_get' % cyclades_pkg, return_value=FR())
344 def test_get_image_metadata(self, IG):
345 FR.json = dict(metadata=dict(values=img_recv['image']))
346 r = self.client.get_image_metadata(img_ref)
347 self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta'))
348 self.assert_dicts_are_equal(img_recv['image'], r)
349 FR.json = dict(meta=img_recv['image'])
351 self.client.get_image_metadata(img_ref, key)
352 self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta/%s' % key))
354 @patch('%s.perform_request' % khttp, return_value=FR())
355 def test_shutdown_server(self, PR):
356 vm_id = vm_recv['server']['id']
358 self.client.shutdown_server(vm_id)
359 self.assertEqual(self.client.http_client.url, self.url)
361 self.client.http_client.path,
362 '/servers/%s/action' % vm_id)
365 ('post', '{"shutdown": {}}', {}, {}))
367 @patch('%s.perform_request' % khttp, return_value=FR())
368 def test_start_server(self, PR):
369 vm_id = vm_recv['server']['id']
371 self.client.start_server(vm_id)
372 self.assertEqual(self.client.http_client.url, self.url)
374 self.client.http_client.path,
375 '/servers/%s/action' % vm_id)
376 self.assertEqual(PR.call_args[0], ('post', '{"start": {}}', {}, {}))
378 @patch('%s.perform_request' % khttp, return_value=FR())
379 def test_get_server_console(self, PR):
380 cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
382 vm_id = vm_recv['server']['id']
383 r = self.client.get_server_console(vm_id)
384 self.assertEqual(self.client.http_client.url, self.url)
386 self.client.http_client.path,
387 '/servers/%s/action' % vm_id)
388 self.assert_dicts_are_equal(cnsl['console'], r)
391 ('post', '{"console": {"type": "vnc"}}', {}, {}))
393 def test_get_firewall_profile(self):
394 vm_id = vm_recv['server']['id']
396 ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
399 'get_server_details',
400 return_value=ret) as GSD:
401 r = self.client.get_firewall_profile(vm_id)
402 self.assertEqual(r, v)
403 self.assertEqual(GSD.call_args[0], (vm_id,))
404 ret['attachments']['values'][0].pop('firewallProfile')
407 self.client.get_firewall_profile,
410 @patch('%s.perform_request' % khttp, return_value=FR())
411 def test_set_firewall_profile(self, PR):
412 vm_id = vm_recv['server']['id']
415 self.client.set_firewall_profile(vm_id, v)
416 self.assertEqual(self.client.http_client.url, self.url)
418 self.client.http_client.path,
419 '/servers/%s/action' % vm_id)
420 self.assertEqual(PR.call_args[0], (
422 '{"firewallProfile": {"profile": "%s"}}' % v,
426 @patch('%s.perform_request' % khttp, return_value=FR())
427 def test_get_server_stats(self, PR):
428 vm_id = vm_recv['server']['id']
429 stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
430 FR.json = dict(stats=stats)
431 r = self.client.get_server_stats(vm_id)
432 self.assertEqual(self.client.http_client.url, self.url)
434 self.client.http_client.path,
435 '/servers/%s/stats' % vm_id)
436 self.assert_dicts_are_equal(stats, r)
438 @patch('%s.perform_request' % khttp, return_value=FR())
439 def test_create_network(self, PR):
440 net_name = net_send['network']['name']
444 cidr='192.168.0.0/24',
445 gateway='192.168.0.1',
448 test_args = dict(full_args)
449 test_args.update(dict(empty=None, full=None))
450 for arg, val in test_args.items():
451 kwargs = {} if arg == 'empty' else full_args if (
452 arg == 'full') else {arg: val}
453 r = self.client.create_network(net_name, **kwargs)
454 self.assertEqual(self.client.http_client.url, self.url)
456 self.client.http_client.path,
458 self.assert_dicts_are_equal(r, net_recv['network'])
459 data = PR.call_args[0][1]
460 expected = dict(network=dict(net_send['network']))
461 expected['network'].update(kwargs)
462 self.assert_dicts_are_equal(loads(data), expected)
464 @patch('%s.perform_request' % khttp, return_value=FR())
465 def test_connect_server(self, PR):
466 vm_id = vm_recv['server']['id']
467 net_id = net_recv['network']['id']
469 self.client.connect_server(vm_id, net_id)
470 self.assertEqual(self.client.http_client.url, self.url)
472 self.client.http_client.path,
473 '/networks/%s/action' % net_id)
476 ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
478 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
479 def test_disconnect_server(self, NP):
480 vm_id = vm_recv['server']['id']
481 net_id = net_recv['network']['id']
482 nic_id = 'nic-%s-%s' % (net_id, vm_id)
484 dict(id=nic_id, network_id=net_id),
485 dict(id='another-nic-id', network_id='another-net-id'),
486 dict(id=nic_id * 2, network_id=net_id * 2)]
490 return_value=vm_nics) as LSN:
491 r = self.client.disconnect_server(vm_id, nic_id)
492 self.assertEqual(r, 1)
493 self.assertEqual(LSN.call_args[0], (vm_id,))
494 self.assertEqual(NP.call_args[0], (net_id, 'action'))
497 dict(json_data=dict(remove=dict(attachment=nic_id))))
499 @patch('%s.perform_request' % khttp, return_value=FR())
500 def test_list_server_nics(self, PR):
501 vm_id = vm_recv['server']['id']
502 nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
504 r = self.client.list_server_nics(vm_id)
505 self.assertEqual(self.client.http_client.url, self.url)
507 self.client.http_client.path,
508 '/servers/%s/ips' % vm_id)
509 expected = nics['addresses']['values']
510 for i in range(len(r)):
511 self.assert_dicts_are_equal(r[i], expected[i])
513 @patch('%s.perform_request' % khttp, return_value=FR())
514 def test_list_networks(self, PR):
516 r = self.client.list_networks()
517 self.assertEqual(self.client.http_client.url, self.url)
518 self.assertEqual(self.client.http_client.path, '/networks')
519 expected = net_list['networks']['values']
520 for i in range(len(r)):
521 self.assert_dicts_are_equal(expected[i], r[i])
522 self.client.list_networks(detail=True)
523 self.assertEqual(self.client.http_client.url, self.url)
524 self.assertEqual(self.client.http_client.path, '/networks/detail')
526 @patch('%s.perform_request' % khttp, return_value=FR())
527 def test_list_network_nics(self, PR):
528 net_id = net_recv['network']['id']
530 r = self.client.list_network_nics(net_id)
531 self.assertEqual(self.client.http_client.url, self.url)
533 self.client.http_client.path,
534 '/networks/%s' % net_id)
535 expected = net_recv['network']['attachments']['values']
536 for i in range(len(r)):
537 self.assert_dicts_are_equal(r[i], expected[i])
539 @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
540 def test_disconnect_network_nics(self, NP):
541 net_id = net_recv['network']['id']
542 nics = ['nic1', 'nic2', 'nic3']
546 return_value=nics) as lnn:
547 self.client.disconnect_network_nics(net_id)
548 lnn.assert_called_once_with(net_id)
549 for i in range(len(nics)):
550 expected = call(net_id, 'action', json_data=dict(
551 remove=dict(attachment=nics[i])))
552 self.assertEqual(expected, NP.mock_calls[i])
554 @patch('%s.perform_request' % khttp, return_value=FR())
555 def test_get_network_details(self, PR):
557 net_id = net_recv['network']['id']
558 r = self.client.get_network_details(net_id)
559 self.assertEqual(self.client.http_client.url, self.url)
561 self.client.http_client.path,
562 '/networks/%s' % net_id)
563 self.assert_dicts_are_equal(r, net_recv['network'])
565 @patch('%s.perform_request' % khttp, return_value=FR())
566 def test_update_network_name(self, PR):
567 net_id = net_recv['network']['id']
568 new_name = '%s_new' % net_id
570 self.client.update_network_name(net_id, new_name)
571 self.assertEqual(self.client.http_client.url, self.url)
572 self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
573 (method, data, a_headers, a_params) = PR.call_args[0]
574 self.assert_dicts_are_equal(
575 dict(network=dict(name=new_name)),
578 @patch('%s.perform_request' % khttp, return_value=FR())
579 def test_delete_server(self, PR):
580 vm_id = vm_recv['server']['id']
582 self.client.delete_server(vm_id)
583 self.assertEqual(self.client.http_client.url, self.url)
584 self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
586 @patch('%s.perform_request' % khttp, return_value=FR())
587 def test_delete_image(self, PR):
589 self.client.delete_image(img_ref)
590 self.assertEqual(self.client.http_client.url, self.url)
591 self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
593 @patch('%s.perform_request' % khttp, return_value=FR())
594 def test_delete_network(self, PR):
595 net_id = net_recv['network']['id']
597 self.client.delete_network(net_id)
598 self.assertEqual(self.client.http_client.url, self.url)
599 self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
601 @patch('%s.perform_request' % khttp, return_value=FR())
602 def test_create_image_metadata(self, PR):
603 metadata = dict(m1='v1', m2='v2', m3='v3')
604 FR.json = dict(meta=img_recv['image'])
607 self.client.create_image_metadata,
608 img_ref, 'key', 'value')
610 for k, v in metadata.items():
611 r = self.client.create_image_metadata(img_ref, k, v)
612 self.assertEqual(self.client.http_client.url, self.url)
614 self.client.http_client.path,
615 '/images/%s/meta/%s' % (img_ref, k))
616 (method, data, a_headers, a_params) = PR.call_args[0]
617 self.assertEqual(dict(meta={k: v}), loads(data))
618 self.assert_dicts_are_equal(r, img_recv['image'])
620 @patch('%s.images_post' % cyclades_pkg, return_value=FR())
621 def test_update_image_metadata(self, images_post):
622 metadata = dict(m1='v1', m2='v2', m3='v3')
623 FR.json = dict(metadata=metadata)
624 r = self.client.update_image_metadata(img_ref, **metadata)
625 self.assert_dicts_are_equal(r, metadata)
626 (called_id, cmd) = images_post.call_args[0]
627 self.assertEqual(called_id, img_ref)
628 self.assertEqual(cmd, 'meta')
629 data = images_post.call_args[1]['json_data']
630 self.assert_dicts_are_equal(data, dict(metadata=metadata))
632 @patch('%s.images_delete' % cyclades_pkg, return_value=FR())
633 def test_delete_image_metadata(self, images_delete):
635 self.client.delete_image_metadata(img_ref, key)
637 (img_ref, '/meta/' + key),
638 images_delete.call_args[0])
640 if __name__ == '__main__':
642 from kamaki.clients.test import runTestCase
643 runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])