Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (20 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, cyclades
40

    
41
img_ref = "1m4g3-r3f3r3nc3"
42
vm_name = "my new VM"
43
fid = 42
44
vm_recv = dict(server=dict(
45
    status="BUILD",
46
    updated="2013-03-01T10:04:00.637152+00:00",
47
    hostId="",
48
    name=vm_name,
49
    imageRef=img_ref,
50
    created="2013-03-01T10:04:00.087324+00:00",
51
    flavorRef=fid,
52
    adminPass="n0n3sh@11p@55",
53
    suspended=False,
54
    progress=0,
55
    id=31173,
56
    metadata=dict(os="debian", users="root")))
57
vm_list = dict(servers=[
58
    dict(name='n1', id=1),
59
    dict(name='n2', id=2)])
60
net_send = dict(network=dict(dhcp=False, name='someNet'))
61
net_recv = dict(network=dict(
62
    status="PENDING",
63
    updated="2013-03-05T15:04:51.758780+00:00",
64
    name="someNet",
65
    created="2013-03-05T15:04:51.758728+00:00",
66
    cidr6=None,
67
    id="2130",
68
    gateway6=None,
69
    public=False,
70
    dhcp=False,
71
    cidr="192.168.1.0/24",
72
    type="MAC_FILTERED",
73
    gateway=None,
74
    attachments=[dict(name='att1'), dict(name='att2')]))
75
net_list = dict(networks=[
76
    dict(id=1, name='n1'),
77
    dict(id=2, name='n2'),
78
    dict(id=3, name='n3')])
79
firewalls = dict(attachments=[
80
    dict(firewallProfile='50m3_pr0f1L3', otherStuff='57uff')])
81

    
82

    
83
class FR(object):
84
    """FR stands for Fake Response"""
85
    json = vm_recv
86
    headers = {}
87
    content = json
88
    status = None
89
    status_code = 200
90

    
91
rest_pkg = 'kamaki.clients.cyclades.CycladesRestClient'
92
cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
93

    
94

    
95
class CycladesRestClient(TestCase):
96

    
97
    """Set up a Cyclades thorough test"""
98
    def setUp(self):
99
        self.url = 'http://cyclades.example.com'
100
        self.token = 'cyc14d3s70k3n'
101
        self.client = cyclades.CycladesRestClient(self.url, self.token)
102

    
103
    def tearDown(self):
104
        FR.json = vm_recv
105

    
106
    @patch('%s.set_param' % rest_pkg)
107
    @patch('%s.get' % rest_pkg, return_value=FR())
108
    def test_servers_get(self, get, SP):
109
        for args in product(
110
                ('', 'vm_id'),
111
                ('', 'cmd'),
112
                (200, 204),
113
                (None, '50m3-d473'),
114
                ({}, {'k': 'v'})):
115
            (srv_id, command, success, changes_since, kwargs) = args
116
            self.client.servers_get(*args[:4], **kwargs)
117
            srv_str = '/%s' % srv_id if srv_id else ''
118
            cmd_str = '/%s' % command if command else ''
119
            self.assertEqual(get.mock_calls[-1], call(
120
                '/servers%s%s' % (srv_str, cmd_str),
121
                success=success,
122
                **kwargs))
123
            if changes_since:
124
                self.assertEqual(
125
                    SP.mock_calls[-1],
126
                    call('changes-since', changes_since, changes_since))
127

    
128
    @patch('%s.get' % rest_pkg, return_value=FR())
129
    def test_networks_get(self, get):
130
        for args in product(
131
                ('', 'net_id'),
132
                ('', 'cmd'),
133
                (200, 204),
134
                ({}, {'k': 'v'})):
135
            (srv_id, command, success, kwargs) = args
136
            self.client.networks_get(*args[:3], **kwargs)
137
            srv_str = '/%s' % srv_id if srv_id else ''
138
            cmd_str = '/%s' % command if command else ''
139
            self.assertEqual(get.mock_calls[-1], call(
140
                '/networks%s%s' % (srv_str, cmd_str),
141
                success=success,
142
                **kwargs))
143

    
144
    @patch('%s.delete' % rest_pkg, return_value=FR())
145
    def test_networks_delete(self, delete):
146
        for args in product(
147
                ('', 'net_id'),
148
                ('', 'cmd'),
149
                (202, 204),
150
                ({}, {'k': 'v'})):
151
            (srv_id, command, success, kwargs) = args
152
            self.client.networks_delete(*args[:3], **kwargs)
153
            srv_str = '/%s' % srv_id if srv_id else ''
154
            cmd_str = '/%s' % command if command else ''
155
            self.assertEqual(delete.mock_calls[-1], call(
156
                '/networks%s%s' % (srv_str, cmd_str),
157
                success=success,
158
                **kwargs))
159

    
160
    @patch('%s.set_header' % rest_pkg)
161
    @patch('%s.post' % rest_pkg, return_value=FR())
162
    def test_networks_post(self, post, SH):
163
        from json import dumps
164
        for args in product(
165
                ('', 'net_id'),
166
                ('', 'cmd'),
167
                (None, [dict(json="data"), dict(data="json")]),
168
                (202, 204),
169
                ({}, {'k': 'v'})):
170
            (srv_id, command, json_data, success, kwargs) = args
171
            self.client.networks_post(*args[:4], **kwargs)
172
            vm_str = '/%s' % srv_id if srv_id else ''
173
            cmd_str = '/%s' % command if command else ''
174
            if json_data:
175
                json_data = dumps(json_data)
176
                self.assertEqual(SH.mock_calls[-2:], [
177
                    call('Content-Type', 'application/json'),
178
                    call('Content-Length', len(json_data))])
179
            self.assertEqual(post.mock_calls[-1], call(
180
                '/networks%s%s' % (vm_str, cmd_str),
181
                data=json_data, success=success,
182
                **kwargs))
183

    
184
    @patch('%s.set_header' % rest_pkg)
185
    @patch('%s.put' % rest_pkg, return_value=FR())
186
    def test_networks_put(self, put, SH):
187
        from json import dumps
188
        for args in product(
189
                ('', 'net_id'),
190
                ('', 'cmd'),
191
                (None, [dict(json="data"), dict(data="json")]),
192
                (202, 204),
193
                ({}, {'k': 'v'})):
194
            (srv_id, command, json_data, success, kwargs) = args
195
            self.client.networks_put(*args[:4], **kwargs)
196
            vm_str = '/%s' % srv_id if srv_id else ''
197
            cmd_str = '/%s' % command if command else ''
198
            if json_data:
199
                json_data = dumps(json_data)
200
                self.assertEqual(SH.mock_calls[-2:], [
201
                    call('Content-Type', 'application/json'),
202
                    call('Content-Length', len(json_data))])
203
            self.assertEqual(put.mock_calls[-1], call(
204
                '/networks%s%s' % (vm_str, cmd_str),
205
                data=json_data, success=success,
206
                **kwargs))
207

    
208
    @patch('%s.get' % rest_pkg, return_value=FR())
209
    def test_floating_ip_pools_get(self, get):
210
        for args in product(
211
                (200, 204),
212
                ({}, {'k': 'v'})):
213
            success, kwargs = args
214
            r = self.client.floating_ip_pools_get(success, **kwargs)
215
            self.assertTrue(isinstance(r, FR))
216
            self.assertEqual(get.mock_calls[-1], call(
217
                '/os-floating-ip-pools', success=success, **kwargs))
218

    
219
    @patch('%s.get' % rest_pkg, return_value=FR())
220
    def test_floating_ips_get(self, get):
221
        for args in product(
222
                (200, 204),
223
                ({}, {'k': 'v'})):
224
            success, kwargs = args
225
            r = self.client.floating_ips_get(success, **kwargs)
226
            self.assertTrue(isinstance(r, FR))
227
            self.assertEqual(get.mock_calls[-1], call(
228
                '/os-floating-ips', success=success, **kwargs))
229

    
230
    @patch('%s.set_header' % rest_pkg)
231
    @patch('%s.post' % rest_pkg, return_value=FR())
232
    def test_floating_ips_post(self, post, SH):
233
        for args in product(
234
                (None, [dict(json="data"), dict(data="json")]),
235
                (202, 204),
236
                ({}, {'k': 'v'})):
237
            (json_data, success, kwargs) = args
238
            self.client.floating_ips_post(*args[:2], **kwargs)
239
            if json_data:
240
                json_data = dumps(json_data)
241
                self.assertEqual(SH.mock_calls[-2:], [
242
                    call('Content-Type', 'application/json'),
243
                    call('Content-Length', len(json_data))])
244
            self.assertEqual(post.mock_calls[-1], call(
245
                '/os-floating-ips',
246
                data=json_data, success=success,
247
                **kwargs))
248

    
249
    @patch('%s.get' % rest_pkg, return_value=FR())
250
    def test_floating_ip_get(self, get):
251
        for args in product(
252
                ('fip1', 'fip2'),
253
                (200, 204),
254
                ({}, {'k': 'v'})):
255
            fip, success, kwargs = args
256
            r = self.client.floating_ip_get(fip, success, **kwargs)
257
            self.assertTrue(isinstance(r, FR))
258
            self.assertEqual(get.mock_calls[-1], call(
259
                '/os-floating-ip/%s' % fip, success=success, **kwargs))
260

    
261
    @patch('%s.delete' % rest_pkg, return_value=FR())
262
    def test_floating_ip_delete(self, delete):
263
        for args in product(
264
                ('fip1', 'fip2'),
265
                (200, 204),
266
                ({}, {'k': 'v'})):
267
            fip, success, kwargs = args
268
            r = self.client.floating_ip_delete(fip, success, **kwargs)
269
            self.assertTrue(isinstance(r, FR))
270
            self.assertEqual(delete.mock_calls[-1], call(
271
                '/os-floating-ip/%s' % fip, success=success, **kwargs))
272

    
273

    
274
class CycladesClient(TestCase):
275

    
276
    def assert_dicts_are_equal(self, d1, d2):
277
        for k, v in d1.items():
278
            self.assertTrue(k in d2)
279
            if isinstance(v, dict):
280
                self.assert_dicts_are_equal(v, d2[k])
281
            else:
282
                self.assertEqual(unicode(v), unicode(d2[k]))
283

    
284
    """Set up a Cyclades thorough test"""
285
    def setUp(self):
286
        self.url = 'http://cyclades.example.com'
287
        self.token = 'cyc14d3s70k3n'
288
        self.client = cyclades.CycladesClient(self.url, self.token)
289

    
290
    def tearDown(self):
291
        FR.status_code = 200
292
        FR.json = vm_recv
293

    
294
    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
295
    def test_list_servers(self, SG):
296
        FR.json = vm_list
297
        for detail, since in ((0, 0), (True, 0), (0, 'd473'), (True, 'd473')):
298
            r = self.client.list_servers(detail=detail, changes_since=since)
299
            self.assertEqual(SG.mock_calls[-1], call(
300
                command='detail' if detail else '',
301
                changes_since=since))
302
            expected = vm_list['servers']
303
            for i, vm in enumerate(r):
304
                self.assert_dicts_are_equal(vm, expected[i])
305
            self.assertEqual(i + 1, len(expected))
306

    
307
    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
308
    def test_shutdown_server(self, SP):
309
        vm_id = vm_recv['server']['id']
310
        self.client.shutdown_server(vm_id)
311
        SP.assert_called_once_with(
312
            vm_id, 'action',
313
            json_data=dict(shutdown=dict()), success=202)
314

    
315
    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
316
    def test_start_server(self, SP):
317
        vm_id = vm_recv['server']['id']
318
        self.client.start_server(vm_id)
319
        SP.assert_called_once_with(
320
            vm_id, 'action',
321
            json_data=dict(start=dict()), success=202)
322

    
323
    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
324
    def test_get_server_console(self, SP):
325
        cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
326
        FR.json = cnsl
327
        vm_id = vm_recv['server']['id']
328
        r = self.client.get_server_console(vm_id)
329
        SP.assert_called_once_with(
330
            vm_id, 'action',
331
            json_data=dict(console=dict(type='vnc')), success=200)
332
        self.assert_dicts_are_equal(r, cnsl['console'])
333

    
334
    def test_get_firewall_profile(self):
335
        vm_id = vm_recv['server']['id']
336
        v = firewalls['attachments'][0]['firewallProfile']
337
        with patch.object(
338
                cyclades.CycladesClient, 'get_server_details',
339
                return_value=firewalls) as GSD:
340
            r = self.client.get_firewall_profile(vm_id)
341
            GSD.assert_called_once_with(vm_id)
342
            self.assertEqual(r, v)
343
        with patch.object(
344
                cyclades.CycladesClient, 'get_server_details',
345
                return_value=dict()):
346
            self.assertRaises(
347
                ClientError,
348
                self.client.get_firewall_profile,
349
                vm_id)
350

    
351
    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
352
    def test_set_firewall_profile(self, SP):
353
        vm_id = vm_recv['server']['id']
354
        v = firewalls['attachments'][0]['firewallProfile']
355
        self.client.set_firewall_profile(vm_id, v)
356
        SP.assert_called_once_with(
357
            vm_id, 'action',
358
            json_data=dict(firewallProfile=dict(profile=v)), success=202)
359

    
360
    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
361
    def test_get_server_stats(self, SG):
362
        vm_id = vm_recv['server']['id']
363
        stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
364
        FR.json = dict(stats=stats)
365
        r = self.client.get_server_stats(vm_id)
366
        SG.assert_called_once_with(vm_id, 'stats')
367
        self.assert_dicts_are_equal(stats, r)
368

    
369
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
370
    def test_create_network(self, NP):
371
        net_name = net_send['network']['name']
372
        FR.json = net_recv
373
        full_args = dict(
374
                cidr='192.168.0.0/24',
375
                gateway='192.168.0.1',
376
                type='MAC_FILTERED',
377
                dhcp=True)
378
        test_args = dict(full_args)
379
        test_args.update(dict(empty=None, full=None))
380
        net_exp = dict(dhcp=False, name=net_name, type='MAC_FILTERED')
381
        for arg, val in test_args.items():
382
            kwargs = {} if arg == 'empty' else full_args if (
383
                arg == 'full') else {arg: val}
384
            expected = dict(network=dict(net_exp))
385
            expected['network'].update(kwargs)
386
            r = self.client.create_network(net_name, **kwargs)
387
            self.assertEqual(
388
                NP.mock_calls[-1],
389
                call(json_data=expected, success=202))
390
            self.assert_dicts_are_equal(r, net_recv['network'])
391

    
392
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
393
    def test_connect_server(self, NP):
394
        vm_id = vm_recv['server']['id']
395
        net_id = net_recv['network']['id']
396
        self.client.connect_server(vm_id, net_id)
397
        NP.assert_called_once_with(
398
            net_id, 'action',
399
            json_data=dict(add=dict(serverRef=vm_id)))
400

    
401
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
402
    def test_disconnect_server(self, NP):
403
        net_id, vm_id = net_recv['network']['id'], vm_recv['server']['id']
404
        nic_id = 'nic-%s-%s' % (net_id, vm_id)
405
        vm_nics = [
406
            dict(id=nic_id, network_id=net_id),
407
            dict(id='another-nic-id', network_id='another-net-id'),
408
            dict(id=nic_id * 2, network_id=net_id * 2)]
409
        with patch.object(
410
                cyclades.CycladesClient,
411
                'list_server_nics',
412
                return_value=vm_nics) as LSN:
413
            r = self.client.disconnect_server(vm_id, nic_id)
414
            LSN.assert_called_once_with(vm_id)
415
            NP.assert_called_once_with(
416
                net_id, 'action',
417
                json_data=dict(remove=dict(attachment=nic_id)))
418
            self.assertEqual(r, 1)
419

    
420
    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
421
    def test_list_server_nics(self, SG):
422
        vm_id = vm_recv['server']['id']
423
        nics = dict(addresses=[dict(id='nic1'), dict(id='nic2')])
424
        FR.json = nics
425
        r = self.client.list_server_nics(vm_id)
426
        SG.assert_called_once_with(vm_id, 'ips')
427
        expected = nics['addresses']
428
        for i in range(len(r)):
429
            self.assert_dicts_are_equal(r[i], expected[i])
430
        self.assertEqual(i + 1, len(r))
431

    
432
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
433
    def test_list_networks(self, NG):
434
        FR.json = net_list
435
        expected = net_list['networks']
436
        for detail in ('', 'detail'):
437
            r = self.client.list_networks(detail=True if detail else False)
438
            self.assertEqual(NG.mock_calls[-1], call(command=detail))
439
            for i, net in enumerate(expected):
440
                self.assert_dicts_are_equal(r[i], net)
441
            self.assertEqual(i + 1, len(r))
442

    
443
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
444
    def test_list_network_nics(self, NG):
445
        net_id = net_recv['network']['id']
446
        FR.json = net_recv
447
        r = self.client.list_network_nics(net_id)
448
        NG.assert_called_once_with(network_id=net_id)
449
        expected = net_recv['network']['attachments']
450
        for i in range(len(r)):
451
            self.assert_dicts_are_equal(r[i], expected[i])
452

    
453
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
454
    def test_disconnect_network_nics(self, NP):
455
        net_id = net_recv['network']['id']
456
        nics = ['nic1', 'nic2', 'nic3']
457
        with patch.object(
458
                cyclades.CycladesClient,
459
                'list_network_nics',
460
                return_value=nics) as LNN:
461
            self.client.disconnect_network_nics(net_id)
462
            LNN.assert_called_once_with(net_id)
463
            for i in range(len(nics)):
464
                expected = call(net_id, 'action', json_data=dict(
465
                    remove=dict(attachment=nics[i])))
466
                self.assertEqual(expected, NP.mock_calls[i])
467

    
468
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
469
    def test_get_network_details(self, NG):
470
        FR.json = net_recv
471
        net_id = net_recv['network']['id']
472
        r = self.client.get_network_details(net_id)
473
        NG.assert_called_once_with(network_id=net_id)
474
        self.assert_dicts_are_equal(r, net_recv['network'])
475

    
476
    @patch('%s.networks_put' % cyclades_pkg, return_value=FR())
477
    def test_update_network_name(self, NP):
478
        net_id = net_recv['network']['id']
479
        new_name = '%s_new' % net_id
480
        self.client.update_network_name(net_id, new_name)
481
        NP.assert_called_once_with(
482
            network_id=net_id,
483
            json_data=dict(network=dict(name=new_name)))
484

    
485
    def test_delete_network(self):
486
        net_id = net_recv['network']['id']
487
        with patch.object(
488
                cyclades.CycladesClient, 'networks_delete',
489
                return_value=FR()) as ND:
490
            self.client.delete_network(net_id)
491
            ND.assert_called_once_with(net_id)
492
        with patch.object(
493
                cyclades.CycladesClient, 'networks_delete',
494
                side_effect=ClientError('A 421 Error', 421)):
495
            try:
496
                self.client.delete_network(421)
497
            except ClientError as err:
498
                self.assertEqual(err.status, 421)
499
                self.assertEqual(err.details, [
500
                    'Network may be still connected to at least one server'])
501

    
502

    
503
if __name__ == '__main__':
504
    from sys import argv
505
    from kamaki.clients.test import runTestCase
506
    not_found = True
507
    if not argv[1:] or argv[1] == 'CycladesClient':
508
        not_found = False
509
        runTestCase(CycladesClient, 'Cyclades Client', argv[2:])
510
    if not argv[1:] or argv[1] == 'CycladesRestClient':
511
        not_found = False
512
        runTestCase(CycladesRestClient, 'CycladesRest Client', argv[2:])
513
    if not_found:
514
        print('TestCase %s not found' % argv[1])