Revision 7a3c66e1

b/docs/commands.rst
8 8
In this context, objects are not services, but virtual objects like a server, a
9 9
file or an image. The action concerns objects of the specified type. Some
10 10
actions (e.g. "delete" or "info") need to operate on an existing object. The
11
identifiers strictly identify this object and they can have the form of an id 
12
(e.g., `server delete <SERVER_ID>`) or a foreign key (e.g., 
13
`port create <NETWORK_ID> <DEVICE_ID>`)
11
identifiers strictly identify this object and they should have the form of an id
12
(e.g., `server delete <SERVER_ID>`).
14 13

  
15 14
The examples bellow showcase some commands. The kamaki-shell (check
16 15
`Usage section <usage.html#interactive-shell>`_ for details) is chosen as the
......
351 350
    * Try network-connect (to get help) *
352 351
    [network]: connect 
353 352
    Syntax error
354
    usage: connect <network id> <device id> [-s] [-h] [-i] [--config CONFIG]
353
    usage: connect <network id> --device-id <DEVICE_ID> [-s] [-h] [-i] [--config CONFIG]
355 354

  
356 355
    Connect a server to a network
357 356

  
......
364 363
      -v,--verbose:  More info at response
365 364

  
366 365
    * Connect VM with id 11687 to network with id 1409
367
    [network]: connect 11687 1409 --wait
366
    [network]: connect 11687 --device-id=1409 --wait
368 367
    Creating port between network 1409 and server 11687
369 368
    New port: 8
370 369

  
b/kamaki/cli/commands/network.py
55 55

  
56 56

  
57 57
about_authentication = '\nUser Authentication:\
58
    \n* to check authentication: /user authenticate\
59
    \n* to set authentication token: /config set cloud.<cloud>.token <token>'
58
    \n  to check authentication: [kamaki] ]user authenticate\
59
    \n  to set authentication token: \
60
    [kamaki] config set cloud.<CLOUD>.token <TOKEN>'
60 61

  
61 62

  
62 63
class _port_wait(_service_wait):
......
292 293

  
293 294
    @value.setter
294 295
    def value(self, new_pools):
296
        if not new_pools:
297
            return
295 298
        new_list = []
296 299
        for pool in new_pools:
297 300
            start, comma, end = pool.partition(',')
......
635 638
        ip_address=ValueArgument(
636 639
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
637 640
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
641
        device_id=RepeatableArgument(
642
            'Connect this device to the network (can be repeated)',
643
            '--device-id')
638 644
    )
645
    required = ('device_id', )
639 646

  
640 647
    @errors.generic.all
641 648
    @errors.cyclades.connection
......
646 653
            network_id, server_id))
647 654
        self.connect(network_id, server_id)
648 655

  
649
    def main(self, network_id, device_id):
656
    def main(self, network_id):
650 657
        super(self.__class__, self)._run()
651
        self._run(network_id=network_id, server_id=device_id)
658
        for sid in self['device_id']:
659
            self._run(network_id=network_id, server_id=sid)
652 660

  
653 661

  
654 662
@command(network_cmds)
......
663 671
        return CycladesClient(URL, self.client.token)
664 672

  
665 673
    arguments = dict(
666
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait'))
674
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
675
        device_id=RepeatableArgument(
676
            'Disconnect device from the network (can be repeated)',
677
            '--device-id')
667 678
    )
679
    required = ('device_id', )
668 680

  
669 681
    @errors.generic.all
670 682
    @errors.cyclades.connection
......
673 685
    def _run(self, network_id, server_id):
674 686
        vm = self._cyclades_client().get_server_details(server_id)
675 687
        ports = [port for port in vm['attachments'] if (
676
            port['network_id'] not in ('network_id', ))]
688
            port['network_id'] in (network_id, ))]
677 689
        if not ports:
678 690
            raiseCLIError('Network %s is not connected to device %s' % (
679 691
                network_id, server_id))
......
692 704
                        raise
693 705
                    self.error('Port %s is deleted' % port['id'])
694 706

  
695
    def main(self, network_id, device_id):
707
    def main(self, network_id):
696 708
        super(self.__class__, self)._run()
697
        self._run(network_id=network_id, server_id=device_id)
709
        for sid in self['device_id']:
710
            self._run(network_id=network_id, server_id=sid)
b/kamaki/cli/commands/pithos.py
300 300
            if_unmodified_since=self['if_unmodified_since'],
