Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / cyclades / test.py @ 8c40c4a6

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
from mock import patch, Mock, call
34
from unittest import TestCase
35
from json import loads
36

    
37
from kamaki.clients import Client, ClientError
38
from kamaki.clients.cyclades import CycladesClient
39
from kamaki.clients.cyclades_rest_api import CycladesClientApi
40

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

    
115

    
116
class FR(object):
117
    """FR stands for Fake Response"""
118
    json = vm_recv
119
    headers = {}
120
    content = json
121
    status = None
122
    status_code = 200
123

    
124
    def release(self):
125
        pass
126

    
127
khttp = 'kamaki.clients.connection.kamakicon.KamakiHTTPConnection'
128
cyclades_pkg = 'kamaki.clients.cyclades.CycladesClient'
129

    
130

    
131
class Cyclades(TestCase):
132

    
133
    def assert_dicts_are_equal(self, d1, d2):
134
        for k, v in d1.items():
135
            self.assertTrue(k in d2)
136
            if isinstance(v, dict):
137
                self.assert_dicts_are_equal(v, d2[k])
138
            else:
139
                self.assertEqual(unicode(v), unicode(d2[k]))
140

    
141
    """Set up a Cyclades thorough test"""
142
    def setUp(self):
143
        self.url = 'http://cyclades.example.com'
144
        self.token = 'cyc14d3s70k3n'
145
        self.client = CycladesClient(self.url, self.token)
146
        from kamaki.clients.connection.kamakicon import KamakiHTTPConnection
147
        self.C = KamakiHTTPConnection
148

    
149
    def tearDown(self):
150
        FR.status_code = 200
151
        FR.json = vm_recv
152

    
153
    def test_list_servers(self):
154
        FR.json = vm_list
155
        with patch.object(
156
                self.C,
157
                'perform_request',
158
                return_value=FR()) as perform_req:
159
            r = self.client.list_servers()
160
            self.assertEqual(self.client.http_client.url, self.url)
161
            self.assertEqual(self.client.http_client.path, '/servers')
162
            (method, data, a_headers, a_params) = perform_req.call_args[0]
163
            self.assert_dicts_are_equal(dict(values=r), vm_list['servers'])
164
            r = self.client.list_servers(detail=True)
165
            self.assertEqual(self.client.http_client.url, self.url)
166
            self.assertEqual(self.client.http_client.path, '/servers/detail')
167
        with patch.object(
168
                CycladesClientApi,
169
                'servers_get',
170
                return_value=FR()) as servers_get:
171
            self.client.list_servers(changes_since=True)
172
            self.assertTrue(servers_get.call_args[1]['changes_since'])
173

    
174
    @patch('%s.perform_request' % khttp, return_value=FR())
175
    def test_get_flavor_details(self, PR):
176
        FR.json = dict(flavor=flavor_list['flavors'])
177
        r = self.client.get_flavor_details(fid)
178
        self.assertEqual(self.client.http_client.url, self.url)
179
        self.assertEqual(self.client.http_client.path, '/flavors/%s' % fid)
180
        self.assert_dicts_are_equal(r, flavor_list['flavors'])
181

    
182
    @patch('%s.perform_request' % khttp, return_value=FR())
183
    def test_list_images(self, PR):
184
        FR.json = img_list
185
        r = self.client.list_images()
186
        self.assertEqual(self.client.http_client.url, self.url)
187
        self.assertEqual(self.client.http_client.path, '/images')
188
        expected = img_list['images']['values']
189
        for i in range(len(r)):
190
            self.assert_dicts_are_equal(expected[i], r[i])
191
        self.client.list_images(detail=True)
192
        self.assertEqual(self.client.http_client.url, self.url)
193
        self.assertEqual(self.client.http_client.path, '/images/detail')
194

    
195
    @patch('%s.perform_request' % khttp, return_value=FR())
196
    def test_get_image_details(self, PR):
197
        FR.json = img_recv
198
        r = self.client.get_image_details(img_ref)
199
        self.assertEqual(self.client.http_client.url, self.url)
