Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / cyclades / test.py @ 0651cda3

History | View | Annotate | Download (17.9 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_shutdown_server(self, PR):
176
        vm_id = vm_recv['server']['id']
177
        FR.status_code = 202
178
        self.client.shutdown_server(vm_id)
179
        self.assertEqual(self.client.http_client.url, self.url)
180
        self.assertEqual(
181
            self.client.http_client.path,
182
            '/servers/%s/action' % vm_id)
183
        self.assertEqual(
184
            PR.call_args[0],
185
            ('post',  '{"shutdown": {}}', {}, {}))
186

    
187
    @patch('%s.perform_request' % khttp, return_value=FR())
188
    def test_start_server(self, PR):
189
        vm_id = vm_recv['server']['id']
190
        FR.status_code = 202
191
        self.client.start_server(vm_id)
192
        self.assertEqual(self.client.http_client.url, self.url)
193
        self.assertEqual(
194
            self.client.http_client.path,
195
            '/servers/%s/action' % vm_id)
196
        self.assertEqual(PR.call_args[0], ('post',  '{"start": {}}', {}, {}))
197

    
198
    @patch('%s.perform_request' % khttp, return_value=FR())
199
    def test_get_server_console(self, PR):
200
        cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
201
        FR.json = cnsl
202
        vm_id = vm_recv['server']['id']
203
        r = self.client.get_server_console(vm_id)
204
        self.assertEqual(self.client.http_client.url, self.url)
205
        self.assertEqual(
206
            self.client.http_client.path,
207
            '/servers/%s/action' % vm_id)
208
        self.assert_dicts_are_equal(cnsl['console'], r)
209
        self.assertEqual(
210
            PR.call_args[0],
211
            ('post',  '{"console": {"type": "vnc"}}', {}, {}))
212

    
213
    def test_get_firewall_profile(self):
214
        vm_id = vm_recv['server']['id']
215
        v = 'Some profile'
216
        ret = {'attachments': {'values': [{'firewallProfile': v, 1:1}]}}
217
        with patch.object(
218
                CycladesClient,
219
                'get_server_details',
220
                return_value=ret) as GSD:
221
            r = self.client.get_firewall_profile(vm_id)
222
            self.assertEqual(r, v)
223
            self.assertEqual(GSD.call_args[0], (vm_id,))
224
            ret['attachments']['values'][0].pop('firewallProfile')
225
            self.assertRaises(
226
                ClientError,
227
                self.client.get_firewall_profile,
228
                vm_id)
229

    
230
    @patch('%s.perform_request' % khttp, return_value=FR())
231
    def test_set_firewall_profile(self, PR):
232
        vm_id = vm_recv['server']['id']
233
        v = 'Some profile'
234
        FR.status_code = 202
235
        self.client.set_firewall_profile(vm_id, v)
236
        self.assertEqual(self.client.http_client.url, self.url)
237
        self.assertEqual(
238
            self.client.http_client.path,
239
            '/servers/%s/action' % vm_id)
240
        self.assertEqual(PR.call_args[0], (
241
            'post',
242
            '{"firewallProfile": {"profile": "%s"}}' % v,
243
            {},
244
            {}))
245

    
246
    @patch('%s.perform_request' % khttp, return_value=FR())
247
    def test_get_server_stats(self, PR):
248
        vm_id = vm_recv['server']['id']
249
        stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
250
        FR.json = dict(stats=stats)
251
        r = self.client.get_server_stats(vm_id)
252
        self.assertEqual(self.client.http_client.url, self.url)
253
        self.assertEqual(
254
            self.client.http_client.path,
255
            '/servers/%s/stats' % vm_id)
256
        self.assert_dicts_are_equal(stats, r)
257

    
258
    @patch('%s.perform_request' % khttp, return_value=FR())
259
    def test_create_network(self, PR):
260
        net_name = net_send['network']['name']
261
        FR.json = net_recv
262
        FR.status_code = 202
263
        full_args = dict(
264
                cidr='192.168.0.0/24',
265
                gateway='192.168.0.1',
266
                type='MAC_FILTERED',
267
                dhcp=True)
268
        test_args = dict(full_args)
269
        test_args.update(dict(empty=None, full=None))
270
        for arg, val in test_args.items():
271
            kwargs = {} if arg == 'empty' else full_args if (
272
                arg == 'full') else {arg: val}
273
            r = self.client.create_network(net_name, **kwargs)
274
            self.assertEqual(self.client.http_client.url, self.url)
275
            self.assertEqual(
276
                self.client.http_client.path,
277
                '/networks')
278
            self.assert_dicts_are_equal(r, net_recv['network'])
279
            data = PR.call_args[0][1]
280
            expected = dict(network=dict(net_send['network']))
281
            expected['network'].update(kwargs)
282
            self.assert_dicts_are_equal(loads(data), expected)
283

    
284
    @patch('%s.perform_request' % khttp, return_value=FR())
285
    def test_connect_server(self, PR):
286
        vm_id = vm_recv['server']['id']
287
        net_id = net_recv['network']['id']
288
        FR.status_code = 202
289
        self.client.connect_server(vm_id, net_id)
290
        self.assertEqual(self.client.http_client.url, self.url)
291
        self.assertEqual(
292
            self.client.http_client.path,
293
            '/networks/%s/action' % net_id)
294
        self.assertEqual(
295
            PR.call_args[0],
296
            ('post', '{"add": {"serverRef": %s}}' % vm_id, {}, {}))
297

    
298
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
299
    def test_disconnect_server(self, NP):
300
        vm_id = vm_recv['server']['id']
301
        net_id = net_recv['network']['id']
302
        nic_id = 'nic-%s-%s' % (net_id, vm_id)
303
        vm_nics = [
304
            dict(id=nic_id, network_id=net_id),
305
            dict(id='another-nic-id', network_id='another-net-id'),
306
            dict(id=nic_id * 2, network_id=net_id * 2)]
307
        with patch.object(
308
                CycladesClient,
309
                'list_server_nics',
310
                return_value=vm_nics) as LSN:
311
            r = self.client.disconnect_server(vm_id, nic_id)
312
            self.assertEqual(r, 1)
313
            self.assertEqual(LSN.call_args[0], (vm_id,))
314
            self.assertEqual(NP.call_args[0], (net_id, 'action'))
315
            self.assertEqual(
316
                NP.call_args[1],
317
                dict(json_data=dict(remove=dict(attachment=nic_id))))
318

    
319
    @patch('%s.perform_request' % khttp, return_value=FR())
320
    def test_list_server_nics(self, PR):
321
        vm_id = vm_recv['server']['id']
322
        nics = dict(addresses=dict(values=[dict(id='nic1'), dict(id='nic2')]))
323
        FR.json = nics
324
        r = self.client.list_server_nics(vm_id)
325
        self.assertEqual(self.client.http_client.url, self.url)
326
        self.assertEqual(
327
            self.client.http_client.path,
328
            '/servers/%s/ips' % vm_id)
329
        expected = nics['addresses']['values']
330
        for i in range(len(r)):
331
            self.assert_dicts_are_equal(r[i], expected[i])
332

    
333
    @patch('%s.perform_request' % khttp, return_value=FR())
334
    def test_list_networks(self, PR):
335
        FR.json = net_list
336
        r = self.client.list_networks()
337
        self.assertEqual(self.client.http_client.url, self.url)
338
        self.assertEqual(self.client.http_client.path, '/networks')
339
        expected = net_list['networks']['values']
340
        for i in range(len(r)):
341
            self.assert_dicts_are_equal(expected[i], r[i])
342
        self.client.list_networks(detail=True)
343
        self.assertEqual(self.client.http_client.url, self.url)
344
        self.assertEqual(self.client.http_client.path, '/networks/detail')
345

    
346
    @patch('%s.perform_request' % khttp, return_value=FR())
347
    def test_list_network_nics(self, PR):
348
        net_id = net_recv['network']['id']
349
        FR.json = net_recv
350
        r = self.client.list_network_nics(net_id)
351
        self.assertEqual(self.client.http_client.url, self.url)
352
        self.assertEqual(
353
            self.client.http_client.path,
354
            '/networks/%s' % net_id)
355
        expected = net_recv['network']['attachments']['values']
356
        for i in range(len(r)):
357
            self.assert_dicts_are_equal(r[i], expected[i])
358

    
359
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
360
    def test_disconnect_network_nics(self, NP):
361
        net_id = net_recv['network']['id']
362
        nics = ['nic1', 'nic2', 'nic3']
363
        with patch.object(
364
                CycladesClient,
365
                'list_network_nics',
366
                return_value=nics) as lnn:
367
            self.client.disconnect_network_nics(net_id)
368
            lnn.assert_called_once_with(net_id)
369
            for i in range(len(nics)):
370
                expected = call(net_id, 'action', json_data=dict(
371
                    remove=dict(attachment=nics[i])))
372
                self.assertEqual(expected, NP.mock_calls[i])
373

    
374
    @patch('%s.perform_request' % khttp, return_value=FR())
375
    def test_get_network_details(self, PR):
376
        FR.json = net_recv
377
        net_id = net_recv['network']['id']
378
        r = self.client.get_network_details(net_id)
379
        self.assertEqual(self.client.http_client.url, self.url)
380
        self.assertEqual(
381
            self.client.http_client.path,
382
            '/networks/%s' % net_id)
383
        self.assert_dicts_are_equal(r, net_recv['network'])
384

    
385
    @patch('%s.perform_request' % khttp, return_value=FR())
386
    def test_update_network_name(self, PR):
387
        net_id = net_recv['network']['id']
388
        new_name = '%s_new' % net_id
389
        FR.status_code = 204
390
        self.client.update_network_name(net_id, new_name)
391
        self.assertEqual(self.client.http_client.url, self.url)
392
        self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
393
        (method, data, a_headers, a_params) = PR.call_args[0]
394
        self.assert_dicts_are_equal(
395
            dict(network=dict(name=new_name)),
396
            loads(data))
397

    
398
    @patch('%s.perform_request' % khttp, return_value=FR())
399
    def test_delete_image(self, PR):
400
        FR.status_code = 204
401
        self.client.delete_image(img_ref)
402
        self.assertEqual(self.client.http_client.url, self.url)
403
        self.assertEqual(self.client.http_client.path, '/images/%s' % img_ref)
404

    
405
    @patch('%s.perform_request' % khttp, return_value=FR())
406
    def test_delete_network(self, PR):
407
        net_id = net_recv['network']['id']
408
        FR.status_code = 204
409
        self.client.delete_network(net_id)
410
        self.assertEqual(self.client.http_client.url, self.url)
411
        self.assertEqual(self.client.http_client.path, '/networks/%s' % net_id)
412

    
413
    @patch('%s.perform_request' % khttp, return_value=FR())
414
    def test_create_image_metadata(self, PR):
415
        metadata = dict(m1='v1', m2='v2', m3='v3')
416
        FR.json = dict(meta=img_recv['image'])
417
        self.assertRaises(
418
            ClientError,
419
            self.client.create_image_metadata,
420
            img_ref, 'key', 'value')
421
        FR.status_code = 201
422
        for k, v in metadata.items():
423
            r = self.client.create_image_metadata(img_ref, k, v)
424
            self.assertEqual(self.client.http_client.url, self.url)
425
            self.assertEqual(
426
                self.client.http_client.path,
427
                '/images/%s/meta/%s' % (img_ref, k))
428
            (method, data, a_headers, a_params) = PR.call_args[0]
429
            self.assertEqual(dict(meta={k: v}), loads(data))
430
            self.assert_dicts_are_equal(r, img_recv['image'])
431

    
432
    @patch('%s.images_post' % cyclades_pkg, return_value=FR())
433
    def test_update_image_metadata(self, images_post):
434
        metadata = dict(m1='v1', m2='v2', m3='v3')
435
        FR.json = dict(metadata=metadata)
436
        r = self.client.update_image_metadata(img_ref, **metadata)
437
        self.assert_dicts_are_equal(r, metadata)
438
        (called_id, cmd) = images_post.call_args[0]
439
        self.assertEqual(called_id, img_ref)
440
        self.assertEqual(cmd, 'meta')
441
        data = images_post.call_args[1]['json_data']
442
        self.assert_dicts_are_equal(data, dict(metadata=metadata))
443

    
444
    @patch('%s.images_delete' % cyclades_pkg, return_value=FR())
445
    def test_delete_image_metadata(self, images_delete):
446
        key = 'metakey'
447
        self.client.delete_image_metadata(img_ref, key)
448
        self.assertEqual(
449
            (img_ref, '/meta/' + key),
450
            images_delete.call_args[0])
451

    
452
if __name__ == '__main__':
453
    from sys import argv
454
    from kamaki.clients.test import runTestCase
455
    runTestCase(Cyclades, 'Cyclades (multi) Client', argv[1:])