301 301
            until=self['until'],
302 302
            meta=self['meta'])
303

  
304
        #  REMOVE THIS if version >> 0.12
305
        if not r.json:
306
            self.error('  NOTE: Since v0.12, use / for containers e.g.,')
307
            self.error('    [kamaki] file list /pithos')
308

  
303 309
        files = self._filter_by_name(r.json)
304 310
        if self['more']:
305 311
            outbu, self._out = self._out, StringIO()
b/kamaki/clients/cyclades/__init__.py
112 112
        r = self.servers_action_post(server_id, json_data=req, success=200)
113 113
        return r.json['console']
114 114

  
115
    def get_firewall_profile(self, server_id):
116
        """
117
        :param server_id: integer (str or int)
118

  
119
        :returns: (str) ENABLED | DISABLED | PROTECTED
120

  
121
        :raises ClientError: 520 No Firewall Profile
122
        """
123
        r = self.get_server_details(server_id)
124
        try:
125
            return r['attachments'][0]['firewallProfile']
126
        except KeyError:
127
            raise ClientError(
128
                'No Firewall Profile',
129
                details='Server %s is missing a firewall profile' % server_id)
130

  
131
    def set_firewall_profile(self, server_id, profile):
132
        """Set the firewall profile for the public interface of a server
133

  
134
        :param server_id: integer (str or int)
135

  
136
        :param profile: (str) ENABLED | DISABLED | PROTECTED
137

  
138
        :returns: (dict) response headers
139
        """
140
        req = {'firewallProfile': {'profile': profile}}
141
        r = self.servers_action_post(server_id, json_data=req, success=202)
142
        return r.headers
143

  
144
    def list_server_nics(self, server_id):
145
        """
146
        :param server_id: integer (str or int)
147

  
148
        :returns: (dict) network interface connections
149
        """
150
        r = self.servers_ips_get(server_id)
151
        return r.json['attachments']
152

  
153 115
    def get_server_stats(self, server_id):
154 116
        """
155 117
        :param server_id: integer (str or int)
......
186 148
        return self._wait(
187 149
            server_id, current_status, get_status, delay, max_wait, wait_cb)
188 150

  
189
    def wait_firewall(
190
            self, server_id,
191
            current_status='DISABLED', delay=1, max_wait=100, wait_cb=None):
192
        """Wait while the public network firewall status is current_status
193

  
194
        :param server_id: integer (str or int)
195

  
196
        :param current_status: (str) DISABLED | ENABLED | PROTECTED
197

  
198
        :param delay: time interval between retries
199

  
200
        :max_wait: (int) timeout in secconds
201

  
202
        :param wait_cb: if set a progressbar is used to show progress
203

  
204
        :returns: (str) the new mode if succesfull, (bool) False if timed out
205
        """
206

  
207
        def get_status(self, server_id):
208
            return self.get_firewall_profile(server_id), None
209

  
210
        return self._wait(
211
            server_id, current_status, get_status, delay, max_wait, wait_cb)
212

  
213 151

  
214 152
class CycladesNetworkClient(NetworkClient):
215 153
    """Cyclades Network API extentions"""
......
252 190
        if fixed_ips:
253 191
            for fixed_ip in fixed_ips or []:
254 192
                if not 'ip_address' in fixed_ip:
255
                    raise ValueError(
256
                        'Invalid format for "fixed_ips"', details=[
257
                        'fixed_ips format: [{"ip_address": IPv4}, ...]'])
193
                    raise ValueError('Invalid fixed_ip [%s]' % fixed_ip)
258 194
            port['fixed_ips'] = fixed_ips
259 195
        r = self.ports_post(json_data=dict(port=port), success=201)
260 196
        return r.json['port']
b/kamaki/clients/cyclades/rest_api.py
33 33

  
34 34
from kamaki.clients.compute import ComputeClient
35 35
from kamaki.clients.utils import path4url
36
import json
37 36

  
38 37

  
39 38
class CycladesRestClient(ComputeClient):
40 39
    """Synnefo Cyclades REST API Client"""
41 40

  
42
    def servers_stats_get(self, server_id, success=200, **kwargs):
41
    def servers_stats_get(self, server_id, **kwargs):
43 42
        """GET base_url/servers/<server_id>/stats"""
44 43
        path = path4url('servers', server_id, 'stats')
45
        return self.get(path, success=success, **kwargs)
46

  
47
    # def networks_get(
48
    #         self,
49
    #         network_id='',
50
    #         command='',
51
    #         success=(200, 203),
52
    #         **kwargs):
53
    #     """GET base_url/networks[/network_id][/command] request