200
        self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
201
        self.assert_dicts_are_equal(r, img_recv['image'])
202

    
203
    @patch('%s.images_get' % cyclades_pkg, return_value=FR())
204
    def test_get_image_metadata(self, IG):
205
        FR.json = dict(metadata=dict(values=img_recv['image']))
206
        r = self.client.get_image_metadata(img_ref)
207
        self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta'))
208
        self.assert_dicts_are_equal(img_recv['image'], r)
209
        FR.json = dict(meta=img_recv['image'])
210
        key = 'somekey'
211
        self.client.get_image_metadata(img_ref, key)
212
        self.assertEqual(IG.call_args[0], ('%s' % img_ref, '/meta/%s' % key))
213

    
214
    @patch('%s.perform_request' % khttp, return_value=FR())
215
    def test_shutdown_server(self, PR):
216
        vm_id = vm_recv['server']['id']
217
        FR.status_code = 202
218
        self.client.shutdown_server(vm_id)
219
        self.assertEqual(self.client.http_client.url, self.url)
220
        self.assertEqual(
221
            self.client.http_client.path,
222
            '/servers/%s/action' % vm_id)
223
        self.assertEqual(
224
            PR.call_args[0],
225
            ('post',  '{"shutdown": {}}', {}, {}))
226

    
227
    @patch('%s.perform_request' % khttp, return_value=FR())
228
    def test_start_server(self, PR):
229
        vm_id = vm_recv['server']['id']
230
        FR.status_code = 202
231
        self.client.start_server(vm_id)
232
        self.assertEqual(self.client.http_client.url, self.url)
233
        self.assertEqual(
234
            self.client.http_client.path,
235
            '/servers/%s/action' % vm_id)
236
        self.assertEqual(PR.call_args[0], ('post',  '{"start": {}}', {}, {}))
237

    
238
    @patch('%s.perform_request' % khttp, return_value=FR())
239
    def test_get_server_console(self, PR):
240
        cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
241
        FR.json = cnsl
242
        vm_id = vm_recv['server']['id']
243
        r = self.client.get_server_console(vm_id)
244
        self.assertEqual(self.client.http_client.url, self.url)
245
        self.assertEqual(
246
            self.client.http_client.path,
247
            '/servers/%s/action' % vm_id)
248
        self.assert_dicts_are_equal(cnsl['console'], r)
249
        self.assertEqual(
250
            PR.call_args[0],
251
            ('post',  '{"console": {"type": "vnc"}}', {}, {}))
252

    
253
    def test_get_firewall_profile(self):
254
        vm_id = vm_recv['server']['id']
255
        v = 'Some profile'
256
        ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
257
        with patch.object(
258
                CycladesClient,
259
                'get_server_details',
260
                return_value=ret) as GSD:
261
            r = self.client.get_firewall_profile(vm_id)
262
            self.assertEqual(r, v)
263
            self.assertEqual(GSD.call_args[0], (vm_id,))
264
            ret['attachments']['values'][0].pop('firewallProfile')
265
            self.assertRaises(
266
                ClientError,
267
                self.client.get_firewall_profile,
268
                vm_id)
269

    
270
    @patch('%s.perform_request' % khttp, return_value=FR())
271
    def test_set_firewall_profile(self, PR):
272
        vm_id = vm_recv['server']['id']
273
        v = 'Some profile'
274
        FR.status_code = 202
275
        self.client.set_firewall_profile(vm_id, v)
276
        self.assertEqual(self.client.http_client.url, self.url)
277
        self.assertEqual(
278
            self.client.http_client.path,
279
            '/servers/%s/action' % vm_id)
280
        self.assertEqual(PR.call_args[0], (
281
            'post',
282
            '{"firewallProfile": {"profile": "%s"}}' % v,
283
            {},
284
            {}))
285

    
286
    @patch('%s.perform_request' % khttp, return_value=FR())
287
    def test_get_server_stats(self, PR):
288
        vm_id = vm_recv['server']['id']
289
        stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
290
        FR.json = dict(stats=stats)
