Revision 5c433331

b/kamaki/cli/commands/network.py
1
# Copyright 2011-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 base64 import b64encode
35
from os.path import exists, expanduser
36
from io import StringIO
37
from pydoc import pager
38

  
39
from kamaki.cli import command
40
from kamaki.cli.command_tree import CommandTree
41
from kamaki.cli.utils import remove_from_items, filter_dicts_by_dict
42
from kamaki.cli.errors import (
43
    raiseCLIError, CLISyntaxError, CLIBaseUrlError, CLIInvalidArgument)
44
from kamaki.clients.network import NetworkClient, ClientError
45
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
46
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
47
from kamaki.cli.commands import _command_init, errors, addLogSettings
48
from kamaki.cli.commands import (
49
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
50

  
51

  
52
network_cmds = CommandTree('network', 'Networking API network commands')
53
port_cmds = CommandTree('port', 'Networking API network commands')
54
subnet_cmds = CommandTree('subnet', 'Networking API network commands')
55
_commands = [network_cmds, port_cmds, subnet_cmds]
56

  
57

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

  
62

  
63
class _init_network(_command_init):
64
    @errors.generic.all
65
    @addLogSettings
66
    def _run(self, service='network'):
67
        if getattr(self, 'cloud', None):
68
            base_url = self._custom_url(service) or self._custom_url(
69
                'compute')
70
            if base_url:
71
                token = self._custom_token(service) or self._custom_token(
72
                    'compute') or self.config.get_cloud('token')
73
                self.client = NetworkClient(
74
                  base_url=base_url, token=token)
75
                return
76
        else:
77
            self.cloud = 'default'
78
        if getattr(self, 'auth_base', False):
79
            cyclades_endpoints = self.auth_base.get_service_endpoints(
80
                self._custom_type('compute') or 'compute',
81
                self._custom_version('compute') or '')
82
            base_url = cyclades_endpoints['publicURL']
83
            token = self.auth_base.token
84
            self.client = NetworkClient(base_url=base_url, token=token)
85
        else:
86
            raise CLIBaseUrlError(service='network')
87

  
88
    def main(self):
89
        self._run()
90

  
91

  
92
@command(network_cmds)
93
class network_list(_init_network, _optional_json, _name_filter, _id_filter):
94
    """List networks
95
    Use filtering arguments (e.g., --name-like) to manage long server lists
96
    """
97

  
98
    arguments = dict(
99
        detail=FlagArgument('show detailed output', ('-l', '--details')),
100
        more=FlagArgument(
101
            'output results in pages (-n to set items per page, default 10)',
102
            '--more'),
103
    )
104

  
105
    @errors.generic.all
106
    @errors.cyclades.connection
107
    def _run(self):
108
        nets = self.client.list_networks()
109
        nets = self._filter_by_name(nets)
110
        nets = self._filter_by_id(nets)
111
        if not self['detail']:
112
            nets = [dict(id=net['id'], name=net['name']) for net in nets]
113
        kwargs = dict()
114
        if self['more']:
115
            kwargs['out'] = StringIO()
116
            kwargs['title'] = ()
117
        self._print(nets, **kwargs)
118
        if self['more']:
119
            pager(kwargs['out'].getvalue())
120

  
121
    def main(self):
122
        super(self.__class__, self)._run()
123
        self._run()
124

  
125

  
126
@command(network_cmds)
127
class network_info(_init_network, _optional_json):
128
    """Get details about a network"""
129

  
130
    @errors.generic.all
131
    @errors.cyclades.connection
132
    @errors.cyclades.network_id
133
    def _run(self, network_id):
134
        net = self.client.get_network_details(network_id)
135
        self._print(net, self.print_dict)
136

  
137
    def main(self, network_id):
138
        super(self.__class__, self)._run()
139
        self._run(network_id=network_id)
140

  
141

  
142
@command(network_cmds)
143
class network_create(_init_network, _optional_json):
144
    """Create a new network"""
145

  
146
    arguments = dict(shared=FlagArgument(
147
        'Network will be shared (special privileges required)', '--shared')
148
    )
149

  
150
    @errors.generic.all
151
    @errors.cyclades.connection
152
    def _run(self, name):
153
        #  admin_state_up is not used in Cyclades
154
        net = self.client.create_network(
155
            name, shared=self['shared'])
156
        self._print(net, self.print_dict)
157

  
158
    def main(self, name):
159
        super(self.__class__, self)._run()
160
        self._run(name=name)
161

  
162

  
163
@command(network_cmds)
164
class network_delete(_init_network, _optional_output_cmd):
165
    """Delete a network"""
166

  
167
    @errors.generic.all
168
    @errors.cyclades.connection
169
    @errors.cyclades.network_id
170
    def _run(self, network_id):
171
        r = self.client.delete_network(network_id)
172
        self._optional_output(r)
173

  
174
    def main(self, network_id):
175
        super(self.__class__, self)._run()
176
        self._run(network_id=network_id)
177

  
178

  
179
@command(network_cmds)
180
class network_set(_init_network, _optional_json):
181
    """Set an attribute of a network, leave the rest untouched (update)
182
    Only "--name" is supported for now
183
    """
184

  
185
    arguments = dict(name=ValueArgument('New name of the network', '--name'))
186

  
187
    @errors.generic.all
188
    @errors.cyclades.connection
189
    @errors.cyclades.network_id
190
    def _run(self, network_id):
191
        if self['name'] in (None, ):
192
            raise CLISyntaxError(
193
                'Missing network atrributes to update',
194
                details=[
195
                    'At least one if the following is expected:',
196
                    '  --name=<new name>'])
197
        r = self.client.update_network(network_id, name=self['name'])
198
        self._print(r, self.print_dict)
199

  
200
    def main(self, network_id):
201
        super(self.__class__, self)._run()
202
        self._run(network_id=network_id)
/dev/null
1
# Copyright 2011-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 base64 import b64encode
35
from os.path import exists, expanduser
36
from io import StringIO
37
from pydoc import pager
38

  
39
from kamaki.cli import command
40
from kamaki.cli.command_tree import CommandTree
41
from kamaki.cli.utils import remove_from_items, filter_dicts_by_dict
42
from kamaki.cli.errors import (
43
    raiseCLIError, CLISyntaxError, CLIBaseUrlError, CLIInvalidArgument)
44
from kamaki.clients.networking import NetworkingClient, ClientError
45
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
46
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
47
from kamaki.cli.commands import _command_init, errors, addLogSettings
48
from kamaki.cli.commands import (
49
    _optional_output_cmd, _optional_json, _name_filter, _id_filter)
50

  
51

  
52
network_cmds = CommandTree('network', 'Networking API network commands')
53
port_cmds = CommandTree('port', 'Networking API network commands')
54
subnet_cmds = CommandTree('subnet', 'Networking API network commands')
55
_commands = [network_cmds, port_cmds, subnet_cmds]
56

  
57

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

  
62

  
63
class _init_networking(_command_init):
64
    @errors.generic.all
65
    @addLogSettings
66
    def _run(self, service='network'):
67
        if getattr(self, 'cloud', None):
68
            base_url = self._custom_url(service) or self._custom_url(
69
                'compute')
70
            if base_url:
71
                token = self._custom_token(service) or self._custom_token(
72
                    'compute') or self.config.get_cloud('token')
73
                self.client = NetworkingClient(
74
                  base_url=base_url, token=token)
75
                return
76
        else:
77
            self.cloud = 'default'
78
        if getattr(self, 'auth_base', False):
79
            cyclades_endpoints = self.auth_base.get_service_endpoints(
80
                self._custom_type('compute') or 'compute',
81
                self._custom_version('compute') or '')
82
            base_url = cyclades_endpoints['publicURL']
83
            token = self.auth_base.token
84
            self.client = NetworkingClient(base_url=base_url, token=token)
85
        else:
86
            raise CLIBaseUrlError(service='network')
87

  
88
    def main(self):
89
        self._run()
90

  
91

  
92
@command(network_cmds)
93
class network_list(_init_networking, _optional_json, _name_filter, _id_filter):
94
    """List networks
95
    Use filtering arguments (e.g., --name-like) to manage long server lists
96
    """
97

  
98
    arguments = dict(
99
        detail=FlagArgument('show detailed output', ('-l', '--details')),
100
        more=FlagArgument(
101
            'output results in pages (-n to set items per page, default 10)',
102
            '--more'),
103
    )
104

  
105
    @errors.generic.all
106
    @errors.cyclades.connection
107
    def _run(self):
108
        nets = self.client.list_networks()
109
        nets = self._filter_by_name(nets)
110
        nets = self._filter_by_id(nets)
111
        if not self['detail']:
112
            nets = [dict(id=net['id'], name=net['name']) for net in nets]
113
        kwargs = dict()
114
        if self['more']:
115
            kwargs['out'] = StringIO()
116
            kwargs['title'] = ()
117
        self._print(nets, **kwargs)
118
        if self['more']:
119
            pager(kwargs['out'].getvalue())
120

  
121
    def main(self):
122
        super(self.__class__, self)._run()
123
        self._run()
124

  
125

  
126
@command(network_cmds)
127
class network_info(_init_networking, _optional_json):
128
    """Get details about a network"""
129

  
130
    @errors.generic.all
131
    @errors.cyclades.connection
132
    @errors.cyclades.network_id
133
    def _run(self, network_id):
134
        net = self.client.get_network_details(network_id)
135
        self._print(net, self.print_dict)
136

  
137
    def main(self, network_id):
138
        super(self.__class__, self)._run()
139
        self._run(network_id=network_id)
140

  
141

  
142
@command(network_cmds)
143
class network_create(_init_networking, _optional_json):
144
    """Create a new network"""
145

  
146
    arguments = dict(shared=FlagArgument(
147
        'Network will be shared (special privileges required)', '--shared')
148
    )
149

  
150
    @errors.generic.all
151
    @errors.cyclades.connection
152
    def _run(self, name):
153
        #  admin_state_up is not used in Cyclades
154
        net = self.client.create_network(
155
            name, shared=self['shared'])
156
        self._print(net, self.print_dict)
157

  
158
    def main(self, name):
159
        super(self.__class__, self)._run()
160
        self._run(name=name)
161

  
162

  
163
@command(network_cmds)
164
class network_delete(_init_networking, _optional_output_cmd):
165
    """Delete a network"""
166

  
167
    @errors.generic.all
168
    @errors.cyclades.connection
169
    @errors.cyclades.network_id
170
    def _run(self, network_id):
171
        r = self.client.delete_network(network_id)
172
        self._optional_output(r)
173

  
174
    def main(self, network_id):
175
        super(self.__class__, self)._run()
176
        self._run(network_id=network_id)
177

  
178

  
179
@command(network_cmds)
180
class network_set(_init_networking, _optional_json):
181
    """Set an attribute of a network, leave the rest untouched (update)
182
    Only "--name" is supported for now
183
    """
184

  
185
    arguments = dict(name=ValueArgument('New name of the network', '--name'))
186

  
187
    @errors.generic.all
188
    @errors.cyclades.connection
189
    @errors.cyclades.network_id
190
    def _run(self, network_id):
191
        if self['name'] in (None, ):
192
            raise CLISyntaxError(
193
                'Missing network atrributes to update',
194
                details=[
195
                    'At least one if the following is expected:',
196
                    '  --name=<new name>'])
197
        r = self.client.update_network(network_id, name=self['name'])
198
        self._print(r, self.print_dict)
199

  
200
    def main(self, network_id):
201
        super(self.__class__, self)._run()
202
        self._run(network_id=network_id)
b/kamaki/clients/network/__init__.py
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 kamaki.clients import ClientError
35
from kamaki.clients.network.rest_api import NetworkRestClient
36

  
37

  
38
class NetworkClient(NetworkRestClient):
39
    """OpenStack Network API 2.0 client"""
40

  
41
    def list_networks(self):
42
        r = self.networks_get(success=200)
43
        return r.json['networks']
44

  
45
    def create_network(self, name, admin_state_up=None, shared=None):
46
        req = dict(network=dict(
47
            name=name, admin_state_up=bool(admin_state_up)))
48
        if shared not in (None, ):
49
            req['network']['shared'] = bool(shared)
50
        r = self.networks_post(json_data=req, success=201)
51
        return r.json['network']
52

  
53
    def create_networks(self, networks):
54
        """Atomic operation for batch network creation (all or nothing)
55
        :param networks: (list) [
56
            {name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..},
57
            {name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..}]
58
            name is mandatory, the rest is optional
59
            e.g., create_networks([
60
                {name: 'net1', admin_state_up: True},
61
                {name: 'net2'}])
62
        :returns: (list of dicts) created networks details
63
        :raises ValueError: if networks is misformated
64
        :raises ClientError: if the request failed or didn't return 201
65
        """
66
        try:
67
            msg = 'The networks parameter must be list or tuple'
68
            assert (
69
                isinstance(networks, list) or isinstance(networks, tuple)), msg
70
            for network in networks:
71
                msg = 'Network specification %s is not a dict' % network
72
                assert isinstance(network, dict), msg
73
                err = set(network).difference(
74
                    ('name', 'admin_state_up', 'shared'))
75
                if err:
76
                    raise ValueError(
77
                        'Invalid key(s): %s in network specification %s' % (
78
                            err, network))
79
                msg = 'Name is missing in network specification: %s' % network
80
                assert network.get('name', None), msg
81
                network.setdefault('admin_state_up', False)
82
        except AssertionError as ae:
83
            raise ValueError('%s' % ae)
84

  
85
        req = dict(networks=list(networks))
86
        r = self.networks_post(json_data=req, success=201)
87
        return r.json['networks']
88

  
89
    def get_network_details(self, network_id):
90
        r = self.networks_get(network_id, success=200)
91
        return r.json['network']
92

  
93
    def update_network(
94
            self, network_id, name=None, admin_state_up=None, shared=None):
95
        network = dict()
96
        if name:
97
            network['name'] = name
98
        if admin_state_up not in (None, ):
99
            network['admin_state_up'] = admin_state_up
100
        if shared not in (None, ):
101
            network['shared'] = shared
102
        network = dict(network=network)
103
        r = self.networks_put(network_id, json_data=network, success=200)
104
        return r.json['network']
105

  
106
    def delete_network(self, network_id):
107
        r = self.networks_delete(network_id, success=204)
108
        return r.headers
109

  
110
    def list_subnets(self):
111
        r = self.subnets_get(success=200)
112
        return r.json['subnets']
113

  
114
    def create_subnet(
115
            self, network_id, cidr,
116
            name=None, allocation_pools=None, gateway_ip=None, subnet_id=None,
117
            ipv6=None, enable_dhcp=None):
118
        """
119
        :param network_id: (str)
120
        :param cidr: (str)
121

  
122
        :param name: (str) The subnet name
123
        :param allocation_pools: (list of dicts) start/end addresses of
124
            allocation pools: [{'start': ..., 'end': ...}, ...]
125
        :param gateway_ip: (str)
126
        :param subnet_id: (str)
127
        :param ipv6: (bool) ip_version == 6 if true else 4 (default)
128
        :param enable_dhcp: (bool)
129
        """
130
        subnet = dict(
131
            network_id=network_id, cidr=cidr, ip_version=6 if ipv6 else 4)
132
        if name:
133
            subnet['name'] = name
134
        if allocation_pools:
135
            subnet['allocation_pools'] = allocation_pools
136
        if gateway_ip:
137
            subnet['gateway_ip'] = gateway_ip
138
        if subnet_id:
139
            subnet['id'] = subnet_id
140
        if enable_dhcp not in (None, ):
141
            subnet['enable_dhcp'] = bool(enable_dhcp)
142
        r = self.subnets_post(json_data=dict(subnet=subnet), success=201)
143
        return r.json['subnet']
144

  
145
    def create_subnets(self, subnets):
146
        """Atomic operation for batch subnet creation (all or nothing)
147
        :param subnets: (list of dicts) {key: ...} with all parameters in the
148
            method create_subnet, where method mandatory / optional paramteres
149
            respond to mandatory / optional paramters in subnets items
150
        :returns: (list of dicts) created subnetss details
151
        :raises ValueError: if subnets parameter is incorrectly formated
152
        :raises ClientError: if the request failed or didn't return 201
153
        """
154
        try:
155
            msg = 'The subnets parameter must be list or tuple'
156
            assert (
157
                isinstance(subnets, list) or isinstance(subnets, tuple)), msg
158
            for subnet in subnets:
159
                msg = 'Subnet specification %s is not a dict' % subnet
160
                assert isinstance(subnet, dict), msg
161
                err = set(subnet).difference((
162
                    'network_id', 'cidr', 'name', 'allocation_pools',
163
                    'gateway_ip', 'subnet_id', 'ipv6', 'enable_dhcp'))
164
                if err:
165
                    raise ValueError(
166
                        'Invalid key(s): %s in subnet specification %s' % (
167
                            err, subnet))
168
                msg = 'network_id is missing in subnet spec: %s' % subnet
169
                assert subnet.get('network_id', None), msg
170
                msg = 'cidr is missing in subnet spec: %s' % subnet
171
                assert subnet.get('cidr', None), msg
172
                subnet['ip_version'] = 6 if subnet.pop('ipv6', None) else 4
173
                if 'subnet_id' in subnet:
174
                    subnet['id'] = subnet.pop('subnet_id')
175
        except AssertionError as ae:
176
            raise ValueError('%s' % ae)
177

  
178
        r = self.subnets_post(
179
            json_data=dict(subnets=list(subnets)), success=201)
180
        return r.json['subnets']
181

  
182
    def get_subnet_details(self, subnet_id):
183
        r = self.subnets_get(subnet_id, success=201)
184
        return r.json
185

  
186
    def update_subnet(
187
            self, network_id, cidr,
188
            name=None, allocation_pools=None, gateway_ip=None, subnet_id=None,
189
            ipv6=None, enable_dhcp=None):
190
        """
191
        :param network_id: (str) used as filter
192
        :param cidr: (str) used as filter
193

  
194
        :param name: (str) The subnet name
195
        :param allocation_pools: (list of dicts) start/end addresses of
196
            allocation pools: [{'start': ..., 'end': ...}, ...]
197
        :param gateway_ip: (str)
198
        :param subnet_id: (str)
199
        :param ipv6: (bool) ip_version == 6 if true, 4 if false, used as filter
200
        :param enable_dhcp: (bool)
201
        """
202
        subnet = dict(network_id=network_id, cidr=cidr)
203
        if name not in (None, ):
204
            subnet['name'] = name
205
        if allocation_pools not in (None, ):
206
            subnet['allocation_pools'] = allocation_pools
207
        if gateway_ip not in (None, ):
208
            subnet['gateway_ip'] = gateway_ip
209
        if subnet_id not in (None, ):
210
            subnet['id'] = subnet_id
211
        if ipv6 not in (None, ):
212
            subnet['ip_version'] = 6 if ipv6 else 4
213
        if enable_dhcp not in (None, ):
214
            subnet['enable_dhcp'] = enable_dhcp
215
        r = self.subnets_put(json_data=dict(subnet=subnet), success=201)
216
        return r.json['subnet']
217

  
218
    def delete_subnet(self, subnet_id):
219
        r = self.subnets_delete(subnet_id, success=204)
220
        return r.headers
221

  
222
    def list_ports(self):
223
        r = self.ports_get(success=200)
224
        return r.json['ports']
225

  
226
    def create_port(
227
            self, network_id,
228
            name=None, status=None, admin_state_up=None, mac_address=None,
229
            fixed_ips=None, security_groups=None):
230
        """
231
        :param network_id: (str)
232

  
233
        :param name: (str)
234
        :param status: (str)
235
        :param admin_state_up: (bool) Router administrative status (UP / DOWN)
236
        :param mac_address: (str)
237
        :param fixed_ips: (str)
238
        :param security_groups: (list)
239
        """
240
        port = dict(network_id=network_id)
241
        if name:
242
            port['name'] = name
243
        if status:
244
            port['status'] = status
245
        if admin_state_up not in (None, ):
246
            port['admin_state_up'] = bool(admin_state_up)
247
        if mac_address:
248
            port['mac_address'] = mac_address
249
        if fixed_ips:
250
            port['fixed_ips'] = fixed_ips
251
        if security_groups:
252
            port['security_groups'] = security_groups
253
        r = self.ports_post(json_data=dict(port=port), success=201)
254
        return r.json['port']
255

  
256
    def create_ports(self, ports):
257
        """Atomic operation for batch port creation (all or nothing)
258
        :param ports: (list of dicts) {key: ...} with all parameters in the
259
            method create_port, where method mandatory / optional paramteres
260
            respond to mandatory / optional paramters in ports items
261
        :returns: (list of dicts) created portss details
262
        :raises ValueError: if ports parameter is incorrectly formated
263
        :raises ClientError: if the request failed or didn't return 201
264
        """
265
        try:
266
            msg = 'The ports parameter must be list or tuple'
267
            assert (
268
                isinstance(ports, list) or isinstance(ports, tuple)), msg
269
            for port in ports:
270
                msg = 'Subnet specification %s is not a dict' % port
271
                assert isinstance(port, dict), msg
272
                err = set(port).difference((
273
                    'network_id', 'status', 'name', 'admin_state_up',
274
                    'mac_address', 'fixed_ips', 'security_groups'))
275
                if err:
276
                    raise ValueError(
277
                        'Invalid key(s): %s in port specification %s' % (
278
                            err, port))
279
                msg = 'network_id is missing in port spec: %s' % port
280
                assert port.get('network_id', None), msg
281
        except AssertionError as ae:
282
            raise ValueError('%s' % ae)
283
        r = self.ports_post(json_data=dict(ports=list(ports)), success=201)
284
        return r.json['ports']
285

  
286
    def get_port_details(self, port_id):
287
        r = self.ports_get(port_id, success=201)
288
        return r.json['ports']
289

  
290
    def delete_port(self, port_id):
291
        r = self.ports_delete(port_id, success=204)
292
        return r.headers
b/kamaki/clients/network/rest_api.py
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 kamaki.clients import Client
35
from kamaki.clients.utils import path4url
36
from json import dumps
37

  
38

  
39
class NetworkRestClient(Client):
40

  
41
    def networks_get(self, network_id=None, **kwargs):
42
        if network_id:
43
            return self.get(path4url('networks', network_id), **kwargs)
44
        return self.get(path4url('networks'), **kwargs)
45

  
46
    def networks_post(self, json_data, **kwargs):
47
        return self.post(path4url('networks'), json=json_data, **kwargs)
48

  
49
    def networks_put(self, network_id, json_data, **kwargs):
50
        return self.put(
51
            path4url('networks', network_id), json=json_data, **kwargs)
52

  
53
    def networks_delete(self, network_id, **kwargs):
54
        return self.delete(path4url('networks', network_id), **kwargs)
55

  
56
    def subnets_get(self, subnet_id=None, **kwargs):
57
        if subnet_id:
58
            return self.get(path4url('subnets', subnet_id), **kwargs)
59
        return self.get(path4url('subnets'), **kwargs)
60

  
61
    def subnets_post(self, json_data, **kwargs):
62
        return self.post(path4url('subnets'), json=json_data, **kwargs)
63

  
64
    def subnets_put(self, subnet_id, **kwargs):
65
        return self.put(path4url('subnets', subnet_id), **kwargs)
66

  
67
    def subnets_delete(self, subnet_id, **kwargs):
68
        return self.delete(path4url('subnets', subnet_id), **kwargs)
69

  
70
    def ports_get(self, port_id=None, **kwargs):
71
        if port_id:
72
            return self.get(path4url('ports', port_id), **kwargs)
73
        return self.get(path4url('ports'), **kwargs)
74

  
75
    def ports_post(
76
            self,
77
            json_data=None,
78
            name=None, mac_address=None, fixed_ips=None, security_groups=None,
79
            **kwargs):
80
        self.set_param('name', name, iff=name)
81
        self.set_param('mac_address', mac_address, iff=mac_address)
82
        self.set_param('fixed_ips', fixed_ips, iff=fixed_ips)
83
        self.set_param('security_groups', security_groups, iff=security_groups)
84
        return self.post(path4url('ports'), json=json_data, **kwargs)
85

  
86
    def ports_put(
87
            self, port_id,
88
            json_data=None,
89
            name=None, mac_address=None, fixed_ips=None, security_groups=None,
90
            **kwargs):
91
        self.set_param('name', name, iff=name)
92
        self.set_param('mac_address', mac_address, iff=mac_address)
93
        self.set_param('fixed_ips', fixed_ips, iff=fixed_ips)
94
        self.set_param('security_groups', security_groups, iff=security_groups)
95
        return self.put(path4url('ports', port_id), json=json_data, **kwargs)
96

  
97
    def ports_delete(self, port_id, **kwargs):
98
        return self.delete(path4url('ports', port_id), **kwargs)
b/kamaki/clients/network/test.py
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 network
40

  
41

  
42
class NetworkRestClient(TestCase):
43

  
44
    """Set up a ComputesRest thorough test"""
45
    def setUp(self):
46
        self.url = 'http://network.example.com'
47
        self.token = 'n2tw0rk70k3n'
48
        self.client = network.NetworkRestClient(self.url, self.token)
49

  
50
    def tearDown(self):
51
        del self.client
52

  
53
    def _assert(self, method_call, path, set_param=None, params=(), **kwargs):
54
        """Assert the REST method call is called as expected"""
55
        x0 = - len(params)
56
        for i, (k, v, c) in enumerate(params):
57
            self.assertEqual(set_param.mock_calls[x0 + i], call(k, v, iff=c))
58

  
59
        self.assertEqual(method_call.mock_calls[-1], call(path, **kwargs))
60

  
61
    @patch('kamaki.clients.Client.get', return_value='ret val')
62
    def test_networks_get(self, get):
63
        netid = 'netid'
64
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
65
            self.assertEqual(self.client.networks_get(**kwargs), 'ret val')
66
            self._assert(get, '/networks', **kwargs)
67
            self.assertEqual(
68
                self.client.networks_get(network_id=netid, **kwargs),
69
                'ret val')
70
            self._assert(get, '/networks/%s' % netid, **kwargs)
71

  
72
    @patch('kamaki.clients.Client.post', return_value='ret val')
73
    def test_networks_post(self, post):
74
        for kwargs in (
75
                dict(json_data=dict(k1='v1')),
76
                dict(json_data=dict(k2='v2'), k3='v3')):
77
            self.assertEqual(self.client.networks_post(**kwargs), 'ret val')
78
            json_data = kwargs.pop('json_data')
79
            self._assert(post, '/networks', json=json_data, **kwargs)
80

  
81
    @patch('kamaki.clients.Client.put', return_value='ret val')
82
    def test_networks_put(self, put):
83
        netid = 'netid'
84
        for kwargs in (
85
                dict(json_data=dict(k1='v1')),
86
                dict(json_data=dict(k2='v2'), k3='v3')):
87
            self.assertEqual(
88
                self.client.networks_put(netid, **kwargs), 'ret val')
89
            json_data = kwargs.pop('json_data')
90
            self._assert(
91
                put, '/networks/%s' % netid, json=json_data, **kwargs)
92

  
93
    @patch('kamaki.clients.Client.delete', return_value='ret val')
94
    def test_networks_delete(self, delete):
95
        netid = 'netid'
96
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
97
            self.assertEqual(
98
                self.client.networks_delete(netid, **kwargs), 'ret val')
99
            self._assert(delete, '/networks/%s' % netid, **kwargs)
100

  
101
    @patch('kamaki.clients.Client.get', return_value='ret val')
102
    def test_subnets_get(self, get):
103
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
104
            self.assertEqual(self.client.subnets_get(**kwargs), 'ret val')
105
            self._assert(get, '/subnets', **kwargs)
106

  
107
            subnet_id = 'subnet id'
108
            self.assertEqual(
109
                self.client.subnets_get(subnet_id=subnet_id, **kwargs),
110
                'ret val')
111
            self._assert(get, '/subnets/%s' % subnet_id, **kwargs)
112

  
113
    @patch('kamaki.clients.Client.post', return_value='ret val')
114
    def test_subnets_post(self, post):
115
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
116
            json_data = dict(subnets='some data')
117
            self.assertEqual(self.client.subnets_post(
118
                json_data=json_data, **kwargs), 'ret val')
119
            self._assert(post, '/subnets', json=json_data, **kwargs)
120

  
121
    @patch('kamaki.clients.Client.put', return_value='ret val')
122
    def test_subnets_put(self, put):
123
        subnet_id = 'subid'
124
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
125
            self.assertEqual(
126
                self.client.subnets_put(subnet_id, **kwargs), 'ret val')
127
            self._assert(put, '/subnets/%s' % subnet_id, **kwargs)
128

  
129
    @patch('kamaki.clients.Client.delete', return_value='ret val')
130
    def test_subnets_delete(self, delete):
131
        netid = 'netid'
132
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
133
            self.assertEqual(
134
                self.client.subnets_delete(netid, **kwargs), 'ret val')
135
            self._assert(delete, '/subnets/%s' % netid, **kwargs)
136

  
137
    @patch('kamaki.clients.Client.get', return_value='ret val')
138
    def test_ports_get(self, get):
139
        for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
140
            self.assertEqual(self.client.ports_get(**kwargs), 'ret val')
141
            self._assert(get, '/ports', **kwargs)
142

  
143
            port_id = 'port id'
144
            self.assertEqual(
145
                self.client.ports_get(port_id=port_id, **kwargs),
146
                'ret val')
147
            self._assert(get, '/ports/%s' % port_id, **kwargs)
148

  
149
    @patch('kamaki.clients.Client.set_param')
150
    @patch('kamaki.clients.Client.post', return_value='ret val')
151
    def test_ports_post(self, post, set_param):
152
        for params, kwargs in product(
153
                [p for p in product(
154
                    (
155
                        ('name', 'port name', 'port name'),
156
                        ('name', None, None)),
157
                    (
158
                        ('mac_address', 'max address', 'max address'),
159
                        ('mac_address', None, None)),
160
                    (
161
                        ('fixed_ips', 'fixed ip', 'fixed ip'),
162
                        ('fixed_ips', None, None)),
163
                    (
164
                        ('security_groups', 'sec groups', 'sec groups'),
165
                        ('security_groups', None, None))
166
                )],
167
                (dict(), dict(k1='v1'), dict(k2='v2', k3='v3'))):
168

  
169
            callargs = dict()
170
            for p in params:
171
                callargs[p[0]] = p[2]
172
            callargs.update(kwargs)
173

  
174
            self.assertEqual(self.client.ports_post(**callargs), 'ret val')
175
            self._assert(
176
                post, '/ports', set_param, params=params, json=None, **kwargs)
177

  
178
            json_data = dict(id='some id', other_param='other val')
179
            callargs['json_data'] = json_data
180
            self.assertEqual(self.client.ports_post(**callargs), 'ret val')
181
            self._assert(
182
                post, '/ports', set_param, params,
183
                json=json_data, **kwargs)
184

  
185
    @patch('kamaki.clients.Client.set_param')
186
    @patch('kamaki.clients.Client.put', return_value='ret val')
187
    def test_ports_put(self, put, set_param):
188
        port_id = 'portid'
189
        for params, kwargs in product(
190
                [p for p in product(
191
                    (
192
                        ('name', 'port name', 'port name'),
193
                        ('name', None, None)),
194
                    (
195
                        ('mac_address', 'max address', 'max address'),
196
                        ('mac_address', None, None)),
197
                    (
198
                        ('fixed_ips', 'fixed ip', 'fixed ip'),
199
                        ('fixed_ips', None, None)),
200
                    (
201
                        ('security_groups', 'sec groups', 'sec groups'),
202
                        ('security_groups', None, None))
203
                )],
204
                (dict(), dict(k1='v1'), dict(k2='v2', k3='v3'))):
205

  
206
            callargs = dict()
207
            for p in params:
208
                callargs[p[0]] = p[2]
209
            callargs.update(kwargs)
210

  
211
            self.assertEqual(
212
                self.client.ports_put(port_id, **callargs), 'ret val')
213
            self._assert(
214
                put, '/ports/%s' % port_id, set_param,
215
                params=params, json=None, **kwargs)
216

  
217
            json_data = dict(id='some id', other_param='other val')
218
            callargs['json_data'] = json_data
219
            self.assertEqual(
220
                self.client.ports_put(port_id, **callargs), 'ret val')
221
            self._assert(
222
                put, '/ports/%s' % port_id, set_param, params,
223
                json=json_data, **kwargs)
224

  
225

  
226
class FakeObject(object):
227

  
228
    json = None
229
    headers = None
230

  
231

  
232
class NetworkClient(TestCase):
233

  
234
    """Set up a ComputesRest thorough test"""
235
    def setUp(self):
236
        self.url = 'http://network.example.com'
237
        self.token = 'n2tw0rk70k3n'
238
        self.client = network.NetworkClient(self.url, self.token)
239

  
240
    def tearDown(self):
241
        FakeObject.json, FakeObject.headers = None, None
242
        del self.client
243

  
244
    @patch(
245
        'kamaki.clients.network.NetworkClient.networks_get',
246
        return_value=FakeObject())
247
    def test_list_networks(self, networks_get):
248
        FakeObject.json = dict(networks='ret val')
249
        self.assertEqual(self.client.list_networks(), 'ret val')
250
        networks_get.assert_called_once_with(success=200)
251

  
252
    @patch(
253
        'kamaki.clients.network.NetworkClient.networks_post',
254
        return_value=FakeObject())
255
    def test_create_network(self, networks_post):
256
        for admin_state_up, shared in product((None, True), (None, True)):
257
            FakeObject.json = dict(network='ret val')
258
            name = 'net name'
259
            self.assertEqual(
260
                self.client.create_network(
261
                    name, admin_state_up=admin_state_up, shared=shared),
262
                'ret val')
263
            req = dict(name=name, admin_state_up=bool(admin_state_up))
264
            if shared:
265
                req['shared'] = shared
266
            expargs = dict(json_data=dict(network=req), success=201)
267
            self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
268

  
269
    @patch(
270
        'kamaki.clients.network.NetworkClient.networks_post',
271
        return_value=FakeObject())
272
    def test_create_networks(self, networks_post):
273
        for networks in (
274
                None, dict(name='name'), 'nets', [1, 2, 3], [{'k': 'v'}, ],
275
                [dict(admin_state_up=True, shared=True)],
276
                [dict(name='n1', invalid='mistake'), ],
277
                [dict(name='valid', shared=True), {'err': 'nop'}]):
278
            self.assertRaises(
279
                ValueError, self.client.create_networks, networks)
280

  
281
        FakeObject.json = dict(networks='ret val')
282
        for networks in (
283
                [
284
                    dict(name='net1'),
285
                    dict(name='net 2', admin_state_up=False, shared=True)],
286
                [
287
                    dict(name='net1', admin_state_up=True),
288
                    dict(name='net 2', shared=False),
289
                    dict(name='net-3')],
290
                (dict(name='n.e.t'), dict(name='net 2'))):
291
            self.assertEqual(self.client.create_networks(networks), 'ret val')
292

  
293
            networks = list(networks)
294
            expargs = dict(json_data=dict(networks=networks), success=201)
295
            self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
296

  
297
    @patch(
298
        'kamaki.clients.network.NetworkClient.networks_get',
299
        return_value=FakeObject())
300
    def test_get_network_details(self, networks_get):
301
        netid, FakeObject.json = 'netid', dict(network='ret val')
302
        self.assertEqual(self.client.get_network_details(netid), 'ret val')
303
        networks_get.assert_called_once_with(netid, success=200)
304

  
305
    @patch(
306
        'kamaki.clients.network.NetworkClient.networks_put',
307
        return_value=FakeObject())
308
    def test_update_network(self, networks_put):
309
        netid, FakeObject.json = 'netid', dict(network='ret val')
310
        for name, admin_state_up, shared in product(
311
                ('net name', None), (True, None), (True, None)):
312
            kwargs = dict(
313
                name=name, admin_state_up=admin_state_up, shared=shared)
314
            self.assertEqual(
315
                self.client.update_network(netid, **kwargs), 'ret val')
316
            if name in (None, ):
317
                kwargs.pop('name')
318
            if admin_state_up in (None, ):
319
                kwargs.pop('admin_state_up')
320
            if shared in (None, ):
321
                kwargs.pop('shared')
322
            kwargs = dict(json_data=dict(network=kwargs), success=200)
323
            self.assertEqual(
324
                networks_put.mock_calls[-1], call(netid, **kwargs))
325

  
326
    @patch(
327
        'kamaki.clients.network.NetworkClient.networks_delete',
328
        return_value=FakeObject())
329
    def test_delete_network(self, networks_delete):
330
        netid, FakeObject.headers = 'netid', 'ret headers'
331
        self.assertEqual(self.client.delete_network(netid), 'ret headers')
332
        networks_delete.assert_called_once_with(netid, success=204)
333

  
334
    @patch(
335
        'kamaki.clients.network.NetworkClient.subnets_get',
336
        return_value=FakeObject())
337
    def test_list_subnets(self, subnets_get):
338
        FakeObject.json = dict(subnets='ret val')
339
        self.assertEqual(self.client.list_subnets(), 'ret val')
340
        subnets_get.assert_called_once_with(success=200)
341

  
342
    @patch(
343
        'kamaki.clients.network.NetworkClient.subnets_post',
344
        return_value=FakeObject())
345
    def test_create_subnet(self, subnets_post):
346
        for (
347
                name, allocation_pools, gateway_ip,
348
                subnet_id, ipv6, enable_dhcp) in product(
349
                    ('name', None), ('all pools', None), ('gip', None),
350
                    ('sid', None), (True, None), (True, None)):
351
            kwargs = dict(
352
                name=name, allocation_pools=allocation_pools,
353
                gateway_ip=gateway_ip, subnet_id=subnet_id,
354
                ipv6=ipv6, enable_dhcp=enable_dhcp)
355
            FakeObject.json, network_id, cidr = dict(subnet='rv'), 'name', 'cd'
356
            self.assertEqual(
357
                self.client.create_subnet(network_id, cidr, **kwargs), 'rv')
358
            req = dict(
359
                network_id=network_id, cidr=cidr,
360
                ip_version=6 if kwargs.pop('ipv6', None) else 4)
361
            for k, v in kwargs.items():
362
                if v:
363
                    req['id' if k == 'subnet_id' else k] = v
364
            expargs = dict(json_data=dict(subnet=req), success=201)
365
            self.assertEqual(subnets_post.mock_calls[-1], call(**expargs))
366

  
367
    @patch(
368
        'kamaki.clients.network.NetworkClient.subnets_post',
369
        return_value=FakeObject())
370
    def test_create_subnets(self, subnets_post):
371
        for subnets in (
372
                None, dict(network_id='name'), 'nets', [1, 2, 3], [{'k': 'v'}],
373
                [dict(ipv6=True, enable_dhcp=True)],
374
                [dict(network_id='n1', cidr='dr', invalid='mistake'), ],
375
                [dict(network_id='valid', cidr='valid'), {'err': 'nop'}]):
376
            self.assertRaises(
377
                ValueError, self.client.create_subnets, subnets)
378

  
379
        FakeObject.json = dict(subnets='ret val')
380
        for subnets in (
381
                [
382
                    dict(network_id='n1', cidr='c1'),
383
                    dict(network_id='n 2', cidr='c 2', name='name')],
384
                [
385
                    dict(network_id='n1', cidr='c 6', allocation_pools='a p'),
386
                    dict(network_id='n 2', cidr='c_4', gateway_ip='g ip'),
387
                    dict(network_id='n 2', cidr='c_4', subnet_id='s id'),
388
                    dict(network_id='n-4', cidr='c3', ipv6=True, name='w. 6'),
389
                    dict(network_id='n_5', cidr='c2', enable_dhcp=True)],
390
                (
391
                    dict(network_id='n.e.t', cidr='c-5'),
392
                    dict(network_id='net 2', cidr='c 2'))):
393
            self.assertEqual(self.client.create_subnets(subnets), 'ret val')
394

  
395
            for subnet in subnets:
396
                subnet['ip_version'] = 6 if subnet.pop('ipv6', None) else 4
397
                if 'subnet_id' in subnet:
398
                    subnet['id'] = subnet.pop('subnet_id')
399
            subnets = list(subnets)
400
            expargs = dict(json_data=dict(subnets=subnets), success=201)
401
            self.assertEqual(subnets_post.mock_calls[-1], call(**expargs))
402

  
403
    @patch(
404
        'kamaki.clients.network.NetworkClient.subnets_get',
405
        return_value=FakeObject())
406
    def test_get_subnet_details(self, subnets_get):
407
        subid, FakeObject.json = 'subid', 'ret val'
408
        self.assertEqual(self.client.get_subnet_details(subid), 'ret val')
409
        subnets_get.assert_called_once_with(subid, success=201)
410

  
411
    @patch(
412
        'kamaki.clients.network.NetworkClient.subnets_put',
413
        return_value=FakeObject())
414
    def test_update_subnet(self, subnets_put):
415
        for (
416
                name, allocation_pools, gateway_ip,
417
                subnet_id, ipv6, enable_dhcp) in product(
418
                    ('name', None), ('all pools', None), ('gip', None),
419
                    ('sid', None), (True, False, None), (True, False, None)):
420
            kwargs = dict(
421
                name=name, allocation_pools=allocation_pools,
422
                gateway_ip=gateway_ip, subnet_id=subnet_id,
423
                ipv6=ipv6, enable_dhcp=enable_dhcp)
424
            FakeObject.json, network_id, cidr = dict(subnet='rv'), 'name', 'cd'
425
            self.assertEqual(
426
                self.client.update_subnet(network_id, cidr, **kwargs), 'rv')
427
            req = dict(network_id=network_id, cidr=cidr)
428
            if kwargs.get('ipv6', None) not in (None, ):
429
                req['ip_version'] = 6 if kwargs.pop('ipv6') else 4
430
            for k, v in kwargs.items():
431
                if v not in (None, ):
432
                    req['id' if k == 'subnet_id' else k] = v
433
            expargs = dict(json_data=dict(subnet=req), success=201)
434
            self.assertEqual(subnets_put.mock_calls[-1], call(**expargs))
435

  
436
    @patch(
437
        'kamaki.clients.network.NetworkClient.subnets_delete',
438
        return_value=FakeObject())
439
    def test_delete_subnet(self, subnets_delete):
440
        netid, FakeObject.headers = 'netid', 'ret headers'
441
        self.assertEqual(self.client.delete_subnet(netid), 'ret headers')
442
        subnets_delete.assert_called_once_with(netid, success=204)
443

  
444
    @patch(
445
        'kamaki.clients.network.NetworkClient.ports_get',
446
        return_value=FakeObject())
447
    def test_list_ports(self, ports_get):
448
        FakeObject.json = dict(ports='ret val')
449
        self.assertEqual(self.client.list_ports(), 'ret val')
450
        ports_get.assert_called_once_with(success=200)
451

  
452
    @patch(
453
        'kamaki.clients.network.NetworkClient.ports_post',
454
        return_value=FakeObject())
455
    def test_create_port(self, ports_post):
456
        for (
457
                name, status, admin_state_up,
458
                mac_address, fixed_ips, security_groups
459
                ) in product(
460
                    ('name', None), ('status', None), (True, False, None),
461
                    ('maddr', None), ('some ips', None), ([1, 2, 3], None)):
462
            kwargs = dict(
463
                name=name, status=status, admin_state_up=admin_state_up,
464
                mac_address=mac_address, fixed_ips=fixed_ips,
465
                security_groups=security_groups)
466
            FakeObject.json, network_id = dict(port='ret val'), 'name'
467
            self.assertEqual(
468
                self.client.create_port(network_id, **kwargs), 'ret val')
469
            req = dict(network_id=network_id)
470
            for k, v in kwargs.items():
471
                if v not in (None, ):
472
                    req[k] = v
473
            expargs = dict(json_data=dict(port=req), success=201)
474
            self.assertEqual(ports_post.mock_calls[-1], call(**expargs))
475

  
476
    @patch(
477
        'kamaki.clients.network.NetworkClient.ports_post',
478
        return_value=FakeObject())
479
    def test_create_ports(self, ports_post):
480
        for ports in (
481
                None, dict(network_id='name'), 'nets', [1, 2, 3], [{'k': 'v'}],
482
                [dict(name=True, mac_address='mac')],
483
                [dict(network_id='n1', invalid='mistake'), ],
484
                [dict(network_id='valid', name='valid'), {'err': 'nop'}]):
485
            self.assertRaises(
486
                ValueError, self.client.create_ports, ports)
487

  
488
        FakeObject.json = dict(ports='ret val')
489
        for ports in (
490
                [dict(network_id='n1'), dict(network_id='n 2', name='name')],
491
                [
492
                    dict(network_id='n1', name='n 6', status='status'),
493
                    dict(network_id='n 2', admin_state_up=True, fixed_ips='f'),
494
                    dict(network_id='n 2', mac_address='mc', name='n.a.m.e.'),
495
                    dict(network_id='n-4', security_groups='s~G', name='w. 6'),
496
                    dict(network_id='n_5', admin_state_up=False, name='f a')],
497
                (
498
                    dict(network_id='n.e.t', name='c-5'),
499
                    dict(network_id='net 2', status='YEAH'))):
500
            self.assertEqual(self.client.create_ports(ports), 'ret val')
501
            expargs = dict(json_data=dict(ports=list(ports)), success=201)
502
            self.assertEqual(ports_post.mock_calls[-1], call(**expargs))
503

  
504
    @patch(
505
        'kamaki.clients.network.NetworkClient.ports_get',
506
        return_value=FakeObject())
507
    def test_get_port_details(self, ports_get):
508
        portid, FakeObject.json = 'portid', dict(ports='ret val')
509
        self.assertEqual(self.client.get_port_details(portid), 'ret val')
510
        ports_get.assert_called_once_with(portid, success=201)
511

  
512
    @patch(
513
        'kamaki.clients.network.NetworkClient.ports_delete',
514
        return_value=FakeObject())
515
    def test_delete_port(self, ports_delete):
516
        portid, FakeObject.headers = 'portid', 'ret headers'
517
        self.assertEqual(self.client.delete_port(portid), 'ret headers')
518
        ports_delete.assert_called_once_with(portid, success=204)
519

  
520

  
521
if __name__ == '__main__':
522
    from sys import argv
523
    from kamaki.clients.test import runTestCase
524
    not_found = True
525
    if not argv[1:] or argv[1] == 'NetworkClient':
526
        not_found = False
527
        runTestCase(NetworkClient, 'Network Client', argv[2:])
528
    if not argv[1:] or argv[1] == 'NetworkRestClient':
529
        not_found = False
530
        runTestCase(NetworkRestClient, 'NetworkRest Client', argv[2:])
531
    if not_found:
532
        print('TestCase %s not found' % argv[1])
/dev/null
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 kamaki.clients import ClientError
35
from kamaki.clients.networking.rest_api import NetworkingRestClient
36

  
37

  
38
class NetworkingClient(NetworkingRestClient):
39
    """OpenStack Network API 2.0 client"""
40

  
41
    def list_networks(self):
42
        r = self.networks_get(success=200)
43
        return r.json['networks']
44

  
45
    def create_network(self, name, admin_state_up=None, shared=None):
46
        req = dict(network=dict(
47
            name=name, admin_state_up=bool(admin_state_up)))
48
        if shared not in (None, ):
49
            req['network']['shared'] = bool(shared)
50
        r = self.networks_post(json_data=req, success=201)
51
        return r.json['network']
52

  
53
    def create_networks(self, networks):
54
        """Atomic operation for batch network creation (all or nothing)
55
        :param networks: (list) [
56
            {name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..},
57
            {name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..}]
58
            name is mandatory, the rest is optional
59
            e.g., create_networks([
60
                {name: 'net1', admin_state_up: True},
61
                {name: 'net2'}])
62
        :returns: (list of dicts) created networks details
63
        :raises ValueError: if networks is misformated
64
        :raises ClientError: if the request failed or didn't return 201
65
        """
66
        try:
67
            msg = 'The networks parameter must be list or tuple'
68
            assert (
69
                isinstance(networks, list) or isinstance(networks, tuple)), msg
70
            for network in networks:
71
                msg = 'Network specification %s is not a dict' % network
72
                assert isinstance(network, dict), msg
73
                err = set(network).difference(
74
                    ('name', 'admin_state_up', 'shared'))
75
                if err:
76
                    raise ValueError(
77
                        'Invalid key(s): %s in network specification %s' % (
78
                            err, network))
79
                msg = 'Name is missing in network specification: %s' % network
80
                assert network.get('name', None), msg
81
                network.setdefault('admin_state_up', False)
82
        except AssertionError as ae:
83
            raise ValueError('%s' % ae)
84

  
85
        req = dict(networks=list(networks))
86
        r = self.networks_post(json_data=req, success=201)
87
        return r.json['networks']
88

  
89
    def get_network_details(self, network_id):
90
        r = self.networks_get(network_id, success=200)
91
        return r.json['network']
92

  
93
    def update_network(
94
            self, network_id, name=None, admin_state_up=None, shared=None):
95
        network = dict()
96
        if name:
97
            network['name'] = name
98
        if admin_state_up not in (None, ):
99
            network['admin_state_up'] = admin_state_up
100
        if shared not in (None, ):
101
            network['shared'] = shared
102
        network = dict(network=network)
103
        r = self.networks_put(network_id, json_data=network, success=200)
104
        return r.json['network']
105

  
106
    def delete_network(self, network_id):
107
        r = self.networks_delete(network_id, success=204)
108
        return r.headers
109

  
110
    def list_subnets(self):
111
        r = self.subnets_get(success=200)
112
        return r.json['subnets']
113

  
114
    def create_subnet(
115
            self, network_id, cidr,
116
            name=None, allocation_pools=None, gateway_ip=None, subnet_id=None,
117
            ipv6=None, enable_dhcp=None):
118
        """
119
        :param network_id: (str)
120
        :param cidr: (str)
121

  
122
        :param name: (str) The subnet name
123
        :param allocation_pools: (list of dicts) start/end addresses of
124
            allocation pools: [{'start': ..., 'end': ...}, ...]
125
        :param gateway_ip: (str)
126
        :param subnet_id: (str)
127
        :param ipv6: (bool) ip_version == 6 if true else 4 (default)
128
        :param enable_dhcp: (bool)
129
        """
130
        subnet = dict(
131
            network_id=network_id, cidr=cidr, ip_version=6 if ipv6 else 4)
132
        if name:
133
            subnet['name'] = name
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff