Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / compute / test.py @ 77d1b504

History | View | Annotate | Download (20.2 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 import ClientError, compute
40

    
41

    
42
rest_pkg = 'kamaki.clients.compute.rest_api.ComputeRestClient'
43
compute_pkg = 'kamaki.clients.compute.ComputeClient'
44

    
45
img_ref = "1m4g3-r3f3r3nc3"
46
vm_name = "my new VM"
47
fid = 42
48
vm_send = dict(server=dict(
49
    flavorRef=fid,
50
    name=vm_name,
51
    imageRef=img_ref,
52
    metadata=dict(os="debian", users="root")))
53
vm_recv = dict(server=dict(
54
    status="BUILD",
55
    updated="2013-03-01T10:04:00.637152+00:00",
56
    hostId="",
57
    name=vm_name,
58
    imageRef=img_ref,
59
    created="2013-03-01T10:04:00.087324+00:00",
60
    flavorRef=fid,
61
    adminPass="n0n3sh@11p@55",
62
    suspended=False,
63
    progress=0,
64
    id=31173,
65
    metadata=dict(os="debian", users="root")))
66
img_recv = dict(image=dict(
67
    status="ACTIVE",
68
    updated="2013-02-26T11:10:14+00:00",
69
    name="Debian Base",
70
    created="2013-02-26T11:03:29+00:00",
71
    progress=100,
72
    id=img_ref,
73
    metadata=dict(
74
        partition_table="msdos",
75
        kernel="2.6.32",
76
        osfamily="linux",
77
        users="root",
78
        gui="No GUI",
79
        sortorder="1",
80
        os="debian",
81
        root_partition="1",
82
        description="Debian 6.0.7 (Squeeze) Base System")))
83
vm_list = dict(servers=[
84
    dict(name='n1', id=1),
85
    dict(name='n2', id=2)])
86
flavor_list = dict(flavors=[
87
    dict(id=41, name="C1R1024D20"),
88
    dict(id=42, name="C1R1024D40"),
89
    dict(id=43, name="C1R1028D20")])
90
img_list = dict(images=[
91
    dict(name="maelstrom", id="0fb03e45-7d5a-4515-bd4e-e6bbf6457f06"),
92
    dict(name="edx_saas", id="1357163d-5fd8-488e-a117-48734c526206"),
93
    dict(name="Debian_Wheezy_Base", id="1f8454f0-8e3e-4b6c-ab8e-5236b728dffe"),
94
    dict(name="CentOS", id="21894b48-c805-4568-ac8b-7d4bb8eb533d"),
95
    dict(name="Ubuntu Desktop", id="37bc522c-c479-4085-bfb9-464f9b9e2e31"),
96
    dict(name="Ubuntu 12.10", id="3a24fef9-1a8c-47d1-8f11-e07bd5e544fd"),
97
    dict(name="Debian Base", id="40ace203-6254-4e17-a5cb-518d55418a7d"),
98
    dict(name="ubuntu_bundled", id="5336e265-5c7c-4127-95cb-2bf832a79903")])
99

    
100

    
101
class FR(object):
102
    """FR stands for Fake Response"""
103
    json = vm_recv
104
    headers = {}
105
    content = json
106
    status = None
107
    status_code = 200
108

    
109

    
110
class ComputeRestClient(TestCase):
111

    
112
    """Set up a ComputesRest thorough test"""
113
    def setUp(self):
114
        self.url = 'http://cyclades.example.com'
115
        self.token = 'cyc14d3s70k3n'
116
        self.client = compute.ComputeRestClient(self.url, self.token)
117

    
118
    def tearDown(self):
119
        FR.json = vm_recv
120

    
121
    @patch('%s.get' % rest_pkg, return_value=FR())
122
    def _test_get(self, service, get):
123
        for args in product(
124
                ('', '%s_id' % service),
125
                ('', 'cmd'),
126
                (200, 204),
127
                ({}, {'k': 'v'})):
128
            (srv_id, command, success, kwargs) = args
129
            method = getattr(self.client, '%s_get' % service)
130
            method(*args[:3], **kwargs)
131
            srv_str = '/%s' % srv_id if srv_id else ''
132
            cmd_str = '/%s' % command if command else ''
133
            self.assertEqual(get.mock_calls[-1], call(
134
                '/%s%s%s' % (service, srv_str, cmd_str),
135
                success=success,
136
                **kwargs))
137

    
138
    def test_servers_get(self):
139
        self._test_get('servers')
140

    
141
    def test_flavors_get(self):
142
        self._test_get('flavors')
143

    
144
    def test_images_get(self):
145
        self._test_get('images')
146

    
147
    @patch('%s.delete' % rest_pkg, return_value=FR())
148
    def _test_delete(self, service, delete):
149
        for args in product(
150
                ('', '%s_id' % service),
151
                ('', 'cmd'),
152
                (204, 208),
153
                ({}, {'k': 'v'})):
154
            (srv_id, command, success, kwargs) = args
155
            method = getattr(self.client, '%s_delete' % service)
156
            method(*args[:3], **kwargs)
157
            vm_str = '/%s' % srv_id if srv_id else ''
158
            cmd_str = '/%s' % command if command else ''
159
            self.assertEqual(delete.mock_calls[-1], call(
160
                '/%s%s%s' % (service, vm_str, cmd_str),
161
                success=success,
162
                **kwargs))
163

    
164
    def test_servers_delete(self):
165
        self._test_delete('servers')
166

    
167
    def test_images_delete(self):
168
        self._test_delete('images')
169

    
170
    @patch('%s.set_header' % rest_pkg)
171
    @patch('%s.post' % rest_pkg, return_value=FR())
172
    def _test_post(self, service, post, SH):
173
        for args in product(
174
                ('', '%s_id' % service),
175
                ('', 'cmd'),
176
                (None, [dict(json="data"), dict(data="json")]),
177
                (202, 204),
178
                ({}, {'k': 'v'})):
179
            (srv_id, command, json_data, success, kwargs) = args
180
            method = getattr(self.client, '%s_post' % service)
181
            method(*args[:4], **kwargs)
182
            vm_str = '/%s' % srv_id if srv_id else ''
183
            cmd_str = '/%s' % command if command else ''
184
            if json_data:
185
                json_data = dumps(json_data)
186
                self.assertEqual(SH.mock_calls[-2:], [
187
                    call('Content-Type', 'application/json'),
188
                    call('Content-Length', len(json_data))])
189
            self.assertEqual(post.mock_calls[-1], call(
190
                '/%s%s%s' % (service, vm_str, cmd_str),
191
                data=json_data, success=success,
192
                **kwargs))
193

    
194
    def test_servers_post(self):
195
        self._test_post('servers')
196

    
197
    def test_images_post(self):
198
        self._test_post('images')
199

    
200
    @patch('%s.set_header' % rest_pkg)
201
    @patch('%s.put' % rest_pkg, return_value=FR())
202
    def _test_put(self, service, put, SH):
203
        for args in product(
204
                ('', '%s_id' % service),
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
            method = getattr(self.client, '%s_put' % service)
211
            method(*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
                '/%s%s%s' % (service, vm_str, cmd_str),
221
                data=json_data, success=success,
222
                **kwargs))
223

    
224
    def test_servers_put(self):
225
        self._test_put('servers')
226

    
227
    def test_images_put(self):
228
        self._test_put('images')
229

    
230
    @patch('%s.get' % rest_pkg, return_value=FR())
231
    def test_floating_ip_pools_get(self, get):
232
        for args in product(
233
                ('tenant1', 'tenant2'),
234
                (200, 204),
235
                ({}, {'k': 'v'})):
236
            tenant_id, success, kwargs = args
237
            r = self.client.floating_ip_pools_get(tenant_id, success, **kwargs)
238
            self.assertTrue(isinstance(r, FR))
239
            self.assertEqual(get.mock_calls[-1], call(
240
                '/%s/os-floating-ip-pools' % tenant_id,
241
                success=success, **kwargs))
242

    
243
    @patch('%s.get' % rest_pkg, return_value=FR())
244
    def test_floating_ips_get(self, get):
245
        for args in product(
246
                ('tenant1', 'tenant2'),
247
                (200, 204),
248
                ({}, {'k': 'v'})):
249
            tenant_id, success, kwargs = args
250
            r = self.client.floating_ips_get(tenant_id, success, **kwargs)
251
            self.assertTrue(isinstance(r, FR))
252
            self.assertEqual(get.mock_calls[-1], call(
253
                '/%s/os-floating-ips' % tenant_id,
254
                success=success, **kwargs))
255

    
256
    @patch('%s.set_header' % rest_pkg)
257
    @patch('%s.post' % rest_pkg, return_value=FR())
258
    def test_floating_ips_post(self, post, SH):
259
        for args in product(
260
                ('tenant1', 'tenant2'),
261
                (None, [dict(json="data"), dict(data="json")]),
262
                (202, 204),
263
                ({}, {'k': 'v'})):
264
            (tenant_id, json_data, success, kwargs) = args
265
            self.client.floating_ips_post(*args[:3], **kwargs)
266
            if json_data:
267
                json_data = dumps(json_data)
268
                self.assertEqual(SH.mock_calls[-2:], [
269
                    call('Content-Type', 'application/json'),
270
                    call('Content-Length', len(json_data))])
271
            self.assertEqual(post.mock_calls[-1], call(
272
                '/%s/os-floating-ips' % tenant_id,
273
                data=json_data, success=success,
274
                **kwargs))
275

    
276
    @patch('%s.get' % rest_pkg, return_value=FR())
277
    def test_floating_ip_get(self, get):
278
        for args in product(
279
                ('tenant1', 'tenant2'),
280
                (200, 204),
281
                ({}, {'k': 'v'})):
282
            tenant_id, success, kwargs = args
283
            r = self.client.floating_ip_get(tenant_id, success, **kwargs)
284
            self.assertTrue(isinstance(r, FR))
285
            self.assertEqual(get.mock_calls[-1], call(
286
                '/%s/os-floating-ip' % tenant_id,
287
                success=success, **kwargs))
288

    
289
    @patch('%s.delete' % rest_pkg, return_value=FR())
290
    def test_floating_ip_delete(self, delete):
291
        for args in product(
292
                ('tenant1', 'tenant2'),
293
                (204,),
294
                ({}, {'k': 'v'})):
295
            tenant_id, success, kwargs = args
296
            r = self.client.floating_ip_delete(tenant_id, success, **kwargs)
297
            self.assertTrue(isinstance(r, FR))
298
            self.assertEqual(delete.mock_calls[-1], call(
299
                '/%s/os-floating-ip' % tenant_id,
300
                success=success, **kwargs))
301

    
302

    
303
class ComputeClient(TestCase):
304

    
305
    def assert_dicts_are_equal(self, d1, d2):
306
        for k, v in d1.items():
307
            self.assertTrue(k in d2)
308
            if isinstance(v, dict):
309
                self.assert_dicts_are_equal(v, d2[k])
310
            else:
311
                self.assertEqual(unicode(v), unicode(d2[k]))
312

    
313
    """Set up a Cyclades thorough test"""
314
    def setUp(self):
315
        self.url = 'http://cyclades.example.com'
316
        self.token = 'cyc14d3s70k3n'
317
        self.client = compute.ComputeClient(self.url, self.token)
318

    
319
    def tearDown(self):
320
        FR.status_code = 200
321
        FR.json = vm_recv
322

    
323
    @patch(
324
        '%s.get_image_details' % compute_pkg,
325
        return_value=img_recv['image'])
326
    def test_create_server(self, GID):
327
        with patch.object(
328
                compute.ComputeClient, 'servers_post',
329
                side_effect=ClientError(
330
                    'REQUEST ENTITY TOO LARGE',
331
                    status=403)):
332
            self.assertRaises(
333
                ClientError,
334
                self.client.create_server,
335
                vm_name, fid, img_ref)
336

    
337
        with patch.object(
338
                compute.ComputeClient, 'servers_post',
339
                return_value=FR()) as post:
340
            r = self.client.create_server(vm_name, fid, img_ref)
341
            self.assertEqual(r, FR.json['server'])
342
            self.assertEqual(GID.mock_calls[-1], call(img_ref))
343
            self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
344
            prsn = 'Personality string (does not work with real servers)'
345
            self.client.create_server(vm_name, fid, img_ref, prsn)
346
            expected = dict(server=dict(vm_send['server']))
347
            expected['server']['personality'] = prsn
348
            self.assertEqual(post.mock_calls[-1], call(json_data=expected))
349

    
350
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
351
    def test_list_servers(self, SG):
352
        FR.json = vm_list
353
        for detail in (False, True):
354
            r = self.client.list_servers(detail)
355
            self.assertEqual(SG.mock_calls[-1], call(
356
                command='detail' if detail else ''))
357
            for i, vm in enumerate(vm_list['servers']):
358
                self.assert_dicts_are_equal(r[i], vm)
359
            self.assertEqual(i + 1, len(r))
360

    
361
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
362
    def test_get_server_details(self, SG):
363
        vm_id = vm_recv['server']['id']
364
        r = self.client.get_server_details(vm_id)
365
        SG.assert_called_once_with(vm_id)
366
        self.assert_dicts_are_equal(r, vm_recv['server'])
367

    
368
    @patch('%s.servers_put' % compute_pkg, return_value=FR())
369
    def test_update_server_name(self, SP):
370
        vm_id = vm_recv['server']['id']
371
        new_name = vm_name + '_new'
372
        self.client.update_server_name(vm_id, new_name)
373
        SP.assert_called_once_with(vm_id, json_data=dict(
374
            server=dict(name=new_name)))
375

    
376
    @patch('%s.servers_post' % compute_pkg, return_value=FR())
377
    def test_reboot_server(self, SP):
378
        vm_id = vm_recv['server']['id']
379
        for hard in (None, True):
380
            self.client.reboot_server(vm_id, hard=hard)
381
            self.assertEqual(SP.mock_calls[-1], call(
382
                vm_id, 'action',
383
                json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
384

    
385
    @patch('%s.servers_put' % compute_pkg, return_value=FR())
386
    def test_create_server_metadata(self, SP):
387
        vm_id = vm_recv['server']['id']
388
        metadata = dict(m1='v1', m2='v2', m3='v3')
389
        FR.json = dict(metadata=vm_recv['server'])
390
        for k, v in metadata.items():
391
            r = self.client.create_server_metadata(vm_id, k, v)
392
            self.assert_dicts_are_equal(r, vm_recv['server'])
393
            self.assertEqual(SP.mock_calls[-1], call(
394
                vm_id, 'metadata/%s' % k,
395
                json_data=dict(metadata={k: v}), success=201))
396

    
397
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
398
    def test_get_server_metadata(self, SG):
399
        vm_id = vm_recv['server']['id']
400
        metadata = dict(m1='v1', m2='v2', m3='v3')
401
        FR.json = dict(metadata=metadata)
402
        r = self.client.get_server_metadata(vm_id)
403
        SG.assert_called_once_with(vm_id, '/metadata')
404
        self.assert_dicts_are_equal(r, metadata)
405

    
406
        for k, v in metadata.items():
407
            FR.json = dict(metadata={k: v})
408
            r = self.client.get_server_metadata(vm_id, k)
409
            self.assert_dicts_are_equal(r, {k: v})
410
            self.assertEqual(
411
                SG.mock_calls[-1], call(vm_id, '/metadata/%s' % k))
412

    
413
    @patch('%s.servers_post' % compute_pkg, return_value=FR())
414
    def test_update_server_metadata(self, SP):
415
        vm_id = vm_recv['server']['id']
416
        metadata = dict(m1='v1', m2='v2', m3='v3')
417
        FR.json = dict(metadata=metadata)
418
        r = self.client.update_server_metadata(vm_id, **metadata)
419
        self.assert_dicts_are_equal(r, metadata)
420
        SP.assert_called_once_with(
421
            vm_id, 'metadata',
422
            json_data=dict(metadata=metadata), success=201)
423

    
424
    @patch('%s.servers_delete' % compute_pkg, return_value=FR())
425
    def test_delete_server_metadata(self, SD):
426
        vm_id = vm_recv['server']['id']
427
        key = 'metakey'
428
        self.client.delete_server_metadata(vm_id, key)
429
        SD.assert_called_once_with(vm_id, 'metadata/' + key)
430

    
431
    @patch('%s.flavors_get' % compute_pkg, return_value=FR())
432
    def test_list_flavors(self, FG):
433
        FR.json = flavor_list
434
        for cmd in ('', 'detail'):
435
            r = self.client.list_flavors(detail=(cmd == 'detail'))
436
            self.assertEqual(FG.mock_calls[-1], call(command=cmd))
437
            self.assertEqual(r, flavor_list['flavors'])
438

    
439
    @patch('%s.flavors_get' % compute_pkg, return_value=FR())
440
    def test_get_flavor_details(self, FG):
441
        FR.json = dict(flavor=flavor_list['flavors'][0])
442
        r = self.client.get_flavor_details(fid)
443
        FG.assert_called_once_with(fid)
444
        self.assert_dicts_are_equal(r, flavor_list['flavors'][0])
445

    
446
    @patch('%s.images_get' % compute_pkg, return_value=FR())
447
    def test_list_images(self, IG):
448
        FR.json = img_list
449
        for cmd in ('', 'detail'):
450
            r = self.client.list_images(detail=(cmd == 'detail'))
451
            self.assertEqual(IG.mock_calls[-1], call(command=cmd))
452
            expected = img_list['images']
453
            for i in range(len(r)):
454
                self.assert_dicts_are_equal(expected[i], r[i])
455

    
456
    @patch('%s.images_get' % compute_pkg, return_value=FR())
457
    def test_get_image_details(self, IG):
458
        FR.json = img_recv
459
        r = self.client.get_image_details(img_ref)
460
        IG.assert_called_once_with(img_ref)
461
        self.assert_dicts_are_equal(r, img_recv['image'])
462

    
463
    @patch('%s.images_get' % compute_pkg, return_value=FR())
464
    def test_get_image_metadata(self, IG):
465
        for key in ('', '50m3k3y'):
466
            FR.json = dict(metadata=img_recv['image']) if (
467
                key) else dict(metadata=img_recv['image'])
468
            r = self.client.get_image_metadata(img_ref, key)
469
            self.assertEqual(IG.mock_calls[-1], call(
470
                '%s' % img_ref,
471
                '/metadata%s' % (('/%s' % key) if key else '')))
472
            self.assert_dicts_are_equal(img_recv['image'], r)
473

    
474
    @patch('%s.servers_delete' % compute_pkg, return_value=FR())
475
    def test_delete_server(self, SD):
476
        vm_id = vm_recv['server']['id']
477
        self.client.delete_server(vm_id)
478
        SD.assert_called_once_with(vm_id)
479

    
480
    @patch('%s.images_delete' % compute_pkg, return_value=FR())
481
    def test_delete_image(self, ID):
482
        self.client.delete_image(img_ref)
483
        ID.assert_called_once_with(img_ref)
484

    
485
    @patch('%s.images_put' % compute_pkg, return_value=FR())
486
    def test_create_image_metadata(self, IP):
487
        (key, val) = ('k1', 'v1')
488
        FR.json = dict(metadata=img_recv['image'])
489
        r = self.client.create_image_metadata(img_ref, key, val)
490
        IP.assert_called_once_with(
491
            img_ref, 'metadata/%s' % key,
492
            json_data=dict(metadata={key: val}))
493
        self.assert_dicts_are_equal(r, img_recv['image'])
494

    
495
    @patch('%s.images_post' % compute_pkg, return_value=FR())
496
    def test_update_image_metadata(self, IP):
497
        metadata = dict(m1='v1', m2='v2', m3='v3')
498
        FR.json = dict(metadata=metadata)
499
        r = self.client.update_image_metadata(img_ref, **metadata)
500
        IP.assert_called_once_with(
501
            img_ref, 'metadata',
502
            json_data=dict(metadata=metadata))
503
        self.assert_dicts_are_equal(r, metadata)
504

    
505
    @patch('%s.images_delete' % compute_pkg, return_value=FR())
506
    def test_delete_image_metadata(self, ID):
507
        key = 'metakey'
508
        self.client.delete_image_metadata(img_ref, key)
509
        ID.assert_called_once_with(img_ref, '/metadata/%s' % key)
510

    
511

    
512
if __name__ == '__main__':
513
    from sys import argv
514
    from kamaki.clients.test import runTestCase
515
    not_found = True
516
    if not argv[1:] or argv[1] == 'ComputeClient':
517
        not_found = False
518
        runTestCase(ComputeClient, 'Compute Client', argv[2:])
519
    if not argv[1:] or argv[1] == 'ComputeRest':
520
        not_found = False
521
        runTestCase(ComputeRestClient, 'ComputeRest Client', argv[2:])
522
    if not_found:
523
        print('TestCase %s not found' % argv[1])