291
        r = self.client.get_server_stats(vm_id)
292
        self.assertEqual(self.client.http_client.url, self.url)
293
        self.assertEqual(
294
            self.client.http_client.path,
295
            '/servers/%s/stats' % vm_id)
296
        self.assert_dicts_are_equal(stats, r)
297

    
298
    @patch('%s.perform_request' % khttp, return_value=FR())
299
    def test_create_network(self, PR):
300
        net_name = net_send['network']['name']
301
        FR.json = net_recv
302
        FR.status_code = 202
303
        full_args = dict(
304
                cidr='192.168.0.0/24',
305
                gateway='192.168.0.1',
306
                type='MAC_FILTERED',
307
                dhcp=True)
308
        test_args = dict(full_args)
309
        test_args.update(dict(empty=None, full=None))
310
        for arg, val in test_args.items():
311
            kwargs = {} if arg == 'empty' else full_args if (
312
                arg == 'full') else {arg: val}
313
            r = self.client.create_network(net_name, **kwargs)
314
            self.assertEqual(self.client.http_client.url, self.url)
315
            self.assertEqual(
316
                self.client.http_client.path,
317
                '/networks')
318
            self.assert_dicts_are_equal(r, net_recv['network'])
319
            data = PR.call_args[0][1]
320
            expected = dict(network=dict(net_send['network']))
321
            expected['network'].update(kwargs)
322
            self.assert_dicts_are_equal(loads(data), expected)
323

    
324
    @patch('%s.perform_request' % khttp, return_value=FR())
325
    def test_connect_server(self, PR):
326
        vm_id = vm_recv['server']['id']
327
        net_id = net_recv['network']['id']
328
        FR.status_code = 202
329
        self.client.connect_server(vm_id, net_id)
330
        self.assertEqual(self.client.http_client.url, self.url)
331
        self.assertEqual(
332
            self.client.http_client.path,
333
            '/networks/%s/action' % net_id)
