Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / compute / test.py @ a8fe029d

History | View | Annotate | Download (17.3 kB)

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_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] == 'ComputeRestApi':
451
        not_found = False
452
        runTestCase(ComputeRestApi, 'ComputeRestApi Client', argv[2:])
453
    if not_found:
454
        print('TestCase %s not found' % argv[1])