Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / cyclades / test.py @ 6290b789

History | View | Annotate | Download (21.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 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.get' % rest_pkg, return_value=FR())
107
    def test_networks_get(self, get):
108
        for args in product(
109
                ('', 'net_id'),
110
                ('', 'cmd'),
111
                (200, 204),
112
                ({}, {'k': 'v'})):
113
            (srv_id, command, success, kwargs) = args
114
            self.client.networks_get(*args[:3], **kwargs)
115
            srv_str = '/%s' % srv_id if srv_id else ''
116
            cmd_str = '/%s' % command if command else ''
117
            self.assertEqual(get.mock_calls[-1], call(
118
                '/networks%s%s' % (srv_str, cmd_str),
119
                success=success,
120
                **kwargs))
121

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

    
138
    @patch('%s.set_header' % rest_pkg)
139
    @patch('%s.post' % rest_pkg, return_value=FR())
140
    def test_networks_post(self, post, SH):
141
        for args in product(
142
                ('', 'net_id'),
143
                ('', 'cmd'),
144
                (None, [dict(json="data"), dict(data="json")]),
145
                (202, 204),
146
                ({}, {'k': 'v'})):
147
            (srv_id, command, json_data, success, kwargs) = args
148
            self.client.networks_post(*args[:4], **kwargs)
149
            vm_str = '/%s' % srv_id if srv_id else ''
150
            cmd_str = '/%s' % command if command else ''
151
            if json_data:
152
                json_data = dumps(json_data)
153
                self.assertEqual(SH.mock_calls[-2:], [
154
                    call('Content-Type', 'application/json'),
155
                    call('Content-Length', len(json_data))])
156
            self.assertEqual(post.mock_calls[-1], call(
157
                '/networks%s%s' % (vm_str, cmd_str),
158
                data=json_data, success=success,
159
                **kwargs))
160

    
161
    @patch('%s.set_header' % rest_pkg)
162
    @patch('%s.put' % rest_pkg, return_value=FR())
163
    def test_networks_put(self, put, SH):
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_put(*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(put.mock_calls[-1], call(
180
                '/networks%s%s' % (vm_str, cmd_str),
181
                data=json_data, success=success,
182
                **kwargs))
183

    
184
    @patch('%s.get' % rest_pkg, return_value=FR())
185
    def test_floating_ip_pools_get(self, get):
186
        for args in product(
187
                (200, 204),
188
                ({}, {'k': 'v'})):
189
            success, kwargs = args
190
            r = self.client.floating_ip_pools_get(success, **kwargs)
191
            self.assertTrue(isinstance(r, FR))
192
            self.assertEqual(get.mock_calls[-1], call(
193
                '/os-floating-ip-pools', success=success, **kwargs))
194

    
195
    @patch('%s.get' % rest_pkg, return_value=FR())
196
    def test_floating_ips_get(self, get):
197
        for args in product(
198
                ('fip', ''),
199
                (200, 204),
200
                ({}, {'k': 'v'})):
201
            fip, success, kwargs = args
202
            r = self.client.floating_ips_get(fip, success, **kwargs)
203
            self.assertTrue(isinstance(r, FR))
204
            expected = '' if not fip else '/%s' % fip
205
            self.assertEqual(get.mock_calls[-1], call(
206
                '/os-floating-ips%s' % expected, success=success, **kwargs))
207

    
208
    @patch('%s.set_header' % rest_pkg)
209
    @patch('%s.post' % rest_pkg, return_value=FR())
210
    def test_floating_ips_post(self, post, SH):
211
        for args in product(
212
                (None, [dict(json="data"), dict(data="json")]),
213
                ('fip', ''),
214
                (202, 204),
215
                ({}, {'k': 'v'})):
216
            json_data, fip, success, kwargs = args
217
            self.client.floating_ips_post(*args[:3], **kwargs)
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
            expected = '' if not fip else '/%s' % fip
224
            self.assertEqual(post.mock_calls[-1], call(
225
                '/os-floating-ips%s' % expected,
226
                data=json_data, success=success,
227
                **kwargs))
228

    
229
    @patch('%s.delete' % rest_pkg, return_value=FR())
230
    def test_floating_ips_delete(self, delete):
231
        for args in product(
232
                ('fip1', 'fip2'),
233
                (200, 204),
234
                ({}, {'k': 'v'})):
235
            fip, success, kwargs = args
236
            r = self.client.floating_ips_delete(fip, success, **kwargs)
237
            self.assertTrue(isinstance(r, FR))
238
            self.assertEqual(delete.mock_calls[-1], call(
239
                '/os-floating-ips/%s' % fip, success=success, **kwargs))
240

    
241

    
242
class CycladesClient(TestCase):
243

    
244
    def assert_dicts_are_equal(self, d1, d2):
245
        for k, v in d1.items():
246
            self.assertTrue(k in d2)
247
            if isinstance(v, dict):
248
                self.assert_dicts_are_equal(v, d2[k])
249
            else:
250
                self.assertEqual(unicode(v), unicode(d2[k]))
251

    
252
    """Set up a Cyclades thorough test"""
253
    def setUp(self):
254
        self.url = 'http://cyclades.example.com'
255
        self.token = 'cyc14d3s70k3n'
256
        self.client = cyclades.CycladesClient(self.url, self.token)
257

    
258
    def tearDown(self):
259
        FR.status_code = 200
260
        FR.json = vm_recv
261

    
262
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
263
    def test_shutdown_server(self, SP):
264
        vm_id = vm_recv['server']['id']
265
        self.client.shutdown_server(vm_id)
266
        SP.assert_called_once_with(
267
            vm_id, json_data=dict(shutdown=dict()), success=202)
268

    
269
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
270
    def test_start_server(self, SP):
271
        vm_id = vm_recv['server']['id']
272
        self.client.start_server(vm_id)
273
        SP.assert_called_once_with(
274
            vm_id, json_data=dict(start=dict()), success=202)
275

    
276
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
277
    def test_get_server_console(self, SP):
278
        cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
279
        FR.json = cnsl
280
        vm_id = vm_recv['server']['id']
281
        r = self.client.get_server_console(vm_id)
282
        SP.assert_called_once_with(
283
            vm_id, json_data=dict(console=dict(type='vnc')), success=200)
284
        self.assert_dicts_are_equal(r, cnsl['console'])
285

    
286
    def test_get_firewall_profile(self):
287
        vm_id = vm_recv['server']['id']
288
        v = firewalls['attachments'][0]['firewallProfile']
289
        with patch.object(
290
                cyclades.CycladesClient, 'get_server_details',
291
                return_value=firewalls) as GSD:
292
            r = self.client.get_firewall_profile(vm_id)
293
            GSD.assert_called_once_with(vm_id)
294
            self.assertEqual(r, v)
295
        with patch.object(
296
                cyclades.CycladesClient, 'get_server_details',
297
                return_value=dict()):
298
            self.assertRaises(
299
                ClientError,
300
                self.client.get_firewall_profile,
301
                vm_id)
302

    
303
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
304
    def test_set_firewall_profile(self, SP):
305
        vm_id = vm_recv['server']['id']
306
        v = firewalls['attachments'][0]['firewallProfile']
307
        self.client.set_firewall_profile(vm_id, v)
308
        SP.assert_called_once_with(vm_id, json_data=dict(
309
            firewallProfile=dict(profile=v)), success=202)
310

    
311
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
312
    def test_create_network(self, NP):
313
        net_name = net_send['network']['name']
314
        FR.json = net_recv
315
        full_args = dict(
316
                cidr='192.168.0.0/24',
317
                gateway='192.168.0.1',
318
                type='MAC_FILTERED',
319
                dhcp=True)
320
        test_args = dict(full_args)
321
        test_args.update(dict(empty=None, full=None))
322
        net_exp = dict(dhcp=False, name=net_name, type='MAC_FILTERED')
323
        for arg, val in test_args.items():
324
            kwargs = {} if arg == 'empty' else full_args if (
325
                arg == 'full') else {arg: val}
326
            expected = dict(network=dict(net_exp))
327
            expected['network'].update(kwargs)
328
            r = self.client.create_network(net_name, **kwargs)
329
            self.assertEqual(
330
                NP.mock_calls[-1],
331
                call(json_data=expected, success=202))
332
            self.assert_dicts_are_equal(r, net_recv['network'])
333

    
334
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
335
    def test_connect_server(self, NP):
336
        vm_id = vm_recv['server']['id']
337
        net_id = net_recv['network']['id']
338
        self.client.connect_server(vm_id, net_id)
339
        NP.assert_called_once_with(
340
            net_id, 'action',
341
            json_data=dict(add=dict(serverRef=vm_id)))
342

    
343
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
344
    def test_disconnect_server(self, NP):
345
        net_id, vm_id = net_recv['network']['id'], vm_recv['server']['id']
346
        nic_id = 'nic-%s-%s' % (net_id, vm_id)
347
        vm_nics = [
348
            dict(id=nic_id, network_id=net_id),
349
            dict(id='another-nic-id', network_id='another-net-id'),
350
            dict(id=nic_id * 2, network_id=net_id * 2)]
351
        with patch.object(
352
                cyclades.CycladesClient,
353
                'list_server_nics',
354
                return_value=vm_nics) as LSN:
355
            r = self.client.disconnect_server(vm_id, nic_id)
356
            LSN.assert_called_once_with(vm_id)
357
            NP.assert_called_once_with(
358
                net_id, 'action',
359
                json_data=dict(remove=dict(attachment=nic_id)))
360
            self.assertEqual(r, 1)
361

    
362
    @patch('%s.servers_ips_get' % cyclades_pkg, return_value=FR())
363
    def test_list_server_nics(self, SG):
364
        vm_id = vm_recv['server']['id']
365
        nics = dict(attachments=[dict(id='nic1'), dict(id='nic2')])
366
        FR.json = nics
367
        r = self.client.list_server_nics(vm_id)
368
        SG.assert_called_once_with(vm_id)
369
        expected = nics['attachments']
370
        for i in range(len(r)):
371
            self.assert_dicts_are_equal(r[i], expected[i])
372
        self.assertEqual(i + 1, len(r))
373

    
374
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
375
    def test_list_networks(self, NG):
376
        FR.json = net_list
377
        expected = net_list['networks']
378
        for detail in ('', 'detail'):
379
            r = self.client.list_networks(detail=True if detail else False)
380
            self.assertEqual(NG.mock_calls[-1], call(command=detail))
381
            for i, net in enumerate(expected):
382
                self.assert_dicts_are_equal(r[i], net)
383
            self.assertEqual(i + 1, len(r))
384

    
385
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
386
    def test_list_network_nics(self, NG):
387
        net_id = net_recv['network']['id']
388
        FR.json = net_recv
389
        r = self.client.list_network_nics(net_id)
390
        NG.assert_called_once_with(network_id=net_id)
391
        expected = net_recv['network']['attachments']
392
        for i in range(len(r)):
393
            self.assert_dicts_are_equal(r[i], expected[i])
394

    
395
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
396
    def test_disconnect_network_nics(self, NP):
397
        net_id = net_recv['network']['id']
398
        nics = ['nic1', 'nic2', 'nic3']
399
        with patch.object(
400
                cyclades.CycladesClient,
401
                'list_network_nics',
402
                return_value=nics) as LNN:
403
            self.client.disconnect_network_nics(net_id)
404
            LNN.assert_called_once_with(net_id)
405
            for i in range(len(nics)):
406
                expected = call(net_id, 'action', json_data=dict(
407
                    remove=dict(attachment=nics[i])))
408
                self.assertEqual(expected, NP.mock_calls[i])
409

    
410
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
411
    def test_get_network_details(self, NG):
412
        FR.json = net_recv
413
        net_id = net_recv['network']['id']
414
        r = self.client.get_network_details(net_id)
415
        NG.assert_called_once_with(network_id=net_id)
416
        self.assert_dicts_are_equal(r, net_recv['network'])
417

    
418
    @patch('%s.networks_put' % cyclades_pkg, return_value=FR())
419
    def test_update_network_name(self, NP):
420
        net_id = net_recv['network']['id']
421
        new_name = '%s_new' % net_id
422
        self.client.update_network_name(net_id, new_name)
423
        NP.assert_called_once_with(
424
            network_id=net_id,
425
            json_data=dict(network=dict(name=new_name)))
426

    
427
    def test_delete_network(self):
428
        net_id = net_recv['network']['id']
429
        with patch.object(
430
                cyclades.CycladesClient, 'networks_delete',
431
                return_value=FR()) as ND:
432
            self.client.delete_network(net_id)
433
            ND.assert_called_once_with(net_id)
434
        with patch.object(
435
                cyclades.CycladesClient, 'networks_delete',
436
                side_effect=ClientError('A 421 Error', 421)):
437
            try:
438
                self.client.delete_network(421)
439
            except ClientError as err:
440
                self.assertEqual(err.status, 421)
441
                self.assertEqual(err.details, [
442
                    'Network may be still connected to at least one server'])
443

    
444
    @patch('%s.floating_ip_pools_get' % cyclades_pkg, return_value=FR())
445
    def test_get_floating_ip_pools(self, get):
446
        r = self.client.get_floating_ip_pools()
447
        self.assert_dicts_are_equal(r, FR.json)
448
        self.assertEqual(get.mock_calls[-1], call())
449

    
450
    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
451
    def test_get_floating_ips(self, get):
452
        r = self.client.get_floating_ips()
453
        self.assert_dicts_are_equal(r, FR.json)
454
        self.assertEqual(get.mock_calls[-1], call())
455

    
456
    @patch('%s.floating_ips_post' % cyclades_pkg, return_value=FR())
457
    def test_alloc_floating_ip(self, post):
458
        FR.json = dict(floating_ip=dict(
459
            fixed_ip='fip',
460
            id=1,
461
            instance_id='lala',
462
            ip='102.0.0.1',
463
            pool='pisine'))
464
        for args in product(
465
                (None, 'pisine'),
466
                (None, 'Iwannanip')):
467
            r = self.client.alloc_floating_ip(*args)
468
            pool, address = args
469
            self.assert_dicts_are_equal(r, FR.json['floating_ip'])
470
            json_data = dict()
471
            if pool:
472
                json_data['pool'] = pool
473
                if address:
474
                    json_data['address'] = address
475
            self.assertEqual(post.mock_calls[-1], call(json_data))
476

    
477
    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
478
    def test_get_floating_ip(self, get):
479
        FR.json = dict(floating_ip=dict(
480
            fixed_ip='fip',
481
            id=1,
482
            instance_id='lala',
483
            ip='102.0.0.1',
484
            pool='pisine'))
485
        self.assertRaises(AssertionError, self.client.get_floating_ip, None)
486
        fip = 'fip'
487
        r = self.client.get_floating_ip(fip)
488
        self.assert_dicts_are_equal(r, FR.json['floating_ip'])
489
        self.assertEqual(get.mock_calls[-1], call(fip))
490

    
491
    @patch('%s.floating_ips_delete' % cyclades_pkg, return_value=FR())
492
    def test_delete_floating_ip(self, delete):
493
        self.assertRaises(AssertionError, self.client.delete_floating_ip, None)
494
        fip = 'fip'
495
        r = self.client.delete_floating_ip(fip)
496
        self.assert_dicts_are_equal(r, FR.headers)
497
        self.assertEqual(delete.mock_calls[-1], call(fip))
498

    
499
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
500
    def test_attach_floating_ip(self, spost):
501
        vmid, addr = 42, 'anIpAddress'
502
        for err, args in {
503
                ValueError: ['not a server id', addr],
504
                TypeError: [None, addr],
505
                AssertionError: [vmid, None],
506
                AssertionError: [vmid, '']}.items():
507
            self.assertRaises(
508
                err, self.client.attach_floating_ip, *args)
509
        r = self.client.attach_floating_ip(vmid, addr)
510
        self.assert_dicts_are_equal(r, FR.headers)
511
        expected = dict(addFloatingIp=dict(address=addr))
512
        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
513

    
514
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
515
    def test_detach_floating_ip(self, spost):
516
        vmid, addr = 42, 'anIpAddress'
517
        for err, args in {
518
                ValueError: ['not a server id', addr],
519
                TypeError: [None, addr],
520
                AssertionError: [vmid, None],
521
                AssertionError: [vmid, '']}.items():
522
            self.assertRaises(
523
                err, self.client.detach_floating_ip, *args)
524
        r = self.client.detach_floating_ip(vmid, addr)
525
        self.assert_dicts_are_equal(r, FR.headers)
526
        expected = dict(removeFloatingIp=dict(address=addr))
527
        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
528

    
529

    
530
if __name__ == '__main__':
531
    from sys import argv
532
    from kamaki.clients.test import runTestCase
533
    not_found = True
534
    if not argv[1:] or argv[1] == 'CycladesClient':
535
        not_found = False
536
        runTestCase(CycladesClient, 'Cyclades Client', argv[2:])
537
    if not argv[1:] or argv[1] == 'CycladesRestClient':
538
        not_found = False
539
        runTestCase(CycladesRestClient, 'CycladesRest Client', argv[2:])
540
    if not_found:
541
        print('TestCase %s not found' % argv[1])