Prepare PithorRest testcase for implementation
[kamaki] / kamaki / clients / compute / test.py
1 # Copyright 2013 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
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.
15 #
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.
28 #
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
34 from mock import patch, call
35 from unittest import TestCase
36 from itertools import product
37 from json import dumps
38
39 from kamaki.clients.compute import ComputeClient, ComputeRestClient
40 from kamaki.clients import ClientError
41
42
43 rest_pkg = 'kamaki.clients.compute.rest_api.ComputeRestClient'
44 compute_pkg = 'kamaki.clients.compute.ComputeClient'
45
46 img_ref = "1m4g3-r3f3r3nc3"
47 vm_name = "my new VM"
48 fid = 42
49 vm_send = dict(server=dict(
50     flavorRef=fid,
51     name=vm_name,
52     imageRef=img_ref,
53     metadata=dict(os="debian", users="root")))
54 vm_recv = dict(server=dict(
55     status="BUILD",
56     updated="2013-03-01T10:04:00.637152+00:00",
57     hostId="",
58     name=vm_name,
59     imageRef=img_ref,
60     created="2013-03-01T10:04:00.087324+00:00",
61     flavorRef=fid,
62     adminPass="n0n3sh@11p@55",
63     suspended=False,
64     progress=0,
65     id=31173,
66     metadata=dict(values=dict(os="debian", users="root"))))
67 img_recv = dict(image=dict(
68     status="ACTIVE",
69     updated="2013-02-26T11:10:14+00:00",
70     name="Debian Base",
71     created="2013-02-26T11:03:29+00:00",
72     progress=100,
73     id=img_ref,
74     metadata=dict(values=dict(
75         partition_table="msdos",
76         kernel="2.6.32",
77         osfamily="linux",
78         users="root",
79         gui="No GUI",
80         sortorder="1",
81         os="debian",
82         root_partition="1",
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
101
102 class FR(object):
103     """FR stands for Fake Response"""
104     json = vm_recv
105     headers = {}
106     content = json
107     status = None
108     status_code = 200
109
110     def release(self):
111         pass
112
113
114 class ComputeRest(TestCase):
115
116     """Set up a ComputesRest thorough test"""
117     def setUp(self):
118         self.url = 'http://cyclades.example.com'
119         self.token = 'cyc14d3s70k3n'
120         self.client = ComputeRestClient(self.url, self.token)
121
122     def tearDown(self):
123         FR.json = vm_recv
124
125     @patch('%s.get' % rest_pkg, return_value=FR())
126     def _test_get(self, service, get):
127         for args in product(
128                 ('', '%s_id' % service),
129                 ('', 'cmd'),
130                 (200, 204),
131                 ({}, {'k': 'v'})):
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),
139                 success=success,
140                 **kwargs))
141
142     def test_servers_get(self):
143         self._test_get('servers')
144
145     def test_flavors_get(self):
146         self._test_get('flavors')
147
148     def test_images_get(self):
149         self._test_get('images')
150
151     @patch('%s.delete' % rest_pkg, return_value=FR())
152     def _test_delete(self, service, delete):
153         for args in product(
154                 ('', '%s_id' % service),
155                 ('', 'cmd'),
156                 (204, 208),
157                 ({}, {'k': 'v'})):
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),
165                 success=success,
166                 **kwargs))
167
168     def test_servers_delete(self):
169         self._test_delete('servers')
170
171     def test_images_delete(self):
172         self._test_delete('images')
173
174     @patch('%s.set_header' % rest_pkg)
175     @patch('%s.post' % rest_pkg, return_value=FR())
176     def _test_post(self, service, post, SH):
177         for args in product(
178                 ('', '%s_id' % service),
179                 ('', 'cmd'),
180                 (None, [dict(json="data"), dict(data="json")]),
181                 (202, 204),
182                 ({}, {'k': 'v'})):
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 ''
188             if json_data:
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,
196                 **kwargs))
197
198     def test_servers_post(self):
199         self._test_post('servers')
200
201     def test_images_post(self):
202         self._test_post('images')
203
204     @patch('%s.set_header' % rest_pkg)
205     @patch('%s.put' % rest_pkg, return_value=FR())
206     def _test_put(self, service, put, SH):
207         for args in product(
208                 ('', '%s_id' % service),
209                 ('', 'cmd'),
210                 (None, [dict(json="data"), dict(data="json")]),
211                 (204, 504),
212                 ({}, {'k': 'v'})):
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 ''
218             if json_data:
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,
226                 **kwargs))
227
228     def test_servers_put(self):
229         self._test_put('servers')
230
231     def test_images_put(self):
232         self._test_put('images')
233
234
235 class Compute(TestCase):
236
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])
242             else:
243                 self.assertEqual(unicode(v), unicode(d2[k]))
244
245     """Set up a Cyclades thorough test"""
246     def setUp(self):
247         self.url = 'http://cyclades.example.com'
248         self.token = 'cyc14d3s70k3n'
249         self.client = ComputeClient(self.url, self.token)
250
251     def tearDown(self):
252         FR.status_code = 200
253         FR.json = vm_recv
254
255     @patch(
256         '%s.get_image_details' % compute_pkg,
257         return_value=img_recv['image'])
258     def test_create_server(self, GID):
259         with patch.object(
260                 ComputeClient, 'servers_post',
261                 side_effect=ClientError(
262                     'REQUEST ENTITY TOO LARGE',
263                     status=403)):
264             self.assertRaises(
265                 ClientError,
266                 self.client.create_server,
267                 vm_name, fid, img_ref)
268
269         with patch.object(
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))
281
282     @patch('%s.servers_get' % compute_pkg, return_value=FR())
283     def test_list_servers(self, SG):
284         FR.json = vm_list
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))
292
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'])
299
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)))
307
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(
314                 vm_id, 'action',
315                 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
316
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))
328
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)
337
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))
343
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(
352             vm_id, 'meta',
353             json_data=dict(metadata=metadata), success=201)
354
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']
358         key = 'metakey'
359         self.client.delete_server_metadata(vm_id, key)
360         SD.assert_called_once_with(vm_id, 'meta/' + key)
361
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'])
369
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'])
376
377     @patch('%s.images_get' % compute_pkg, return_value=FR())
378     def test_list_images(self, IG):
379         FR.json = img_list
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])
386
387     @patch('%s.images_get' % compute_pkg, return_value=FR())
388     def test_get_image_details(self, IG):
389         FR.json = img_recv
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'])
393
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(
401                 '%s' % img_ref,
402                 '/meta%s' % (('/%s' % key) if key else '')))
403             self.assert_dicts_are_equal(img_recv['image'], r)
404
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)
410
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)
415
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'])
425
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(
432             img_ref, 'meta',
433             json_data=dict(metadata=metadata))
434         self.assert_dicts_are_equal(r, metadata)
435
436     @patch('%s.images_delete' % compute_pkg, return_value=FR())
437     def test_delete_image_metadata(self, ID):
438         key = 'metakey'
439         self.client.delete_image_metadata(img_ref, key)
440         ID.assert_called_once_with(img_ref, '/meta/%s' % key)
441
442
443 if __name__ == '__main__':
444     from sys import argv
445     from kamaki.clients.test import runTestCase
446     not_found = True
447     if not argv[1:] or argv[1] == 'Compute':
448         not_found = False
449         runTestCase(Compute, 'Compute Client', argv[2:])
450     if not argv[1:] or argv[1] == 'ComputeRest':
451         not_found = False
452         runTestCase(ComputeRest, 'ComputeRest Client', argv[2:])
453     if not_found:
454         print('TestCase %s not found' % argv[1])