334
        self.assertEqual(
335
            PR.call_args[0],
336
            ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
337

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

    
359
    @patch('%s.perform_request' % khttp, return_value=FR())
360
    def test_list_server_nics(self, PR):
361
        vm_id = vm_recv['server']['id']
362
        nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
363
        FR.json = nics
364
        r = self.client.list_server_nics(vm_id)
365
        self.assertEqual(self.client.http_client.url, self.url)
366
        self.assertEqual(
367
            self.client.http_client.path,
368
            '/servers/%s/ips' % vm_id)
369
        expected = nics['addresses']['values']
370
        for i in range(len(r)):
371
            self.assert_dicts_are_equal(r[i], expected[i])
372

    
373
    @patch('%s.perform_request' % khttp, return_value=FR())
374
    def test_list_networks(self, PR):
375
        FR.json = net_list
376
        r = self.client.list_networks()
377
        self.assertEqual(self.client.http_client.url, self.url)
378
        self.assertEqual(self.client.http_client.path, '/networks')
379
        expected = net_list['networks']['values']
380
        for i in range(len(r)):
381
            self.assert_dicts_are_equal(expected[i], r[i])
382
        self.client.list_networks(detail=True)
383
        self.assertEqual(self.client.http_client.url, self.url)
384
        self.assertEqual(self.client.http_client.path, '/networks/detail')
385

    
386
    @patch('%s.perform_request' % khttp, return_value=FR())
387
    def test_list_network_nics(self, PR):
388
        net_id = net_recv['network']['id']
389
        FR.json = net_recv
390
        r = self.client.list_network_nics(net_id)
391
        self.assertEqual(self.client.http_client.url, self.url)
392
        self.assertEqual(
393
            self.client.http_client.path,
394
            '/networks/%s' % net_id)
395
        expected = net_recv['network']['attachments']['values']
396
        for i in range(len(r)):
397
            self.assert_dicts_are_equal(r[i], expected[i])
398

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

    
414
    @patch('%s.perform_request' % khttp, return_value=FR())
415
    def test_get_network_details(self, PR):
416
        FR.json = net_recv
417
        net_id = net_recv['network']['id']
418
        r = self.client.get_network_details(net_id)
419
        self.assertEqual(self.client.http_client.url, self.url)
420
        self.assertEqual(
421
            self.client.http_client.path,
422
            '/networks/%s' % net_id)
423
        self.assert_dicts_are_equal(r, net_recv['network'])
424

    
425
    @patch('%s.perform_request' % khttp, return_value=FR())
426
    def test_update_network_name(self, PR):
427
        net_id = net_recv['network']['id']
428
        new_name = '%s_new' % net_id
429
        FR.status_code = 204
430
        self.client.update_network_name(net_id, new_name)
431
        self.assertEqual(self.client.http_client.url, self.url)
432
        self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
433
        (method, data, a_headers, a_params) = PR.call_args[0]
434
        self.assert_dicts_are_equal(
435
            dict(network=dict(name=new_name)),
436
            loads(data))
437

    
438
    @patch('%s.perform_request' % khttp, return_value=FR())
439
    def test_delete_server(self, PR):
440
        vm_id = vm_recv['server']['id']
441
        FR.status_code = 204
442
        self.client.delete_server(vm_id)
443
        self.assertEqual(self.client.http_client.url, self.url)
444
        self.assertEqual(self.client.http_client.path, '/servers/%s' % vm_id)
445

    
446
    @patch('%s.perform_request' % khttp, return_value=FR())
447
    def test_delete_image(self, PR):
448
        FR.status_code = 204
449
        self.client.delete_image(img_ref)
450
        self.assertEqual(self.client.http_client.url, self.url)
451
        self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
452

    
453
    @patch('%s.perform_request' % khttp, return_value=FR())
454
    def test_delete_network(self, PR):
455
        net_id = net_recv['network']['id']
456
        FR.status_code = 204
457
        self.client.delete_network(net_id)
458
        self.assertEqual(self.client.http_client.url, self.url)
459
        self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
460

    
461
    @patch('%s.perform_request' % khttp, return_value=FR())
462
    def test_create_image_metadata(self, PR):
463
        metadata = dict(m1='v1', m2='v2', m3='v3')
464
        FR.json = dict(meta=img_recv['image'])
465
        self.assertRaises(
466
            ClientError,
467
            self.client.create_image_metadata,
468
            img_ref, 'key', 'value')
469
        FR.status_code = 201
470
        for k, v in metadata.items():
471
            r = self.client.create_image_metadata(img_ref, k, v)
472
            self.assertEqual(self.client.http_client.url, self.url)
473
            self.assertEqual(
474
                self.client.http_client.path,
475
                '/images/%s/meta/%s' % (img_ref, k))
476
            (method, data, a_headers, a_params) = PR.call_args[0]
477
            self.assertEqual(dict(meta={k: v}), loads(data))
478
            self.assert_dicts_are_equal(r, img_recv['image'])
479

    
480
    @patch('%s.images_post' % cyclades_pkg, return_value=FR())
481
    def test_update_image_metadata(self, images_post):
482
        metadata = dict(m1='v1', m2='v2', m3='v3')
483
        FR.json = dict(metadata=metadata)
484
        r = self.client.update_image_metadata(img_ref, **metadata)
485
        self.assert_dicts_are_equal(r, metadata)
486
        (called_id, cmd) = images_post.call_args[0]
487
        self.assertEqual(called_id, img_ref)
488
        self.assertEqual(cmd, 'meta')
489
        data = images_post.call_args[1]['json_data']
490
        self.assert_dicts_are_equal(data, dict(metadata=metadata))
491

    
492
    @patch('%s.images_delete' % cyclades_pkg, return_value=FR())
493
    def test_delete_image_metadata(self, images_delete):
494
        key = 'metakey'
495
        self.client.delete_image_metadata(img_ref, key)
496
        self.assertEqual(
497
            (img_ref, '/meta/' + key),
498
            images_delete.call_args[0])
499

    
500
if __name__ == '__main__':
501
    from sys import argv
502
    from kamaki.clients.test import runTestCase
503
    runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])