54

  
55
    #     :param network_id: integer (str or int)
56

  
57
    #     :param command: (str) 'detail' or ''
58

  
59
    #     :param success: success code or list or tuple of accepted success
60
    #         codes. if server response code is not in this list, a ClientError
61
    #         raises
62

  
63
    #     :returns: request response
64
    #     """
65
    #     path = path4url('networks', network_id, command)
66
    #     return self.get(path, success=success, **kwargs)
67

  
68
    # def networks_delete(
69
    #         self,
70
    #         network_id='',
71
    #         command='',
72
    #         success=204,
73
    #         **kwargs):
74
    #     """DEL ETE base_url/networks[/network_id][/command] request
75

  
76
    #     :param network_id: integer (str or int)
77

  
78
    #     :param command: (str) 'detail' or ''
79

  
80
    #     :param success: success code or list or tuple of accepted success
81
    #         codes. if server response code is not in this list, a ClientError
82
    #         raises
83

  
84
    #     :returns: request response
85
    #     """
86
    #     path = path4url('networks', network_id, command)
87
    #     return self.delete(path, success=success, **kwargs)
88

  
89
    # def networks_post(
90
    #         self,
91
    #         network_id='',
92
    #         command='',
93
    #         json_data=None,
94
    #         success=202,
95
    #         **kwargs):
96
    #     """POST base_url/servers[/server_id]/[command] request
97

  
98
    #     :param network_id: integer (str or int)
99

  
100
    #     :param command: (str) 'detail' or ''
101

  
102
    #     :param json_data: (dict) will be send as data
103

  
104
    #     :param success: success code or list or tuple of accepted success
105
    #         codes. if server response code is not in this list, a ClientError
106
    #         raises
107

  
108
    #     :returns: request response
109
    #     """
110
    #     data = json_data
111
    #     if json_data is not None:
112
    #         data = json.dumps(json_data)
113
    #         self.set_header('Content-Type', 'application/json')
114
    #         self.set_header('Content-Length', len(data))
115

  
116
    #     path = path4url('networks', network_id, command)
117
    #     return self.post(path, data=data, success=success, **kwargs)
118

  
119
    # def networks_put(
120
    #         self,
121
    #         network_id='',
122
    #         command='',
123
    #         json_data=None,
124
    #         success=204,
125
    #         **kwargs):
126
    #     """PUT base_url/servers[/server_id]/[command] request
127

  
128
    #     :param network_id: integer (str or int)
129

  
130
    #     :param command: (str) 'detail' or ''
131

  
132
    #     :param json_data: (dict) will be send as data
133

  
134
    #     :param success: success code or list or tuple of accepted success
135
    #         codes. if server response code is not in this list, a ClientError
136
    #         raises
137

  
138
    #     :returns: request response
139
    #     """
140
    #     data = json_data
141
    #     if json_data is not None:
142
    #         data = json.dumps(json_data)
143
    #         self.set_header('Content-Type', 'application/json')
144
    #         self.set_header('Content-Length', len(data))
145

  
146
    #     path = path4url('networks', network_id, command)
147
    #     return self.put(path, data=data, success=success, **kwargs)
44
        return self.get(path, success=200, **kwargs)
b/kamaki/clients/cyclades/test.py
94 94

  
95 95
class CycladesRestClient(TestCase):
96 96

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

  
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))
102
    @patch('kamaki.clients.Client.get', return_value='ret')
103
    def test_servers_stats_get(self, get):
104
        server_id = 'server id'
105
        self.assertEqual(self.client.servers_stats_get(server_id), 'ret')
106
        get.assert_called_once_with(
107
            '/servers/%s/stats' % server_id, success=200)
240 108

  
241 109

  
242 110
class CycladesNetworkClient(TestCase):
......
286 154
                ('port name', None),
287 155
                ([1, 2, 3], None),
288 156
                (
289
                    dict(subnet_id='sid', ip_address='ipa'),
290
                    dict(subnet_id='sid'), dict(ip_address='ipa'),
157
                    [dict(subnet_id='sid', ip_address='ipa')],
158
                    [dict(subnet_id='sid')], [dict(ip_address='ipa')],
291 159
                    None)):
