Commit ba1ab65d authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Implement create_subnet(s), update implementations

Refs: #4546

OpenStack modified the API during development, that was odd!
parent afba96a7
# Copyright 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
:param Copyright 2013 GRNET S.A. All rights reserved.
:param
:param Redistribution and use in source and binary forms, with or
:param without modification, are permitted provided that the following
:param conditions are met:
:param
:param 1. Redistributions of source code must retain the above
:param copyright notice, this list of conditions and the following
:param disclaimer.
:param
:param 2. Redistributions in binary form must reproduce the above
:param copyright notice, this list of conditions and the following
:param disclaimer in the documentation and/or other materials
:param provided with the distribution.
:param
:param THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
:param OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
:param WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
:param PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
:param CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
:param SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
:param LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
:param USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
:param AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
:param LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
:param ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
:param POSSIBILITY OF SUCH DAMAGE.
:param
:param The views and conclusions contained in the software and
:param documentation are those of the authors and should not be
:param interpreted as representing official policies, either expressed
:param or implied, of GRNET S.A.
from kamaki.clients import ClientError
from kamaki.clients.networking.rest_api import NetworkingRestClient
......@@ -44,18 +44,22 @@ class NetworkingClient(NetworkingRestClient):
def create_network(self, name, admin_state_up=None, shared=None):
req = dict(network=dict(
name=name or '', admin_state_up=bool(admin_state_up)))
r = self.networks_post(json_data=req, shared=shared, success=201)
name=name, admin_state_up=bool(admin_state_up)))
if shared not in (None, ):
req['network']['shared'] = bool(shared)
r = self.networks_post(json_data=req, success=201)
return r.json['network']
def create_networks(self, networks, shared=None):
"""
:param networks: (list or tuple) [
{name: ..., admin_state_up: ...},
{name:..., admin_state_up: ...}]
def create_networks(self, networks):
"""Atomic operation for batch network creation (all or nothing)
:param networks: (list) [
{name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..},
{name: ..(str).., admin_state_up: ..(bool).., shared: ..(bool)..}]
name is mandatory, the rest is optional
e.g. create_networks(({name: 'net1', shared: True}, {name: net2}))
:returns: list of dicts of created networks
e.g., create_networks([
{name: 'net1', admin_state_up: True},
{name: 'net2'}])
:returns: (list of dicts) created networks details
:raises ValueError: if networks is misformated
:raises ClientError: if the request failed or didn't return 201
"""
......@@ -66,7 +70,8 @@ class NetworkingClient(NetworkingRestClient):
for network in networks:
msg = 'Network specification %s is not a dict' % network
assert isinstance(network, dict), msg
err = set(network).difference(('name', 'admin_state_up'))
err = set(network).difference(
('name', 'admin_state_up', 'shared'))
if err:
raise ValueError(
'Invalid key(s): %s in network specification %s' % (
......@@ -78,7 +83,7 @@ class NetworkingClient(NetworkingRestClient):
raise ValueError('%s' % ae)
req = dict(networks=list(networks))
r = self.networks_post(json_data=req, shared=shared, success=201)
r = self.networks_post(json_data=req, success=201)
return r.json['networks']
def get_network_details(self, network_id):
......@@ -92,11 +97,81 @@ class NetworkingClient(NetworkingRestClient):
network['name'] = name
if admin_state_up not in (None, ):
network['admin_state_up'] = admin_state_up
if shared not in (None, ):
network['shared'] = shared
network = dict(network=network)
r = self.networks_put(
network_id, json_data=network, shared=shared, success=200)
r = self.networks_put(network_id, json_data=network, success=200)
return r.json['network']
def delete_network(self, network_id):
r = self.networks_delete(network_id, success=204)
return r.headers
def list_subnets(self):
r = self.subnets_get(success=200)
return r.json['subnets']
def create_subnet(
self, network_id, cidr,
name=None, allocation_pools=None, gateway_ip=None, subnet_id=None
ipv6=None, endble_dhcp=None):
"""
:param network_id: (str)
:param cidr: (str)
:param name: (str) The subnet name
:param allocation_pools: (list of dicts) start/end addresses of
allocation pools: [{'start': ..., 'end': ...}, ...]
:param gateway_ip: (str)
:param subnet_id: (str)
:param ipv6: (bool) ip_version == 6 if true else 4 (default)
:param enable_dhcp: (bool)
"""
subnet = dict(
network_id=network_id, cidr=cidr, ip_version=6 if ipv6 else 4)
if name:
subnet['name'] = name
if allocation_pools:
subnet['allocation_pools'] = allocation_pools
if gateway_ip:
subnet['gateway_ip'] = gateway_ip
if subnet_id:
subnet['id'] = subnet_id
if enable_dhcp not in (None, ):
subnet['enable_dhcp'] = bool(enable_dhcp)
r = self.subnets_post(json_data=dict(subnet=subnet), success=201)
return r.json['subnet']
def create_subnets(self, subnets):
"""Atomic operation for batch subnet creation (all or nothing)
:param subnets: (list of dicts) {key: ...} with all parameters in the
method create_subnet, where method mandatory / optional paramteres
respond to mandatory / optional paramters in subnets items
:returns: (list of dicts) created subnetss details
:raises ValueError: if subnets parameter is incorrectly formated
:raises ClientError: if the request failed or didn't return 201
"""
try:
msg = 'The subnets parameter must be list or tuple'
assert (
isinstance(subnets, list) or isinstance(subnets, tuple)), msg
for subnet in subnets:
msg = 'Subnet specification %s is not a dict' % subnet
assert isinstance(subnet, dict), msg
err = set(subnet).difference((
'network_id', 'cidr', 'name', 'allocation_pools',
'gateway_ip', 'subnet_id', 'ipv6', 'endble_dhcp'))
if err:
raise ValueError(
'Invalid key(s): %s in subnet specification %s' % (
err, subnet))
msg = 'network_id is missing in subnet spec: %s' % subnet
assert subnet.get('network_id', None), msg
msg = 'cidr is missing in subnet spec: %s' % subnet
assert subnet.get('cidr', None), msg
subnet['ip_version'] = 6 if subnet.pop('ipv6', None) else 4
except AssertionError as ae:
raise ValueError('%s' % ae)
r = self.subnets_post(json_data=dict(subnets=subnets), success=201)
return r.json['subnets']
......@@ -43,23 +43,12 @@ class NetworkingRestClient(Client):
return self.get(path4url('networks', network_id), **kwargs)
return self.get(path4url('networks'), **kwargs)
def networks_post(self, json_data=None, shared=None, **kwargs):
path = path4url('networks')
self.set_param('shared', bool(shared), iff=shared)
return self.post(
path, data=dumps(json_data) if json_data else None, **kwargs)
def networks_put(
self, network_id,
json_data=None, admin_state_up=None, shared=None, **kwargs):
path = path4url('networks', network_id)
self.set_param(
'admin_state_up', bool(admin_state_up), iff=admin_state_up)
self.set_param('shared', bool(shared), iff=shared)
def networks_post(self, json_data, **kwargs):
return self.post(path4url('networks'), data=dumps(json_data), **kwargs)
def networks_put(self, network_id, json_data, **kwargs):
return self.put(
path, data=dumps(json_data) if json_data else None, **kwargs)
path4url('networks', network_id), data=dumps(json_data), **kwargs)
def networks_delete(self, network_id, **kwargs):
return self.delete(path4url('networks', network_id), **kwargs)
......@@ -69,8 +58,8 @@ class NetworkingRestClient(Client):
return self.get(path4url('subnets', subnet_id), **kwargs)
return self.get(path4url('subnets'), **kwargs)
def subnets_post(self, **kwargs):
return self.post(path4url('subnets'), **kwargs)
def subnets_post(self, json_data, **kwargs):
return self.post(path4url('subnets'), data=dumps(json_data), **kwargs)
def subnets_put(self, subnet_id, **kwargs):
return self.put(path4url('subnets', subnet_id), **kwargs)
......
......@@ -60,73 +60,35 @@ class NetworkingRestClient(TestCase):
@patch('kamaki.clients.Client.get', return_value='ret val')
def test_networks_get(self, get):
netid = 'netid'
for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
self.assertEqual(self.client.networks_get(**kwargs), 'ret val')
self._assert(get, '/networks', **kwargs)
netid = 'netid'
self.assertEqual(
self.client.networks_get(network_id=netid, **kwargs),
'ret val')
self._assert(get, '/networks/%s' % netid, **kwargs)
@patch('kamaki.clients.Client.set_param')
@patch('kamaki.clients.Client.post', return_value='ret val')
def test_networks_post(self, post, set_param):
for params, kwargs in product(
(
(('shared', False, None), ),
(('shared', True, True), )),
(dict(), dict(k1='v1'), dict(k2='v2', k3='v3'))):
callargs = dict()
for p in params:
callargs[p[0]] = p[2]
callargs.update(kwargs)
self.assertEqual(self.client.networks_post(**callargs), 'ret val')
self._assert(
post, '/networks', set_param,
params=params, data=None, **kwargs)
def test_networks_post(self, post):
for kwargs in (
dict(json_data=dict(k1='v1')),
dict(json_data=dict(k2='v2'), k3='v3')):
self.assertEqual(self.client.networks_post(**kwargs), 'ret val')
json_data = kwargs.pop('json_data')
self._assert(post, '/networks', data=dumps(json_data), **kwargs)
json_data = dict(id='some id', other_param='other val')
callargs['json_data'] = json_data
self.assertEqual(self.client.networks_post(**callargs), 'ret val')
self._assert(
post, '/networks', set_param, params,
data=dumps(json_data), **kwargs)
@patch('kamaki.clients.Client.set_param')
@patch('kamaki.clients.Client.put', return_value='ret val')
def test_networks_put(self, put, set_param):
def test_networks_put(self, put):
netid = 'netid'
for params, kwargs in product(
[p for p in product(
(
('admin_state_up', False, None),
('admin_state_up', True, True)),
(('shared', False, None), ('shared', True, True)),
)],
(dict(), dict(k1='v1'), dict(k2='v2', k3='v3'))):
callargs = dict()
for p in params:
callargs[p[0]] = p[2]
callargs.update(kwargs)
self.assertEqual(
self.client.networks_put(netid, **callargs), 'ret val')
self._assert(
put, '/networks/%s' % netid, set_param, params,
data=None, **kwargs)
json_data = dict(id='some id', other_param='other val')
callargs['json_data'] = json_data
for kwargs in (
dict(json_data=dict(k1='v1')),
dict(json_data=dict(k2='v2'), k3='v3')):
self.assertEqual(
self.client.networks_put(netid, **callargs), 'ret val')
self.client.networks_put(netid, **kwargs), 'ret val')
json_data = kwargs.pop('json_data')
self._assert(
put, '/networks/%s' % netid, set_param, params,
data=dumps(json_data), **kwargs)
put, '/networks/%s' % netid, data=dumps(json_data), **kwargs)
@patch('kamaki.clients.Client.delete', return_value='ret val')
def test_networks_delete(self, delete):
......@@ -151,8 +113,10 @@ class NetworkingRestClient(TestCase):
@patch('kamaki.clients.Client.post', return_value='ret val')
def test_subnets_post(self, post):
for kwargs in (dict(), dict(k1='v1'), dict(k2='v2', k3='v3')):
self.assertEqual(self.client.subnets_post(**kwargs), 'ret val')
self._assert(post, '/subnets', **kwargs)
json_data = dict(subnets='some data')
self.assertEqual(self.client.subnets_post(
json_data=json_data, **kwargs), 'ret val')
self._assert(post, '/subnets', data=dumps(json_data), **kwargs)
@patch('kamaki.clients.Client.put', return_value='ret val')
def test_subnets_put(self, put):
......@@ -275,6 +239,7 @@ class NetworkingClient(TestCase):
self.client = networking.NetworkingClient(self.url, self.token)
def tearDown(self):
FakeObject.json, FakeObject.headers = None, None
del self.client
@patch(
......@@ -289,55 +254,45 @@ class NetworkingClient(TestCase):
'kamaki.clients.networking.NetworkingClient.networks_post',
return_value=FakeObject())
def test_create_network(self, networks_post):
for kwargs in (dict(shared=None), dict(shared=True)):
for admin_state_up, shared in product((None, True), (None, True)):
FakeObject.json = dict(network='ret val')
req = dict()
for k, v, exp in (
('admin_state_up', None, False),
('admin_state_up', True, True)):
fullargs = dict(kwargs)
fullargs[k], name = v, 'net name'
self.assertEqual(
self.client.create_network(name, **fullargs), 'ret val')
req[k] = exp
req['name'] = name
expargs = dict(kwargs)
expargs = dict(json_data=dict(network=req), success=201)
expargs.update(kwargs)
self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
name = 'net name'
self.assertEqual(
self.client.create_network(
name, admin_state_up=admin_state_up, shared=shared),
'ret val')
req = dict(name=name, admin_state_up=bool(admin_state_up))
if shared:
req['shared'] = shared
expargs = dict(json_data=dict(network=req), success=201)
self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
@patch(
'kamaki.clients.networking.NetworkingClient.networks_post',
return_value=FakeObject())
def test_create_networks(self, networks_post):
for networks, shared in product(
(
None, dict(name='name'), 'nets', [1, 2, 3], [{'k': 'v'}, ],
[dict(name='n1', invalid='mistake'), ],
[dict(name='valid', admin_state_up=True), {'err': 'nop'}]),
(None, True)
):
for networks in (
None, dict(name='name'), 'nets', [1, 2, 3], [{'k': 'v'}, ],
[dict(admin_state_up=True, shared=True)],
[dict(name='n1', invalid='mistake'), ],
[dict(name='valid', shared=True), {'err': 'nop'}]):
self.assertRaises(
ValueError, self.client.create_networks, networks, shared)
ValueError, self.client.create_networks, networks)
FakeObject.json = dict(networks='ret val')
for networks, kwargs in product(
(
[
dict(name='net1'),
dict(name='net 2', admin_state_up=False)],
[
dict(name='net1', admin_state_up=True),
dict(name='net 2', admin_state_up=False),
dict(name='net-3')],
(dict(name='n.e.t'), dict(name='net 2'))),
(dict(shared=None), dict(shared=True))):
self.assertEqual(
self.client.create_networks(networks, **kwargs), 'ret val')
for networks in (
[
dict(name='net1'),
dict(name='net 2', admin_state_up=False, shared=True)],
[
dict(name='net1', admin_state_up=True),
dict(name='net 2', shared=False),
dict(name='net-3')],
(dict(name='n.e.t'), dict(name='net 2'))):
self.assertEqual(self.client.create_networks(networks), 'ret val')
networks = list(networks)
expargs = dict(json_data=dict(networks=networks), success=201)
expargs.update(kwargs)
self.assertEqual(networks_post.mock_calls[-1], call(**expargs))
@patch(
......@@ -359,13 +314,13 @@ class NetworkingClient(TestCase):
name=name, admin_state_up=admin_state_up, shared=shared)
self.assertEqual(
self.client.update_network(netid, **kwargs), 'ret val')
req = dict()
if name not in (None, ):
req['name'] = name
if admin_state_up not in (None, ):
req['admin_state_up'] = admin_state_up
kwargs = dict(
json_data=dict(network=req), shared=shared, success=200)
if name in (None, ):
kwargs.pop('name')
if admin_state_up in (None, ):
kwargs.pop('admin_state_up')
if shared in (None, ):
kwargs.pop('shared')
kwargs = dict(json_data=dict(network=kwargs), success=200)
self.assertEqual(
networks_put.mock_calls[-1], call(netid, **kwargs))
......@@ -377,6 +332,14 @@ class NetworkingClient(TestCase):
self.assertEqual(self.client.delete_network(netid), 'ret headers')
networks_delete.assert_called_once_with(netid, success=204)
@patch(
'kamaki.clients.networking.NetworkingClient.subnets_get',
return_value=FakeObject())
def test_list_subnets(self, subnets_get):
FakeObject.json = dict(subnets='ret val')
self.assertEqual(self.client.list_subnets(), 'ret val')
subnets_get.assert_called_once_with(success=200)
if __name__ == '__main__':
from sys import argv
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment