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, ComputeClientApi
40 from kamaki.clients import ClientError
43 rest_pkg = 'kamaki.clients.compute.rest_api.ComputeClientApi'
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 ComputeRestApi(TestCase):
116 """Set up a ComputesRestApi thorough test"""
118 self.url = 'http://cyclades.example.com'
119 self.token = 'cyc14d3s70k3n'
120 self.client = ComputeClientApi(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_servers_post(self, post, SH):
177 vm_id = vm_recv['server']['id']
181 (None, [dict(json="data"), dict(data="json")]),
184 (server_id, command, json_data, success, kwargs) = args
186 self.client.servers_post(*args[:4], **kwargs)
187 vm_str = '/%s' % server_id if server_id else ''
188 cmd_str = '/%s' % command if command else ''
190 json_data = dumps(json_data)
191 self.assertEqual(SH.mock_calls[-2:], [
192 call('Content-Type', 'application/json'),
193 call('Content-Length', len(json_data))])
194 self.assertEqual(post.mock_calls[-1], call(
195 '/servers%s%s' % (vm_str, cmd_str),
196 data=json_data, success=success,
199 @patch('%s.set_header' % rest_pkg)
200 @patch('%s.put' % rest_pkg, return_value=FR())
201 def test_servers_put(self, put, SH):
202 vm_id = vm_recv['server']['id']
206 (None, [dict(json="data"), dict(data="json")]),
209 (server_id, command, json_data, success, kwargs) = args
211 self.client.servers_put(*args[:4], **kwargs)
212 vm_str = '/%s' % server_id if server_id else ''
213 cmd_str = '/%s' % command if command else ''
215 json_data = dumps(json_data)
216 self.assertEqual(SH.mock_calls[-2:], [
217 call('Content-Type', 'application/json'),
218 call('Content-Length', len(json_data))])
219 self.assertEqual(put.mock_calls[-1], call(
220 '/servers%s%s' % (vm_str, cmd_str),
221 data=json_data, success=success,
225 class Compute(TestCase):
227 def assert_dicts_are_equal(self, d1, d2):
228 for k, v in d1.items():
229 self.assertTrue(k in d2)
230 if isinstance(v, dict):
231 self.assert_dicts_are_equal(v, d2[k])
233 self.assertEqual(unicode(v), unicode(d2[k]))
235 """Set up a Cyclades thorough test"""
237 self.url = 'http://cyclades.example.com'
238 self.token = 'cyc14d3s70k3n'
239 self.client = ComputeClient(self.url, self.token)
246 '%s.get_image_details' % compute_pkg,
247 return_value=img_recv['image'])
248 def test_create_server(self, GID):
250 ComputeClient, 'servers_post',
251 side_effect=ClientError(
252 'REQUEST ENTITY TOO LARGE',
256 self.client.create_server,
257 vm_name, fid, img_ref)
260 ComputeClient, 'servers_post',
261 return_value=FR()) as post:
262 r = self.client.create_server(vm_name, fid, img_ref)
263 self.assertEqual(r, FR.json['server'])
264 self.assertEqual(GID.mock_calls[-1], call(img_ref))
265 self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
266 prsn = 'Personality string (does not work with real servers)'
267 self.client.create_server(vm_name, fid, img_ref, prsn)
268 expected = dict(server=dict(vm_send['server']))
269 expected['server']['personality'] = prsn
270 self.assertEqual(post.mock_calls[-1], call(json_data=expected))
272 @patch('%s.servers_get' % compute_pkg, return_value=FR())
273 def test_list_servers(self, SG):
275 for detail in (False, True):
276 r = self.client.list_servers(detail)
277 self.assertEqual(SG.mock_calls[-1], call(
278 command='detail' if detail else ''))
279 for i, vm in enumerate(vm_list['servers']['values']):
280 self.assert_dicts_are_equal(r[i], vm)
281 self.assertEqual(i + 1, len(r))
283 @patch('%s.servers_get' % compute_pkg, return_value=FR())
284 def test_get_server_details(self, SG):
285 vm_id = vm_recv['server']['id']
286 r = self.client.get_server_details(vm_id)
287 SG.assert_called_once_with(vm_id)
288 self.assert_dicts_are_equal(r, vm_recv['server'])
290 @patch('%s.servers_put' % compute_pkg, return_value=FR())
291 def test_update_server_name(self, SP):
292 vm_id = vm_recv['server']['id']
293 new_name = vm_name + '_new'
294 self.client.update_server_name(vm_id, new_name)
295 SP.assert_called_once_with(vm_id, json_data=dict(
296 server=dict(name=new_name)))
298 @patch('%s.servers_post' % compute_pkg, return_value=FR())
299 def test_reboot_server(self, SP):
300 vm_id = vm_recv['server']['id']
301 for hard in (None, True):
302 self.client.reboot_server(vm_id, hard=hard)
303 self.assertEqual(SP.mock_calls[-1], call(
305 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
307 @patch('%s.servers_put' % compute_pkg, return_value=FR())
308 def test_create_server_metadata(self, SP):
309 vm_id = vm_recv['server']['id']
310 metadata = dict(m1='v1', m2='v2', m3='v3')
311 FR.json = dict(meta=vm_recv['server'])
312 for k, v in metadata.items():
313 r = self.client.create_server_metadata(vm_id, k, v)
314 self.assert_dicts_are_equal(r, vm_recv['server'])
315 self.assertEqual(SP.mock_calls[-1], call(
316 vm_id, 'meta/%s' % k,
317 json_data=dict(meta={k: v}), success=201))
319 @patch('%s.servers_get' % compute_pkg, return_value=FR())
320 def test_get_server_metadata(self, SG):
321 vm_id = vm_recv['server']['id']
322 metadata = dict(m1='v1', m2='v2', m3='v3')
323 FR.json = dict(metadata=dict(values=metadata))
324 r = self.client.get_server_metadata(vm_id)
325 SG.assert_called_once_with(vm_id, '/meta')
326 self.assert_dicts_are_equal(r, metadata)
328 for k, v in metadata.items():
329 FR.json = dict(meta={k: v})
330 r = self.client.get_server_metadata(vm_id, k)
331 self.assert_dicts_are_equal(r, {k: v})
332 self.assertEqual(SG.mock_calls[-1], call(vm_id, '/meta/%s' % k))
334 @patch('%s.servers_post' % compute_pkg, return_value=FR())
335 def test_update_server_metadata(self, SP):
336 vm_id = vm_recv['server']['id']
337 metadata = dict(m1='v1', m2='v2', m3='v3')
338 FR.json = dict(metadata=metadata)
339 r = self.client.update_server_metadata(vm_id, **metadata)
340 self.assert_dicts_are_equal(r, metadata)
341 SP.assert_called_once_with(
343 json_data=dict(metadata=metadata), success=201)
345 @patch('%s.servers_delete' % compute_pkg, return_value=FR())
346 def test_delete_server_metadata(self, SD):
347 vm_id = vm_recv['server']['id']
349 self.client.delete_server_metadata(vm_id, key)
350 SD.assert_called_once_with(vm_id, 'meta/' + key)
352 @patch('%s.flavors_get' % compute_pkg, return_value=FR())
353 def test_list_flavors(self, FG):
354 FR.json = flavor_list
355 for cmd in ('', 'detail'):
356 r = self.client.list_flavors(detail=(cmd == 'detail'))
357 self.assertEqual(FG.mock_calls[-1], call(command=cmd))
358 self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
360 @patch('%s.flavors_get' % compute_pkg, return_value=FR())
361 def test_get_flavor_details(self, FG):
362 FR.json = dict(flavor=flavor_list['flavors'])
363 r = self.client.get_flavor_details(fid)
364 FG.assert_called_once_with(fid)
365 self.assert_dicts_are_equal(r, flavor_list['flavors'])
367 @patch('%s.images_get' % compute_pkg, return_value=FR())
368 def test_list_images(self, IG):
370 for cmd in ('', 'detail'):
371 r = self.client.list_images(detail=(cmd == 'detail'))
372 self.assertEqual(IG.mock_calls[-1], call(command=cmd))
373 expected = img_list['images']['values']
374 for i in range(len(r)):
375 self.assert_dicts_are_equal(expected[i], r[i])
377 @patch('%s.images_get' % compute_pkg, return_value=FR())
378 def test_get_image_details(self, IG):
380 r = self.client.get_image_details(img_ref)
381 IG.assert_called_once_with(img_ref)
382 self.assert_dicts_are_equal(r, img_recv['image'])
384 @patch('%s.images_get' % compute_pkg, return_value=FR())
385 def test_get_image_metadata(self, IG):
386 for key in ('', '50m3k3y'):
387 FR.json = dict(meta=img_recv['image']) if (
388 key) else dict(metadata=dict(values=img_recv['image']))
389 r = self.client.get_image_metadata(img_ref, key)
390 self.assertEqual(IG.mock_calls[-1], call(
392 '/meta%s' % (('/%s' % key) if key else '')))
393 self.assert_dicts_are_equal(img_recv['image'], r)
395 @patch('%s.servers_delete' % compute_pkg, return_value=FR())
396 def test_delete_server(self, SD):
397 vm_id = vm_recv['server']['id']
398 self.client.delete_server(vm_id)
399 SD.assert_called_once_with(vm_id)
401 @patch('%s.images_delete' % compute_pkg, return_value=FR())
402 def test_delete_image(self, ID):
403 self.client.delete_image(img_ref)
404 ID.assert_called_once_with(img_ref)
406 @patch('%s.images_put' % compute_pkg, return_value=FR())
407 def test_create_image_metadata(self, IP):
408 (key, val) = ('k1', 'v1')
409 FR.json = dict(meta=img_recv['image'])
410 r = self.client.create_image_metadata(img_ref, key, val)
411 IP.assert_called_once_with(
412 img_ref, 'meta/%s' % key,
413 json_data=dict(meta={key: val}))
414 self.assert_dicts_are_equal(r, img_recv['image'])
416 @patch('%s.images_post' % compute_pkg, return_value=FR())
417 def test_update_image_metadata(self, IP):
418 metadata = dict(m1='v1', m2='v2', m3='v3')
419 FR.json = dict(metadata=metadata)
420 r = self.client.update_image_metadata(img_ref, **metadata)
421 IP.assert_called_once_with(
423 json_data=dict(metadata=metadata))
424 self.assert_dicts_are_equal(r, metadata)
426 @patch('%s.images_delete' % compute_pkg, return_value=FR())
427 def test_delete_image_metadata(self, ID):
429 self.client.delete_image_metadata(img_ref, key)
430 ID.assert_called_once_with(img_ref, '/meta/%s' % key)
433 if __name__ == '__main__':
435 from kamaki.clients.test import runTestCase
437 if not argv[1:] or argv[1] == 'Compute':
439 runTestCase(Compute, 'Compute Client', argv[2:])
440 if not argv[1:] or argv[1] == 'ComputeRestApi':
442 runTestCase(ComputeRestApi, 'ComputeRestApi Client', argv[2:])
444 print('TestCase %s not found' % argv[1])