292 160

  
293 161
            if fixed_ips:
294
                diff = set(['subnet_id', 'ip_address']).difference(fixed_ips)
162
                diff = set(['ip_address', ]).difference(fixed_ips[0])
295 163
                if diff:
296 164
                    self.assertRaises(
297 165
                        ValueError, self.client.create_port,
......
360 228
            vm_id, json_data=dict(console=dict(type='vnc')), success=200)
361 229
        self.assert_dicts_are_equal(r, cnsl['console'])
362 230

  
363
    def test_get_firewall_profile(self):
364
        vm_id = vm_recv['server']['id']
365
        v = firewalls['attachments'][0]['firewallProfile']
366
        with patch.object(
367
                cyclades.CycladesClient, 'get_server_details',
368
                return_value=firewalls) as GSD:
369
            r = self.client.get_firewall_profile(vm_id)
370
            GSD.assert_called_once_with(vm_id)
371
            self.assertEqual(r, v)
372
        with patch.object(
373
                cyclades.CycladesClient, 'get_server_details',
374
                return_value=dict()):
375
            self.assertRaises(
376
                ClientError,
377
                self.client.get_firewall_profile,
378
                vm_id)
379

  
380
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
381
    def test_set_firewall_profile(self, SP):
382
        vm_id = vm_recv['server']['id']
383
        v = firewalls['attachments'][0]['firewallProfile']
384
        self.client.set_firewall_profile(vm_id, v)
385
        SP.assert_called_once_with(vm_id, json_data=dict(
386
            firewallProfile=dict(profile=v)), success=202)
387

  
388
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
389
    def test_create_network(self, NP):
390
        net_name = net_send['network']['name']
391
        FR.json = net_recv
392
        full_args = dict(
393
                cidr='192.168.0.0/24',
394
                gateway='192.168.0.1',
395
                type='MAC_FILTERED',
396
                dhcp=True)
397
        test_args = dict(full_args)
398
        test_args.update(dict(empty=None, full=None))
399
        net_exp = dict(dhcp=False, name=net_name, type='MAC_FILTERED')
400
        for arg, val in test_args.items():
401
            kwargs = {} if arg == 'empty' else full_args if (
402
                arg == 'full') else {arg: val}
403
            expected = dict(network=dict(net_exp))
404
            expected['network'].update(kwargs)
405
            r = self.client.create_network(net_name, **kwargs)
406
            self.assertEqual(
407
                NP.mock_calls[-1],
408
                call(json_data=expected, success=202))
409
            self.assert_dicts_are_equal(r, net_recv['network'])
410

  
411
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
412
    def test_connect_server(self, NP):
413
        vm_id = vm_recv['server']['id']
414
        net_id = net_recv['network']['id']
415
        self.client.connect_server(vm_id, net_id)
416
        NP.assert_called_once_with(
417
            net_id, 'action',
418
            json_data=dict(add=dict(serverRef=vm_id)))
419

  
420
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
421
    def test_disconnect_server(self, NP):
422
        net_id, vm_id = net_recv['network']['id'], vm_recv['server']['id']
423
        nic_id = 'nic-%s-%s' % (net_id, vm_id)
424
        vm_nics = [
425
            dict(id=nic_id, network_id=net_id),
426
            dict(id='another-nic-id', network_id='another-net-id'),
427
            dict(id=nic_id * 2, network_id=net_id * 2)]
428
        with patch.object(
429
                cyclades.CycladesClient,
430
                'list_server_nics',
431
                return_value=vm_nics) as LSN:
432
            r = self.client.disconnect_server(vm_id, nic_id)
433
            LSN.assert_called_once_with(vm_id)
434
            NP.assert_called_once_with(
435
                net_id, 'action',
436
                json_data=dict(remove=dict(attachment=nic_id)))
437
            self.assertEqual(r, 1)
438

  
439
    @patch('%s.servers_ips_get' % cyclades_pkg, return_value=FR())
440
    def test_list_server_nics(self, SG):
441
        vm_id = vm_recv['server']['id']
442
        nics = dict(attachments=[dict(id='nic1'), dict(id='nic2')])
443
        FR.json = nics
444
        r = self.client.list_server_nics(vm_id)
445
        SG.assert_called_once_with(vm_id)
446
        expected = nics['attachments']
447
        for i in range(len(r)):
448
            self.assert_dicts_are_equal(r[i], expected[i])
