9160fd7cd9493ad699707ff99301a0b2ccfeb6f5
[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
37 from kamaki.clients import ClientError
38 from kamaki.clients.cyclades import CycladesClient
39
40
41 compute_pkg = 'kamaki.clients.cyclades.CycladesClient'
42
43 img_ref = "1m4g3-r3f3r3nc3"
44 vm_name = "my new VM"
45 fid = 42
46 vm_send = dict(server=dict(
47     flavorRef=fid,
48     name=vm_name,
49     imageRef=img_ref,
50     metadata=dict(os="debian", users="root")))
51 vm_recv = dict(server=dict(
52     status="BUILD",
53     updated="2013-03-01T10:04:00.637152+00:00",
54     hostId="",
55     name=vm_name,
56     imageRef=img_ref,
57     created="2013-03-01T10:04:00.087324+00:00",
58     flavorRef=fid,
59     adminPass="n0n3sh@11p@55",
60     suspended=False,
61     progress=0,
62     id=31173,
63     metadata=dict(values=dict(os="debian", users="root"))))
64 img_recv = dict(image=dict(
65     status="ACTIVE",
66     updated="2013-02-26T11:10:14+00:00",
67     name="Debian Base",
68     created="2013-02-26T11:03:29+00:00",
69     progress=100,
70     id=img_ref,
71     metadata=dict(values=dict(
72         partition_table="msdos",
73         kernel="2.6.32",
74         osfamily="linux",
75         users="root",
76         gui="No GUI",
77         sortorder="1",
78         os="debian",
79         root_partition="1",
80         description="Debian 6.0.7 (Squeeze) Base System"))))
81 vm_list = dict(servers=dict(values=[
82     dict(name='n1', id=1),
83     dict(name='n2', id=2)]))
84 flavor_list = dict(flavors=dict(values=[
85     dict(id=41, name="C1R1024D20"),
86     dict(id=42, name="C1R1024D40"),
87     dict(id=43, name="C1R1028D20")]))
88 img_list = dict(images=dict(values=[
89     dict(name="maelstrom", id="0fb03e45-7d5a-4515-bd4e-e6bbf6457f06"),
90     dict(name="edx_saas", id="1357163d-5fd8-488e-a117-48734c526206"),
91     dict(name="Debian_Wheezy_Base", id="1f8454f0-8e3e-4b6c-ab8e-5236b728dffe"),
92     dict(name="CentOS", id="21894b48-c805-4568-ac8b-7d4bb8eb533d"),
93     dict(name="Ubuntu Desktop", id="37bc522c-c479-4085-bfb9-464f9b9e2e31"),
94     dict(name="Ubuntu 12.10", id="3a24fef9-1a8c-47d1-8f11-e07bd5e544fd"),
95     dict(name="Debian Base", id="40ace203-6254-4e17-a5cb-518d55418a7d"),
96     dict(name="ubuntu_bundled", id="5336e265-5c7c-4127-95cb-2bf832a79903")]))
97
98
99 class FR(object):
100     """FR stands for Fake Response"""
101     json = vm_recv
102     headers = {}
103     content = json
104     status = None
105     status_code = 200
106
107     def release(self):
108         pass
109
110
111 class Cyclades(TestCase):
112
113     def assert_dicts_are_equal(self, d1, d2):
114         for k, v in d1.items():
115             self.assertTrue(k in d2)
116             if isinstance(v, dict):
117                 self.assert_dicts_are_equal(v, d2[k])
118             else:
119                 self.assertEqual(unicode(v), unicode(d2[k]))
120
121     """Set up a Cyclades thorough test"""
122     def setUp(self):
123         self.url = 'http://cyclades.example.com'
124         self.token = 'cyc14d3s70k3n'
125         self.client = CycladesClient(self.url, self.token)
126         from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
127         self.C = KamakiHTTPConnection
128
129     def tearDown(self):
130         FR.status_code = 200
131         FR.json = vm_recv
132
133     @patch(
134         '%s.get_image_details' % compute_pkg,
135         return_value=img_recv['image'])
136     def test_create_server(self, GID):
137         with patch.object(
138                 CycladesClient, 'servers_post',
139                 side_effect=ClientError(
140                     'REQUEST ENTITY TOO LARGE',
141                     status=403)):
142             self.assertRaises(
143                 ClientError,
144                 self.client.create_server,
145                 vm_name, fid, img_ref)
146
147         with patch.object(
148                 CycladesClient, 'servers_post',
149                 return_value=FR()) as post:
150             r = self.client.create_server(vm_name, fid, img_ref)
151             self.assertEqual(r, FR.json['server'])
152             self.assertEqual(GID.mock_calls[-1], call(img_ref))
153             self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
154             prsn = 'Personality string (does not work with real servers)'
155             self.client.create_server(vm_name, fid, img_ref, prsn)
156             expected = dict(server=dict(vm_send['server']))
157             expected['server']['personality'] = prsn
158             self.assertEqual(post.mock_calls[-1], call(json_data=expected))
159
160     @patch('%s.servers_get' % compute_pkg, return_value=FR())
161     def test_list_servers(self, SG):
162         FR.json = vm_list
163         for detail in (False, True):
164             r = self.client.list_servers(detail)
165             self.assertEqual(SG.mock_calls[-1], call(
166                 changes_since=None,
167                 command='detail' if detail else ''))
168             for i, vm in enumerate(vm_list['servers']['values']):
169                 self.assert_dicts_are_equal(r[i], vm)
170             self.assertEqual(i + 1, len(r))
171
172     @patch('%s.servers_get' % compute_pkg, return_value=FR())
173     def test_get_server_details(self, SG):
174         vm_id = vm_recv['server']['id']
175         r = self.client.get_server_details(vm_id)
176         SG.assert_called_once_with(vm_id)
177         self.assert_dicts_are_equal(r, vm_recv['server'])
178
179     @patch('%s.servers_put' % compute_pkg, return_value=FR())
180     def test_update_server_name(self, SP):
181         vm_id = vm_recv['server']['id']
182         new_name = vm_name + '_new'
183         self.client.update_server_name(vm_id, new_name)
184         SP.assert_called_once_with(vm_id, json_data=dict(
185             server=dict(name=new_name)))
186
187     @patch('%s.servers_post' % compute_pkg, return_value=FR())
188     def test_reboot_server(self, SP):
189         vm_id = vm_recv['server']['id']
190         for hard in (None, True):
191             self.client.reboot_server(vm_id, hard=hard)
192             self.assertEqual(SP.mock_calls[-1], call(
193                 vm_id, 'action',
194                 json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
195
196     @patch('%s.servers_put' % compute_pkg, return_value=FR())
197     def test_create_server_metadata(self, SP):
198         vm_id = vm_recv['server']['id']
199         metadata = dict(m1='v1', m2='v2', m3='v3')
200         FR.json = dict(meta=vm_recv['server'])
201         for k, v in metadata.items():
202             r = self.client.create_server_metadata(vm_id, k, v)
203             self.assert_dicts_are_equal(r, vm_recv['server'])
204             self.assertEqual(SP.mock_calls[-1], call(
205                 vm_id, 'meta/%s' % k,
206                 json_data=dict(meta={k: v}), success=201))
207
208     @patch('%s.servers_get' % compute_pkg, return_value=FR())
209     def test_get_server_metadata(self, SG):
210         vm_id = vm_recv['server']['id']
211         metadata = dict(m1='v1', m2='v2', m3='v3')
212         FR.json = dict(metadata=dict(values=metadata))
213         r = self.client.get_server_metadata(vm_id)
214         SG.assert_called_once_with(vm_id, '/meta')
215         self.assert_dicts_are_equal(r, metadata)
216
217         for k, v in metadata.items():
218             FR.json = dict(meta={k: v})
219             r = self.client.get_server_metadata(vm_id, k)
220             self.assert_dicts_are_equal(r, {k: v})
221             self.assertEqual(SG.mock_calls[-1], call(vm_id, '/meta/%s' % k))
222
223     @patch('%s.servers_post' % compute_pkg, return_value=FR())
224     def test_update_server_metadata(self, SP):
225         vm_id = vm_recv['server']['id']
226         metadata = dict(m1='v1', m2='v2', m3='v3')
227         FR.json = dict(metadata=metadata)
228         r = self.client.update_server_metadata(vm_id, **metadata)
229         self.assert_dicts_are_equal(r, metadata)
230         SP.assert_called_once_with(
231             vm_id, 'meta',
232             json_data=dict(metadata=metadata), success=201)
233
234     @patch('%s.servers_delete' % compute_pkg, return_value=FR())
235     def test_delete_server_metadata(self, SD):
236         vm_id = vm_recv['server']['id']
237         key = 'metakey'
238         self.client.delete_server_metadata(vm_id, key)
239         SD.assert_called_once_with(vm_id, 'meta/' + key)
240
241     @patch('%s.flavors_get' % compute_pkg, return_value=FR())
242     def test_list_flavors(self, FG):
243         FR.json = flavor_list
244         for cmd in ('', 'detail'):
245             r = self.client.list_flavors(detail=(cmd == 'detail'))
246             self.assertEqual(FG.mock_calls[-1], call(command=cmd))
247             self.assert_dicts_are_equal(dict(values=r), flavor_list['flavors'])
248
249     @patch('%s.flavors_get' % compute_pkg, return_value=FR())
250     def test_get_flavor_details(self, FG):
251         FR.json = dict(flavor=flavor_list['flavors'])
252         r = self.client.get_flavor_details(fid)
253         FG.assert_called_once_with(fid)
254         self.assert_dicts_are_equal(r, flavor_list['flavors'])
255
256     @patch('%s.images_get' % compute_pkg, return_value=FR())
257     def test_list_images(self, IG):
258         FR.json = img_list
259         for cmd in ('', 'detail'):
260             r = self.client.list_images(detail=(cmd == 'detail'))
261             self.assertEqual(IG.mock_calls[-1], call(command=cmd))
262             expected = img_list['images']['values']
263             for i in range(len(r)):
264                 self.assert_dicts_are_equal(expected[i], r[i])
265
266     @patch('%s.images_get' % compute_pkg, return_value=FR())
267     def test_get_image_details(self, IG):
268         FR.json = img_recv
269         r = self.client.get_image_details(img_ref)
270         IG.assert_called_once_with(img_ref)
271         self.assert_dicts_are_equal(r, img_recv['image'])
272
273     @patch('%s.images_get' % compute_pkg, return_value=FR())
274     def test_get_image_metadata(self, IG):
275         for key in ('', '50m3k3y'):
276             FR.json = dict(meta=img_recv['image']) if (
277                 key) else dict(metadata=dict(values=img_recv['image']))
278             r = self.client.get_image_metadata(img_ref, key)
279             self.assertEqual(IG.mock_calls[-1], call(
280                 '%s' % img_ref,
281                 '/meta%s' % (('/%s' % key) if key else '')))
282             self.assert_dicts_are_equal(img_recv['image'], r)
283
284     @patch('%s.servers_delete' % compute_pkg, return_value=FR())
285     def test_delete_server(self, SD):
286         vm_id = vm_recv['server']['id']
287         self.client.delete_server(vm_id)
288         self.assertEqual(SD.mock_calls[-1], call(vm_id))
289
290     @patch('%s.images_delete' % compute_pkg, return_value=FR())
291     def test_delete_image(self, ID):
292         self.client.delete_image(img_ref)
293         self.assertEqual(ID.mock_calls[-1], call(img_ref))
294
295     @patch('%s.images_put' % compute_pkg, return_value=FR())
296     def test_create_image_metadata(self, IP):
297         (key, val) = ('k1', 'v1')
298         FR.json = dict(meta=img_recv['image'])
299         r = self.client.create_image_metadata(img_ref, key, val)
300         self.assertEqual(IP.mock_calls[-1], call(
301             img_ref, 'meta/%s' % key,
302             json_data=dict(meta={key: val})))
303         self.assert_dicts_are_equal(r, img_recv['image'])
304
305     @patch('%s.images_post' % compute_pkg, return_value=FR())
306     def test_update_image_metadata(self, IP):
307         metadata = dict(m1='v1', m2='v2', m3='v3')
308         FR.json = dict(metadata=metadata)
309         r = self.client.update_image_metadata(img_ref, **metadata)
310         self.assertEqual(
311             IP.mock_calls[-1],
312             call(img_ref, 'meta', json_data=dict(metadata=metadata)))
313         self.assert_dicts_are_equal(r, metadata)
314
315     @patch('%s.images_delete' % compute_pkg, return_value=FR())
316     def test_delete_image_metadata(self, ID):
317         key = 'metakey'
318         self.client.delete_image_metadata(img_ref, key)
319         self.assertEqual(ID.mock_calls[-1], call(img_ref, '/meta/%s' % key))
320
321 if __name__ == '__main__':
322     from sys import argv
323     from kamaki.clients.test import runTestCase
324     runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])