Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / compute / test.py @ 4443152f

History | View | Annotate | Download (17.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

    
231
class ComputeClient(TestCase):
232

    
233
    def assert_dicts_are_equal(self, d1, d2):
234
        for k, v in d1.items():
235
            self.assertTrue(k in d2)
236
            if isinstance(v, dict):
237
                self.assert_dicts_are_equal(v, d2[k])
238
            else:
239
                self.assertEqual(unicode(v), unicode(d2[k]))
240

    
241
    """Set up a Cyclades thorough test"""
242
    def setUp(self):
243
        self.url = 'http://cyclades.example.com'
244
        self.token = 'cyc14d3s70k3n'
245
        self.client = compute.ComputeClient(self.url, self.token)
246

    
247
    def tearDown(self):
248
        FR.status_code = 200
249
        FR.json = vm_recv
250

    
251
    @patch(
252
        '%s.get_image_details' % compute_pkg,
253
        return_value=img_recv['image'])
254
    def test_create_server(self, GID):
255
        with patch.object(
256
                compute.ComputeClient, 'servers_post',
257
                side_effect=ClientError(
258
                    'REQUEST ENTITY TOO LARGE',
259
                    status=403)):
260
            self.assertRaises(
261
                ClientError,
262
                self.client.create_server,
263
                vm_name, fid, img_ref)
264

    
265
        with patch.object(
266
                compute.ComputeClient, 'servers_post',
267
                return_value=FR()) as post:
268
            r = self.client.create_server(vm_name, fid, img_ref)
269
            self.assertEqual(r, FR.json['server'])
270
            self.assertEqual(GID.mock_calls[-1], call(img_ref))
271
            self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
272
            prsn = 'Personality string (does not work with real servers)'
273
            self.client.create_server(vm_name, fid, img_ref, prsn)
274
            expected = dict(server=dict(vm_send['server']))
275
            expected['server']['personality'] = prsn
276
            self.assertEqual(post.mock_calls[-1], call(json_data=expected))
277

    
278
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
279
    def test_list_servers(self, SG):
280
        FR.json = vm_list
281
        for detail in (False, True):
282
            r = self.client.list_servers(detail)
283
            self.assertEqual(SG.mock_calls[-1], call(
284
                command='detail' if detail else ''))
285
            for i, vm in enumerate(vm_list['servers']):
286
                self.assert_dicts_are_equal(r[i], vm)
287
            self.assertEqual(i + 1, len(r))
288

    
289
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
290
    def test_get_server_details(self, SG):
291
        vm_id = vm_recv['server']['id']
292
        r = self.client.get_server_details(vm_id)
293
        SG.assert_called_once_with(vm_id)
294
        self.assert_dicts_are_equal(r, vm_recv['server'])
295

    
296
    @patch('%s.servers_put' % compute_pkg, return_value=FR())
297
    def test_update_server_name(self, SP):
298
        vm_id = vm_recv['server']['id']
299
        new_name = vm_name + '_new'
300
        self.client.update_server_name(vm_id, new_name)
301
        SP.assert_called_once_with(vm_id, json_data=dict(
302
            server=dict(name=new_name)))
303

    
304
    @patch('%s.servers_post' % compute_pkg, return_value=FR())
305
    def test_reboot_server(self, SP):
306
        vm_id = vm_recv['server']['id']
307
        for hard in (None, True):
308
            self.client.reboot_server(vm_id, hard=hard)
309
            self.assertEqual(SP.mock_calls[-1], call(
310
                vm_id, 'action',
311
                json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
312

    
313
    @patch('%s.servers_put' % compute_pkg, return_value=FR())
314
    def test_create_server_metadata(self, SP):
315
        vm_id = vm_recv['server']['id']
316
        metadata = dict(m1='v1', m2='v2', m3='v3')
317
        FR.json = dict(meta=vm_recv['server'])
318
        for k, v in metadata.items():
319
            r = self.client.create_server_metadata(vm_id, k, v)
320
            self.assert_dicts_are_equal(r, vm_recv['server'])
321
            self.assertEqual(SP.mock_calls[-1], call(
322
                vm_id, 'metadata/%s' % k,
323
                json_data=dict(meta={k: v}), success=201))
324

    
325
    @patch('%s.servers_get' % compute_pkg, return_value=FR())
326
    def test_get_server_metadata(self, SG):
327
        vm_id = vm_recv['server']['id']
328
        metadata = dict(m1='v1', m2='v2', m3='v3')
329
        FR.json = dict(metadata=metadata)
330
        r = self.client.get_server_metadata(vm_id)
331
        FR.json = dict(meta=metadata)
332
        SG.assert_called_once_with(vm_id, '/metadata')
333
        self.assert_dicts_are_equal(r, metadata)
334

    
335
        for k, v in metadata.items():
336
            FR.json = dict(meta={k: v})
337
            r = self.client.get_server_metadata(vm_id, k)
338
            self.assert_dicts_are_equal(r, {k: v})
339
            self.assertEqual(
340
                SG.mock_calls[-1], call(vm_id, '/metadata/%s' % k))
341

    
342
    @patch('%s.servers_post' % compute_pkg, return_value=FR())
343
    def test_update_server_metadata(self, SP):
344
        vm_id = vm_recv['server']['id']
345
        metadata = dict(m1='v1', m2='v2', m3='v3')
346
        FR.json = dict(metadata=metadata)
347
        r = self.client.update_server_metadata(vm_id, **metadata)
348
        self.assert_dicts_are_equal(r, metadata)
349
        SP.assert_called_once_with(
350
            vm_id, 'metadata',
351
            json_data=dict(metadata=metadata), success=201)
352

    
353
    @patch('%s.servers_delete' % compute_pkg, return_value=FR())
354
    def test_delete_server_metadata(self, SD):
355
        vm_id = vm_recv['server']['id']
356
        key = 'metakey'
357
        self.client.delete_server_metadata(vm_id, key)
358
        SD.assert_called_once_with(vm_id, 'metadata/' + key)
359

    
360
    @patch('%s.flavors_get' % compute_pkg, return_value=FR())
361
    def test_list_flavors(self, FG):
362
        FR.json = flavor_list
363
        for cmd in ('', 'detail'):
364
            r = self.client.list_flavors(detail=(cmd == 'detail'))
365
            self.assertEqual(FG.mock_calls[-1], call(command=cmd))
366
            self.assertEqual(r, flavor_list['flavors'])
367

    
368
    @patch('%s.flavors_get' % compute_pkg, return_value=FR())
369
    def test_get_flavor_details(self, FG):
370
        FR.json = dict(flavor=flavor_list['flavors'][0])
371
        r = self.client.get_flavor_details(fid)
372
        FG.assert_called_once_with(fid)
373
        self.assert_dicts_are_equal(r, flavor_list['flavors'][0])
374

    
375
    @patch('%s.images_get' % compute_pkg, return_value=FR())
376
    def test_list_images(self, IG):
377
        FR.json = img_list
378
        for cmd in ('', 'detail'):
379
            r = self.client.list_images(detail=(cmd == 'detail'))
380
            self.assertEqual(IG.mock_calls[-1], call(command=cmd))
381
            expected = img_list['images']
382
            for i in range(len(r)):
383
                self.assert_dicts_are_equal(expected[i], r[i])
384

    
385
    @patch('%s.images_get' % compute_pkg, return_value=FR())
386
    def test_get_image_details(self, IG):
387
        FR.json = img_recv
388
        r = self.client.get_image_details(img_ref)
389
        IG.assert_called_once_with(img_ref)
390
        self.assert_dicts_are_equal(r, img_recv['image'])
391

    
392
    @patch('%s.images_get' % compute_pkg, return_value=FR())
393
    def test_get_image_metadata(self, IG):
394
        for key in ('', '50m3k3y'):
395
            FR.json = dict(meta=img_recv['image']) if (
396
                key) else dict(metadata=img_recv['image'])
397
            r = self.client.get_image_metadata(img_ref, key)
398
            self.assertEqual(IG.mock_calls[-1], call(
399
                '%s' % img_ref,
400
                '/metadata%s' % (('/%s' % key) if key else '')))
401
            self.assert_dicts_are_equal(img_recv['image'], r)
402

    
403
    @patch('%s.servers_delete' % compute_pkg, return_value=FR())
404
    def test_delete_server(self, SD):
405
        vm_id = vm_recv['server']['id']
406
        self.client.delete_server(vm_id)
407
        SD.assert_called_once_with(vm_id)
408

    
409
    @patch('%s.images_delete' % compute_pkg, return_value=FR())
410
    def test_delete_image(self, ID):
411
        self.client.delete_image(img_ref)
412
        ID.assert_called_once_with(img_ref)
413

    
414
    @patch('%s.images_put' % compute_pkg, return_value=FR())
415
    def test_create_image_metadata(self, IP):
416
        (key, val) = ('k1', 'v1')
417
        FR.json = dict(meta=img_recv['image'])
418
        r = self.client.create_image_metadata(img_ref, key, val)
419
        IP.assert_called_once_with(
420
            img_ref, 'metadata/%s' % key,
421
            json_data=dict(meta={key: val}))
422
        self.assert_dicts_are_equal(r, img_recv['image'])
423

    
424
    @patch('%s.images_post' % compute_pkg, return_value=FR())
425
    def test_update_image_metadata(self, IP):
426
        metadata = dict(m1='v1', m2='v2', m3='v3')
427
        FR.json = dict(metadata=metadata)
428
        r = self.client.update_image_metadata(img_ref, **metadata)
429
        IP.assert_called_once_with(
430
            img_ref, 'metadata',
431
            json_data=dict(metadata=metadata))
432
        self.assert_dicts_are_equal(r, metadata)
433

    
434
    @patch('%s.images_delete' % compute_pkg, return_value=FR())
435
    def test_delete_image_metadata(self, ID):
436
        key = 'metakey'
437
        self.client.delete_image_metadata(img_ref, key)
438
        ID.assert_called_once_with(img_ref, '/metadata/%s' % key)
439

    
440

    
441
if __name__ == '__main__':
442
    from sys import argv
443
    from kamaki.clients.test import runTestCase
444
    not_found = True
445
    if not argv[1:] or argv[1] == 'ComputeClient':
446
        not_found = False
447
        runTestCase(ComputeClient, 'Compute Client', argv[2:])
448
    if not argv[1:] or argv[1] == 'ComputeRest':
449
        not_found = False
450
        runTestCase(ComputeRestClient, 'ComputeRest Client', argv[2:])
451
    if not_found:
452
        print('TestCase %s not found' % argv[1])