449
        self.assertEqual(i + 1, len(r))
450

  
451
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
452
    def test_list_networks(self, NG):
453
        FR.json = net_list
454
        expected = net_list['networks']
455
        for detail in ('', 'detail'):
456
            r = self.client.list_networks(detail=True if detail else False)
457
            self.assertEqual(NG.mock_calls[-1], call(command=detail))
458
            for i, net in enumerate(expected):
459
                self.assert_dicts_are_equal(r[i], net)
460
            self.assertEqual(i + 1, len(r))
461

  
462
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
463
    def test_list_network_nics(self, NG):
464
        net_id = net_recv['network']['id']
465
        FR.json = net_recv
466
        r = self.client.list_network_nics(net_id)
467
        NG.assert_called_once_with(network_id=net_id)
468
        expected = net_recv['network']['attachments']
469
        for i in range(len(r)):
470
            self.assert_dicts_are_equal(r[i], expected[i])
471

  
472
    @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
473
    def test_disconnect_network_nics(self, NP):
474
        net_id = net_recv['network']['id']
475
        nics = ['nic1', 'nic2', 'nic3']
476
        with patch.object(
477
                cyclades.CycladesClient,
478
                'list_network_nics',
479
                return_value=nics) as LNN:
480
            self.client.disconnect_network_nics(net_id)
481
            LNN.assert_called_once_with(net_id)
482
            for i in range(len(nics)):
483
                expected = call(net_id, 'action', json_data=dict(
484
                    remove=dict(attachment=nics[i])))
485
                self.assertEqual(expected, NP.mock_calls[i])
486

  
487
    @patch('%s.networks_get' % cyclades_pkg, return_value=FR())
488
    def test_get_network_details(self, NG):
489
        FR.json = net_recv
490
        net_id = net_recv['network']['id']
491
        r = self.client.get_network_details(net_id)
492
        NG.assert_called_once_with(network_id=net_id)
493
        self.assert_dicts_are_equal(r, net_recv['network'])
494

  
495
    @patch('%s.networks_put' % cyclades_pkg, return_value=FR())
496
    def test_update_network_name(self, NP):
497
        net_id = net_recv['network']['id']
498
        new_name = '%s_new' % net_id
499
        self.client.update_network_name(net_id, new_name)
500
        NP.assert_called_once_with(
501
            network_id=net_id,
502
            json_data=dict(network=dict(name=new_name)))
503

  
504
    def test_delete_network(self):
505
        net_id = net_recv['network']['id']
506
        with patch.object(
507
                cyclades.CycladesClient, 'networks_delete',
508
                return_value=FR()) as ND:
509
            self.client.delete_network(net_id)
510
            ND.assert_called_once_with(net_id)
511
        with patch.object(
512
                cyclades.CycladesClient, 'networks_delete',
513
                side_effect=ClientError('A 421 Error', 421)):
514
            try:
515
                self.client.delete_network(421)
516
            except ClientError as err:
517
                self.assertEqual(err.status, 421)
518
                self.assertEqual(err.details, [
519
                    'Network may be still connected to at least one server'])
520

  
521
    @patch('%s.floating_ip_pools_get' % cyclades_pkg, return_value=FR())
522
    def test_get_floating_ip_pools(self, get):
523
        r = self.client.get_floating_ip_pools()
524
        self.assert_dicts_are_equal(r, FR.json)
525
        self.assertEqual(get.mock_calls[-1], call())
526

  
527
    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
528
    def test_get_floating_ips(self, get):
529
        r = self.client.get_floating_ips()
530
        self.assert_dicts_are_equal(r, FR.json)
531
        self.assertEqual(get.mock_calls[-1], call())
532

  
533
    @patch('%s.floating_ips_post' % cyclades_pkg, return_value=FR())
534
    def test_alloc_floating_ip(self, post):
535
        FR.json = dict(floating_ip=dict(
536
            fixed_ip='fip',
537
            id=1,
538
            instance_id='lala',
539
            ip='102.0.0.1',
540
            pool='pisine'))
541
        for args in product(
542
                (None, 'pisine'),
543
                (None, 'Iwannanip')):
544
            r = self.client.alloc_floating_ip(*args)
545
            pool, address = args
546
            self.assert_dicts_are_equal(r, FR.json['floating_ip'])
547
            json_data = dict()
548
            if pool:
549
                json_data['pool'] = pool
