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.compute import ComputeClient, ComputeRestClient
40 from kamaki.clients import ClientError
43 rest_pkg = 'kamaki.clients.compute.rest_api.ComputeRestClient'
44 compute_pkg = 'kamaki.clients.compute.ComputeClient'
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")]))
103 """FR stands for Fake Response"""
114 class ComputeRest(TestCase):
116 """Set up a ComputesRest thorough test"""
118 self.url = 'http://cyclades.example.com'
119 self.token = 'cyc14d3s70k3n'
120 self.client = ComputeRestClient(self.url, self.token)
125 @patch('%s.get' % rest_pkg, return_value=FR())
126 def _test_get(self, service, get):
128 ('', '%s_id' % service),
132 (srv_id, command, success, kwargs) = args
133 method = getattr(self.client, '%s_get' % service)
134 method(*args[:3], **kwargs)
135 srv_str = '/%s' % srv_id if srv_id else ''
136 cmd_str = '/%s' % command if command else ''
137 self.assertEqual(get.mock_calls[-1], call(
138 '/%s%s%s' % (service, srv_str, cmd_str),
142 def test_servers_get(self):
143 self._test_get('servers')
145 def test_flavors_get(self):
146 self._test_get('flavors')
148 def test_images_get(self):
149 self._test_get('images')
151 @patch('%s.delete' % rest_pkg, return_value=FR())
152 def _test_delete(self, service, delete):
154 ('', '%s_id' % service),
158 (srv_id, command, success, kwargs) = args
159 method = getattr(self.client, '%s_delete' % service)
160 method(*args[:3], **kwargs)
161 vm_str = '/%s' % srv_id if srv_id else ''
162 cmd_str = '/%s' % command if command else ''
163 self.assertEqual(delete.mock_calls[-1], call(
164 '/%s%s%s' % (service, vm_str, cmd_str),
168 def test_servers_delete(self):
169 self._test_delete('servers')
171 def test_images_delete(self):
172 self._test_delete('images')
174 @patch('%s.set_header' % rest_pkg)
175 @patch('%s.post' % rest_pkg, return_value=FR())
176 def _test_post(self, service, post, SH):
178 ('', '%s_id' % service),
180 (None, [dict(json="data"), dict(data="json")]),
183 (srv_id, command, json_data, success, kwargs) = args
184 method = getattr(self.client, '%s_post' % service)
185 method(*args[:4], **kwargs)
186 vm_str = '/%s' % srv_id if srv_id else ''
187 cmd_str = '/%s' % command if command else ''
189 json_data = dumps(json_data)
190 self.assertEqual(SH.mock_calls[-2:], [
191 call('Content-Type', 'application/json'),
192 call('Content-Length', len(json_data))])
193 self.assertEqual(post.mock_calls[-1], call(
194 '/%s%s%s' % (service, vm_str, cmd_str),
195 data=json_data, success=success,
198 def test_servers_post(self):
199 self._test_post('servers')
201 def test_images_post(self):
202 self._test_post('images')
204 @patch('%s.set_header' % rest_pkg)
205 @patch('%s.put' % rest_pkg, return_value=FR())
206 def _test_put(self, service, put, SH):
208 ('', '%s_id' % service),
210 (None, [dict(json="data"), dict(data="json")]),
213 (server_id, command, json_data, success, kwargs) = args
214 method = getattr(self.client, '%s_put' % service)
215 method(*args[:4], **kwargs)
216 vm_str = '/%s' % server_id if server_id else ''
217 cmd_str = '/%s' % command if command else ''
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 self.assertEqual(put.mock_calls[-1], call(
224 '/%s%s%s' % (service, vm_str, cmd_str),
225 data=json_data, success=success,
228 def test_servers_put(self):
229 self._test_put('servers')
231 def test_images_put(self):
232 self._test_put('images')
235 class Compute(TestCase):
237 def assert_dicts_are_equal(self, d1, d2):
238 for k, v in d1.items():
239 self.assertTrue(k in d2)
240 if isinstance(v, dict):
241 self.assert_dicts_are_equal(v, d2[k])
243 self.assertEqual(unicode(v), unicode(d2[k]))
245 """Set up a Cyclades thorough test"""
247 self.url = 'http://cyclades.example.com'
248 self.token = 'cyc14d3s70k3n'
249 self.client = ComputeClient(self.url, self.token)
256 '%s.get_image_details' % compute_pkg,
257 return_value=img_recv['image'])
258 def test_create_server(self, GID):
260 ComputeClient, 'servers_post',
261 side_effect=ClientError(
262 'REQUEST ENTITY TOO LARGE',
266 self.client.create_server,
267 vm_name, fid, img_ref)
270 ComputeClient, 'servers_post',
271 return_value=FR()) as post:
272 r = self.client.create_server(vm_name, fid, img_ref)
273 self.assertEqual(r, FR.json['server'])
274 self.assertEqual(GID.mock_calls[-1], call(img_ref))
275 self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
276 prsn = 'Personality string (does not work with real servers)'
277 self.client.create_server(vm_name, fid, img_ref, prsn)
278 expected = dict(server=dict(vm_send['server']))
279 expected['server']['personality'] = prsn
280 self.assertEqual(post.mock_calls[-1], call(json_data=expected))
282 @patch('%s.servers_get' % compute_pkg, return_value=FR())
283 def test_list_servers(self, SG):
285 for detail in (False, True):
286 r = self.client.list_servers(detail)
287 self.assertEqual(SG.mock_calls[-1], call(
288 command='detail' if detail else ''))
289 for i, vm in enumerate(vm_list['servers']['values']):
290 self.assert_dicts_are_equal(r[i], vm)
291 self.assertEqual(i + 1, len(r))
293 @patch('%s.servers_get' % compute_pkg, return_value=FR())
294 def test_get_server_details(self, SG):
295 vm_id = vm_recv['server']['id']
296 r = self.client.get_server_details(vm_id)
297 SG.assert_called_once_with(vm_id)
298 self.assert_dicts_are_equal(r, vm_recv['server'])
300 @patch('%s.servers_put' % compute_pkg, return_value=FR())
301 def test_update_server_name(self, SP):
302 vm_id = vm_recv['server']['id']
303 new_name = vm_name + '_new'
304 self.client.update_server_name(vm_id, new_name)
305 SP.assert_called_once_with(vm_id, json_data=dict(
306 server=dict(name=new_name)))
308 @patch('%s.servers_post' % compute_pkg, return_value=FR())
309 def test_reboot_server(self, SP):
310 vm_id = vm_recv['server']['id']
311 for hard in (None, True):
312 self.client.reboot_server(vm_id, hard=hard)
313 self.assertEqual(SP.mock_calls[-1], call(
315 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
317 @patch('%s.servers_put' % compute_pkg, return_value=FR())
318 def test_create_server_metadata(self, SP):
319 vm_id = vm_recv['server']['id']
320 metadata = dict(m1='v1', m2='v2', m3='v3')
321 FR.json = dict(meta=vm_recv['server'])
322 for k, v in metadata.items():
323 r = self.client.create_server_metadata(vm_id, k, v)
324 self.assert_dicts_are_equal(r, vm_recv['server'])
325 self.assertEqual(SP.mock_calls[-1], call(
326 vm_id, 'meta/%s' % k,
327 json_data=dict(meta={k: v}), success=201))
329 @patch('%s.servers_get' % compute_pkg, return_value=FR())
330 def test_get_server_metadata(self, SG):
331 vm_id = vm_recv['server']['id']
332 metadata = dict(m1='v1', m2='v2', m3='v3')
333 FR.json = dict(metadata=dict(values=metadata))
334 r = self.client.get_server_metadata(vm_id)
335 SG.assert_called_once_with(vm_id, '/meta')
336 self.assert_dicts_are_equal(r, metadata)
338 for k, v in metadata.items():
339 FR.json = dict(meta={k: v})
340 r = self.client.get_server_metadata(vm_id, k)
341 self.assert_dicts_are_equal(r, {k: v})
342 self.assertEqual(SG.mock_calls[-1], call(vm_id, '/meta/%s' % k))
344 @patch('%s.servers_post' % compute_pkg, return_value=FR())
345 def test_update_server_metadata(self, SP):
346 vm_id = vm_recv['server']['id']
347 metadata = dict(m1='v1', m2='v2', m3='v3')
348 FR.json = dict(metadata=metadata)
349 r = self.client.update_server_metadata(vm_id, **metadata)
350 self.assert_dicts_are_equal(r, metadata)
351 SP.assert_called_once_with(
353 json_data=dict(metadata=metadata), success=201)
355 @patch('%s.servers_delete' % compute_pkg, return_value=FR())
356 def test_delete_server_metadata(self, SD):
357 vm_id = vm_recv['server']['id']
359 self.client.delete_server_metadata(vm_id, key)
360 SD.assert_called_once_with(vm_id, 'meta/' + key)
362 @patch('%s.flavors_get' % compute_pkg, return_value=FR())
363 def test_list_flavors(self, FG):
364 FR.json = flavor_list
365 for cmd in ('', 'detail'):
366 r = self.client.list_flavors(detail=(cmd == 'detail'))
367 self.assertEqual(FG.mock_calls[-1], call(command=cmd))
368 self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
370 @patch('%s.flavors_get' % compute_pkg, return_value=FR())
371 def test_get_flavor_details(self, FG):
372 FR.json = dict(flavor=flavor_list['flavors'])
373 r = self.client.get_flavor_details(fid)
374 FG.assert_called_once_with(fid)
375 self.assert_dicts_are_equal(r, flavor_list['flavors'])
377 @patch('%s.images_get' % compute_pkg, return_value=FR())
378 def test_list_images(self, IG):
380 for cmd in ('', 'detail'):
381 r = self.client.list_images(detail=(cmd == 'detail'))
382 self.assertEqual(IG.mock_calls[-1], call(command=cmd))
383 expected = img_list['images']['values']
384 for i in range(len(r)):
385 self.assert_dicts_are_equal(expected[i], r[i])
387 @patch('%s.images_get' % compute_pkg, return_value=FR())
388 def test_get_image_details(self, IG):
390 r = self.client.get_image_details(img_ref)
391 IG.assert_called_once_with(img_ref)
392 self.assert_dicts_are_equal(r, img_recv['image'])
394 @patch('%s.images_get' % compute_pkg, return_value=FR())
395 def test_get_image_metadata(self, IG):
396 for key in ('', '50m3k3y'):
397 FR.json = dict(meta=img_recv['image']) if (
398 key) else dict(metadata=dict(values=img_recv['image']))
399 r = self.client.get_image_metadata(img_ref, key)
400 self.assertEqual(IG.mock_calls[-1], call(
402 '/meta%s' % (('/%s' % key) if key else '')))
403 self.assert_dicts_are_equal(img_recv['image'], r)
405 @patch('%s.servers_delete' % compute_pkg, return_value=FR())
406 def test_delete_server(self, SD):
407 vm_id = vm_recv['server']['id']
408 self.client.delete_server(vm_id)
409 SD.assert_called_once_with(vm_id)
411 @patch('%s.images_delete' % compute_pkg, return_value=FR())
412 def test_delete_image(self, ID):
413 self.client.delete_image(img_ref)
414 ID.assert_called_once_with(img_ref)
416 @patch('%s.images_put' % compute_pkg, return_value=FR())
417 def test_create_image_metadata(self, IP):
418 (key, val) = ('k1', 'v1')
419 FR.json = dict(meta=img_recv['image'])
420 r = self.client.create_image_metadata(img_ref, key, val)
421 IP.assert_called_once_with(
422 img_ref, 'meta/%s' % key,
423 json_data=dict(meta={key: val}))
424 self.assert_dicts_are_equal(r, img_recv['image'])
426 @patch('%s.images_post' % compute_pkg, return_value=FR())
427 def test_update_image_metadata(self, IP):
428 metadata = dict(m1='v1', m2='v2', m3='v3')
429 FR.json = dict(metadata=metadata)
430 r = self.client.update_image_metadata(img_ref, **metadata)
431 IP.assert_called_once_with(
433 json_data=dict(metadata=metadata))
434 self.assert_dicts_are_equal(r, metadata)
436 @patch('%s.images_delete' % compute_pkg, return_value=FR())
437 def test_delete_image_metadata(self, ID):
439 self.client.delete_image_metadata(img_ref, key)
440 ID.assert_called_once_with(img_ref, '/meta/%s' % key)
443 if __name__ == '__main__':
445 from kamaki.clients.test import runTestCase
447 if not argv[1:] or argv[1] == 'Compute':
449 runTestCase(Compute, 'Compute Client', argv[2:])
450 if not argv[1:] or argv[1] == 'ComputeRest':
452 runTestCase(ComputeRest, 'ComputeRest Client', argv[2:])
454 print('TestCase %s not found' % argv[1])