Unittest ComputeRestApi.images_delete and improve
[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, ComputeClientApi
40 from kamaki.clients import ClientError
41
42
43 rest_pkg = 'kamaki.clients.compute.rest_api.ComputeClientApi'
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 ComputeRestApi(TestCase):
115
116     """Set up a ComputesRestApi thorough test"""
117     def setUp(self):
118         self.url = 'http://cyclades.example.com'
119         self.token = 'cyc14d3s70k3n'
120         self.client = ComputeClientApi(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_servers_post(self, post, SH):
177         vm_id = vm_recv['server']['id']
178         for args in product(
179                 ('', vm_id),
180                 ('', 'cmd'),
181                 (None, [dict(json="data"), dict(data="json")]),
182                 (202, 204),
183                 ({}, {'k': 'v'})):
184             (server_id, command, json_data, success, kwargs) = args
185
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 ''
189             if json_data:
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,
197                 **kwargs))
198
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']
203         for args in product(
204                 ('', vm_id),
205                 ('', 'cmd'),
206                 (None, [dict(json="data"), dict(data="json")]),
207                 (204, 504),
208                 ({}, {'k': 'v'})):
209             (server_id, command, json_data, success, kwargs) = args
210
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 ''
214             if json_data:
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,
222                 **kwargs))
223
224
225 class Compute(TestCase):
226
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])
232             else:
233                 self.assertEqual(unicode(v), unicode(d2[k]))
234
235     """Set up a Cyclades thorough test"""
236     def setUp(self):
237         self.url = 'http://cyclades.example.com'
238         self.token = 'cyc14d3s70k3n'
239         self.client = ComputeClient(self.url, self.token)
240
241     def tearDown(self):
242         FR.status_code = 200
243         FR.json = vm_recv
244
245     @patch(
246         '%s.get_image_details' % compute_pkg,
247         return_value=img_recv['image'])
248     def test_create_server(self, GID):
249         with patch.object(
250                 ComputeClient, 'servers_post',
251                 side_effect=ClientError(
252                     'REQUEST ENTITY TOO LARGE',
253                     status=403)):
254             self.assertRaises(
255                 ClientError,
256                 self.client.create_server,
257                 vm_name, fid, img_ref)
258
259         with patch.object(
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))
271
272     @patch('%s.servers_get' % compute_pkg, return_value=FR())
273     def test_list_servers(self, SG):
274         FR.json = vm_list
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))
282
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'])
289
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)))
297
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(
304                 vm_id, 'action',
305                 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
306
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))
318
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)
327
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))
333
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(
342             vm_id, 'meta',
343             json_data=dict(metadata=metadata), success=201)
344
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']
348         key = 'metakey'
349         self.client.delete_server_metadata(vm_id, key)
350         SD.assert_called_once_with(vm_id, 'meta/' + key)
351
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'])
359
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'])
366
367     @patch('%s.images_get' % compute_pkg, return_value=FR())
368     def test_list_images(self, IG):
369         FR.json = img_list
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])
376
377     @patch('%s.images_get' % compute_pkg, return_value=FR())
378     def test_get_image_details(self, IG):
379         FR.json = img_recv
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'])
383
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(
391                 '%s' % img_ref,
392                 '/meta%s' % (('/%s' % key) if key else '')))
393             self.assert_dicts_are_equal(img_recv['image'], r)
394
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)
400
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)
405
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'])
415
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(
422             img_ref, 'meta',
423             json_data=dict(metadata=metadata))
424         self.assert_dicts_are_equal(r, metadata)
425
426     @patch('%s.images_delete' % compute_pkg, return_value=FR())
427     def test_delete_image_metadata(self, ID):
428         key = 'metakey'
429         self.client.delete_image_metadata(img_ref, key)
430         ID.assert_called_once_with(img_ref, '/meta/%s' % key)
431
432
433 if __name__ == '__main__':
434     from sys import argv
435     from kamaki.clients.test import runTestCase
436     not_found = True
437     if not argv[1:] or argv[1] == 'Compute':
438         not_found = False
439         runTestCase(Compute, 'Compute Client', argv[2:])
440     if not argv[1:] or argv[1] == 'ComputeRestApi':
441         not_found = False
442         runTestCase(ComputeRestApi, 'ComputeRestApi Client', argv[2:])
443     if not_found:
444         print('TestCase %s not found' % argv[1])