550
            if address:
551
                json_data['address'] = address
552
            self.assertEqual(post.mock_calls[-1], call(json_data))
553

  
554
    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
555
    def test_get_floating_ip(self, get):
556
        FR.json = dict(floating_ip=dict(
557
            fixed_ip='fip',
558
            id=1,
559
            instance_id='lala',
560
            ip='102.0.0.1',
561
            pool='pisine'))
562
        self.assertRaises(AssertionError, self.client.get_floating_ip, None)
563
        fip = 'fip'
564
        r = self.client.get_floating_ip(fip)
565
        self.assert_dicts_are_equal(r, FR.json['floating_ip'])
566
        self.assertEqual(get.mock_calls[-1], call(fip))
567

  
568
    @patch('%s.floating_ips_delete' % cyclades_pkg, return_value=FR())
569
    def test_delete_floating_ip(self, delete):
570
        self.assertRaises(AssertionError, self.client.delete_floating_ip, None)
571
        fip = 'fip'
572
        r = self.client.delete_floating_ip(fip)
573
        self.assert_dicts_are_equal(r, FR.headers)
574
        self.assertEqual(delete.mock_calls[-1], call(fip))
575

  
576
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
577
    def test_attach_floating_ip(self, spost):
578
        vmid, addr = 42, 'anIpAddress'
579
        for err, args in {
580
                ValueError: ['not a server id', addr],
581
                TypeError: [None, addr],
582
                AssertionError: [vmid, None],
583
                AssertionError: [vmid, '']}.items():
584
            self.assertRaises(
585
                err, self.client.attach_floating_ip, *args)
586
        r = self.client.attach_floating_ip(vmid, addr)
587
        self.assert_dicts_are_equal(r, FR.headers)
588
        expected = dict(addFloatingIp=dict(address=addr))
589
        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
590

  
591
    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
592
    def test_detach_floating_ip(self, spost):
593
        vmid, addr = 42, 'anIpAddress'
594
        for err, args in {
595
                ValueError: ['not a server id', addr],
596
                TypeError: [None, addr],
597
                AssertionError: [vmid, None],
598
                AssertionError: [vmid, '']}.items():
599
            self.assertRaises(
600
                err, self.client.detach_floating_ip, *args)
601
        r = self.client.detach_floating_ip(vmid, addr)
602
        self.assert_dicts_are_equal(r, FR.headers)
603
        expected = dict(removeFloatingIp=dict(address=addr))
604
        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
605

  
606 231

  
607 232
if __name__ == '__main__':
608 233
    from sys import argv
b/kamaki/clients/network/test.py
490 490
        'kamaki.clients.network.NetworkClient.ports_get',
491 491
        return_value=FakeObject())
492 492
    def test_get_port_details(self, ports_get):
493
        portid, FakeObject.json = 'portid', dict(ports='ret val')
493
        portid, FakeObject.json = 'portid', dict(port='ret val')
494 494
        self.assertEqual(self.client.get_port_details(portid), 'ret val')
495
        ports_get.assert_called_once_with(portid, success=201)
495
        ports_get.assert_called_once_with(portid, success=200)
