Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (17 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_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])