496 496

  
497 497
    @patch(
498 498
        'kamaki.clients.network.NetworkClient.ports_delete',
......
523 523
            for k, v in kwargs.items():
524 524
                if v:
525 525
                    req[k] = v
526
            expargs = dict(json_data=dict(port=req), success=201)
526
            expargs = dict(json_data=dict(port=req), success=200)
527 527
            self.assertEqual(
528 528
                ports_put.mock_calls[-1], call(port_id, **expargs))
529 529

  
b/kamaki/clients/pithos/test.py
197 197
    @patch('%s.set_header' % rest_pkg)
198 198
    @patch('%s.get' % rest_pkg, return_value=FR())
199 199
    def test_account_get(self, get, SH, SP):
200
        keys = ('limit', 'marker', 'format', 'shared', 'until')
200
        keys = ('limit', 'marker', 'format', 'shared', 'public', 'until')
201 201
        for params in product(
202 202
                (None, 42),
203 203
                (None, 'X'),
204 204
                ('json', 'xml'),
205 205
                (False, True),
206
                (False, True),
206 207
                (None, '50m3-d473'),
207 208
                (None, '50m3-07h3r-d473'),
208 209
                (None, 'y37-4n7h3r-d473'),
......
211 212
            args, kwargs = params[-2], params[-1]
212 213
            params = params[:-2]
213 214
            self.client.account_get(*(params + args), **kwargs)
214
            self.assertEqual(SP.mock_calls[-5:],
215
                [call(keys[i], iff=X) if (
216
                    i == 3) else call(
217
                        keys[i], X, iff=X) for i, X in enumerate(params[:5])])
218
            IMS, IUS = params[5], params[6]
215
            self.assertEqual(SP.mock_calls[-6:],
216
                [call(keys[i], iff=X) if (i in (3, 4)) else call(
217
                    keys[i], X, iff=X) for i, X in enumerate(params[:6])])
218
            IMS, IUS = params[6], params[7]
219 219
            self.assertEqual(SH.mock_calls[-2:], [
220 220
                call('If-Modified-Since', IMS),
221 221
                call('If-Unmodified-Since', IUS)])
......
297 297
                ('json', 'some-format'),
298 298
                ([], ['k1', 'k2', 'k3']),
299 299
                (False, True),
300
                (False, True),
300 301
                (None, 'unt1l-d473'),
301 302
                (None, 'y37-4n47h3r'),
302 303
                (None, '4n47h3r-d473'),
......
305 306
            args, kwargs = pm[-2:]
306 307
            pm = pm[:-2]
307 308
            self.client.container_get(*(pm + args), **kwargs)
308
            lmt, mrk, prfx, dlm, path, frmt, meta, shr, unt = pm[:-2]
309
            lmt, mrk, prfx, dlm, path, frmt, meta, shr, pbl, unt = pm[:-2]
309 310
            exp = [call('limit', lmt, iff=lmt), call('marker', mrk, iff=mrk)]
310 311
            exp += [call('path', path)] if path else [
311 312
                call('prefix', prfx, iff=prfx),
312 313
                call('delimiter', dlm, iff=dlm)]
313
            exp += [call('format', frmt, iff=frmt), call('shared', iff=shr)]
314
            exp += [
315
                call('format', frmt, iff=frmt),
316
                call('shared', iff=shr),
317
                call('public', iff=pbl)]
314 318
            if meta:
315 319
                exp += [call('meta', ','.join(meta))]
316 320
            exp += [call('until', unt, iff=unt)]
b/kamaki/clients/utils/test.py
34 34
#from mock import patch, call
35 35

  
36 36
from unittest import TestCase
37
from tempfile import TemporaryFile
38

  
37 39
from kamaki.clients import utils
38 40

  
39 41

  
......
128 130
            self.assertEqual(utils.path4url(*args), expected)
129 131

  
130 132
    def test_readall(self):
131

  
132
        class fakefile(object):
133

  
134
            responses = ['1', '2', '3', '4', '5', '6', '7']
135
            failures = [False, ] * 7
136

  
137
            def __init__(self):
138
                def _read_gen(self):
139
                    for i, r in enumerate(self.responses):
140
                        if self.failures[i]:
141
                            yield ''
142
                        yield r
143
                self._reader = _read_gen(self)
144

  
145
            def read(self, size=None):
146
                return self._reader.next()
147

  
148
        fileobj = fakefile()
149
        self.assertEqual(
150
            ''.join(fakefile.responses), utils.readall(fileobj, 7))
151
        fileobj = fakefile()
152
        self.assertEqual(
153
            ''.join(fakefile.responses[:4]), utils.readall(fileobj, 4))
154
        fileobj = fakefile()
155
        self.assertRaises(IOError, utils.readall, fileobj, 10)
156
        fileobj = fakefile()
157
        fileobj.failures[1] = True
158
        self.assertRaises(IOError, utils.readall, fileobj, 7)
159
        fileobj = fakefile()
160
        fileobj.failures[1] = True
161
        self.assertEqual(
162
            ''.join(fakefile.responses), utils.readall(fileobj, 7, 8))
133
        tstr = '1234567890'
134
        with TemporaryFile() as f:
135
            f.write(tstr)
136
            f.flush()
137
            f.seek(0)
138
            self.assertEqual(utils.readall(f, 5), tstr[:5])
139
            self.assertEqual(utils.readall(f, 10), tstr[5:])
140
            self.assertEqual(utils.readall(f, 1), '')
141
            self.assertRaises(IOError, utils.readall, f, 1, 0)
163 142

  
164 143
if __name__ == '__main__':
165 144
    from sys import argv

Also available in: Unified diff