Update ensure_router_interface by using an Heat stack
Change-Id: I6605da8d6a7ce72a3207db7e05f195946d931b8c
This commit is contained in:
parent
c1ab286885
commit
7c0c433a0d
@ -65,6 +65,7 @@ L3haDifferentHostServerStackFixture = _l3ha.L3haDifferentHostServerStackFixture
|
|||||||
L3haSameHostServerStackFixture = _l3ha.L3haSameHostServerStackFixture
|
L3haSameHostServerStackFixture = _l3ha.L3haSameHostServerStackFixture
|
||||||
|
|
||||||
NetworkStackFixture = _neutron.NetworkStackFixture
|
NetworkStackFixture = _neutron.NetworkStackFixture
|
||||||
|
RouterInterfaceStackFixture = _neutron.RouterInterfaceStackFixture
|
||||||
RouterStackFixture = _neutron.RouterStackFixture
|
RouterStackFixture = _neutron.RouterStackFixture
|
||||||
NetworkWithNetMtuWriteStackFixture = (
|
NetworkWithNetMtuWriteStackFixture = (
|
||||||
_neutron.NetworkWithNetMtuWriteStackFixture)
|
_neutron.NetworkWithNetMtuWriteStackFixture)
|
||||||
@ -75,6 +76,7 @@ has_external_network = _neutron.has_external_network
|
|||||||
skip_unless_has_external_network = _neutron.skip_unless_has_external_network
|
skip_unless_has_external_network = _neutron.skip_unless_has_external_network
|
||||||
get_floating_network_id = _neutron.get_floating_network_id
|
get_floating_network_id = _neutron.get_floating_network_id
|
||||||
get_floating_network = _neutron.get_floating_network
|
get_floating_network = _neutron.get_floating_network
|
||||||
|
ensure_router_interface = _neutron.ensure_router_interface
|
||||||
has_floating_network = _neutron.has_floating_network
|
has_floating_network = _neutron.has_floating_network
|
||||||
skip_unless_has_floating_network = _neutron.skip_unless_has_floating_network
|
skip_unless_has_floating_network = _neutron.skip_unless_has_floating_network
|
||||||
get_router_id = _neutron.get_router_id
|
get_router_id = _neutron.get_router_id
|
||||||
|
@ -155,8 +155,10 @@ class RouterStackFixture(ExternalNetworkStackFixture):
|
|||||||
return requirements
|
return requirements
|
||||||
|
|
||||||
def ensure_router_interface(self,
|
def ensure_router_interface(self,
|
||||||
subnet: neutron.SubnetIdType):
|
subnet: neutron.SubnetIdType = None,
|
||||||
ensure_router_interface(subnet=subnet,
|
network: neutron.NetworkIdType = None):
|
||||||
|
ensure_router_interface(network=network,
|
||||||
|
subnet=subnet,
|
||||||
router=self.router_id,
|
router=self.router_id,
|
||||||
client=self.neutron_client)
|
client=self.neutron_client)
|
||||||
|
|
||||||
@ -167,18 +169,21 @@ class RouterStackFixture(ExternalNetworkStackFixture):
|
|||||||
return self._neutron_client
|
return self._neutron_client
|
||||||
|
|
||||||
|
|
||||||
def ensure_router_interface(subnet: neutron.SubnetIdType,
|
def ensure_router_interface(
|
||||||
router: neutron.RouterIdType = None,
|
router: neutron.RouterIdType = None,
|
||||||
client: neutron.NeutronClientType = None) \
|
subnet: neutron.SubnetIdType = None,
|
||||||
-> neutron.PortType:
|
network: neutron.NetworkIdType = None,
|
||||||
|
client: neutron.NeutronClientType = None,
|
||||||
|
add_cleanup=False) -> neutron.PortType:
|
||||||
|
client = neutron.neutron_client(client)
|
||||||
if router is None:
|
if router is None:
|
||||||
router = get_router_id()
|
router = get_router_id()
|
||||||
router_id = neutron.get_router_id(router)
|
if subnet is None and network is None:
|
||||||
subnet_id = neutron.get_subnet_id(subnet)
|
raise ValueError('Must specify a network or a subnet')
|
||||||
client = neutron.neutron_client(client)
|
|
||||||
try:
|
try:
|
||||||
port = neutron.find_port(fixed_ips=f"subnet_id={subnet_id}",
|
port = neutron.find_port(network=network,
|
||||||
device_id=router_id,
|
device=router,
|
||||||
|
subnet=subnet,
|
||||||
client=client)
|
client=client)
|
||||||
except tobiko.ObjectNotFound:
|
except tobiko.ObjectNotFound:
|
||||||
pass
|
pass
|
||||||
@ -187,37 +192,44 @@ def ensure_router_interface(subnet: neutron.SubnetIdType,
|
|||||||
LOG.debug(f'Router interface already exist:\n{port_dump}')
|
LOG.debug(f'Router interface already exist:\n{port_dump}')
|
||||||
return port
|
return port
|
||||||
|
|
||||||
subnet = neutron.ensure_subnet_gateway(subnet=subnet,
|
if subnet is None:
|
||||||
client=client)
|
assert network is not None
|
||||||
gateway_ip = subnet['gateway_ip']
|
LOG.info("Add router interface to network "
|
||||||
try:
|
f"{neutron.get_network_id(network)}")
|
||||||
port = neutron.find_port(fixed_ips=[f'ip_address={gateway_ip}'],
|
for _subnet in neutron.list_subnets(network=network,
|
||||||
client=client)
|
client=client):
|
||||||
except tobiko.ObjectNotFound:
|
neutron.ensure_subnet_gateway(subnet=_subnet,
|
||||||
LOG.info(f"Add router interface: subnet={subnet_id}")
|
client=client)
|
||||||
interface = neutron.add_router_interface(router=router,
|
|
||||||
subnet=subnet,
|
|
||||||
add_cleanup=False,
|
|
||||||
client=client)
|
|
||||||
port = neutron.find_port(fixed_ips=f"subnet_id={subnet_id}",
|
|
||||||
device_id=router_id)
|
|
||||||
else:
|
else:
|
||||||
port_dump = json.dumps(port, indent=4, sort_keys=True)
|
subnet = neutron.ensure_subnet_gateway(subnet=subnet,
|
||||||
LOG.debug(f'Port with gateway IP already exists:\n{port_dump}')
|
client=client)
|
||||||
port = neutron.create_port(client=client,
|
gateway_ip = subnet['gateway_ip']
|
||||||
network=subnet['network_id'],
|
try:
|
||||||
add_cleanup=False)
|
port = neutron.find_port(fixed_ips=[f'ip_address={gateway_ip}'],
|
||||||
interface = neutron.add_router_interface(router=router,
|
client=client)
|
||||||
port=port,
|
except tobiko.ObjectNotFound:
|
||||||
add_cleanup=False,
|
LOG.info("Add router interface to subnet "
|
||||||
client=client)
|
f"{neutron.get_subnet_id(subnet)}")
|
||||||
|
else:
|
||||||
|
# it must force router to bind new network port because subnet
|
||||||
|
# gateway IP address is already being used
|
||||||
|
port_dump = json.dumps(port, indent=4, sort_keys=True)
|
||||||
|
LOG.debug(f'Port with gateway IP already exists:\n{port_dump}')
|
||||||
|
if network is None:
|
||||||
|
network = subnet['network_id']
|
||||||
|
subnet = None
|
||||||
|
LOG.info("Add router interface to network "
|
||||||
|
f"{neutron.get_network_id(network)}")
|
||||||
|
|
||||||
interface_dump = json.dumps(interface, sort_keys=True, indent=4)
|
stack = RouterInterfaceStackFixture(router=router,
|
||||||
port_dump = json.dumps(port, indent=4, sort_keys=True)
|
subnet=subnet,
|
||||||
LOG.info("Router interface added:\n"
|
network=network,
|
||||||
f"{interface_dump}\n"
|
neutron_client=client)
|
||||||
f"{port_dump}\n")
|
if add_cleanup:
|
||||||
return port
|
tobiko.use_fixture(stack)
|
||||||
|
else:
|
||||||
|
tobiko.setup_fixture(stack)
|
||||||
|
return stack.port_details
|
||||||
|
|
||||||
|
|
||||||
@neutron.skip_if_missing_networking_extensions('port-security')
|
@neutron.skip_if_missing_networking_extensions('port-security')
|
||||||
@ -669,3 +681,100 @@ class FloatingIpStackFixture(heat.HeatStackFixture):
|
|||||||
if self._neutron_client is None:
|
if self._neutron_client is None:
|
||||||
self._neutron_client = self.router_stack.neutron_client
|
self._neutron_client = self.router_stack.neutron_client
|
||||||
return self._neutron_client
|
return self._neutron_client
|
||||||
|
|
||||||
|
|
||||||
|
class RouterInterfaceStackFixture(heat.HeatStackFixture):
|
||||||
|
|
||||||
|
#: Heat template file
|
||||||
|
template = _hot.heat_template_file('neutron/router_interface.yaml')
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
stack_name: str = None,
|
||||||
|
router: neutron.RouterIdType = None,
|
||||||
|
network: neutron.NetworkIdType = None,
|
||||||
|
subnet: neutron.SubnetIdType = None,
|
||||||
|
neutron_client: neutron.NeutronClientType = None):
|
||||||
|
self._router = router
|
||||||
|
self._network = network
|
||||||
|
self._subnet = subnet
|
||||||
|
self._neutron_client = neutron_client
|
||||||
|
super().__init__(stack_name=stack_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def router(self) -> str:
|
||||||
|
if self._router is None:
|
||||||
|
self._router = get_router_id()
|
||||||
|
return neutron.get_router_id(self._router)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def router_details(self) -> neutron.RouterType:
|
||||||
|
if self._router is None or isinstance(self._router, str):
|
||||||
|
self._router = neutron.get_router(self.router,
|
||||||
|
client=self.neutron_client)
|
||||||
|
return self._router
|
||||||
|
|
||||||
|
@property
|
||||||
|
def network(self) -> str:
|
||||||
|
if self._network is None:
|
||||||
|
subnet = self.subnet_details
|
||||||
|
if subnet is None:
|
||||||
|
raise ValueError('Must specify at least network or subnet')
|
||||||
|
self._network = subnet['network_id']
|
||||||
|
return neutron.get_network_id(self._network)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def network_details(self) -> neutron.NetworkType:
|
||||||
|
if self._network is None or isinstance(self._network, str):
|
||||||
|
self._network = neutron.get_network(self.network,
|
||||||
|
client=self.neutron_client)
|
||||||
|
return self._network
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subnet(self) -> typing.Optional[str]:
|
||||||
|
if self._subnet is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return neutron.get_subnet_id(self._subnet)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subnet_details(self) -> typing.Optional[neutron.SubnetType]:
|
||||||
|
if self._subnet is None:
|
||||||
|
return None
|
||||||
|
if isinstance(self._subnet, str):
|
||||||
|
self._subnet = neutron.get_subnet(self._subnet,
|
||||||
|
client=self.neutron_client)
|
||||||
|
return self._subnet
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_subnet(self) -> bool:
|
||||||
|
return self.subnet is not None
|
||||||
|
|
||||||
|
def setup_stack_name(self) -> str:
|
||||||
|
if self.stack_name is None:
|
||||||
|
if self.has_subnet:
|
||||||
|
self.stack_name = f"{self.fixture_name}-{self.subnet}"
|
||||||
|
else:
|
||||||
|
self.stack_name = f"{self.fixture_name}-{self.network}"
|
||||||
|
return self.stack_name
|
||||||
|
|
||||||
|
_port: typing.Optional[neutron.PortIdType] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_details(self) -> neutron.PortType:
|
||||||
|
if self._port is None:
|
||||||
|
port_id = self.port_id
|
||||||
|
if port_id is None:
|
||||||
|
self._port = neutron.find_port(network_id=self.network_id,
|
||||||
|
device_id=self.router_id,
|
||||||
|
client=self.neutron_client)
|
||||||
|
else:
|
||||||
|
self._port = neutron.get_port(port_id,
|
||||||
|
client=self.neutron_client)
|
||||||
|
assert isinstance(self._port, dict)
|
||||||
|
return self._port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def neutron_client(self) -> neutron.NeutronClientType:
|
||||||
|
if self._neutron_client is None:
|
||||||
|
self._neutron_client = neutron.get_neutron_client()
|
||||||
|
return self._neutron_client
|
||||||
|
103
tobiko/openstack/stacks/neutron/router_interface.yaml
Normal file
103
tobiko/openstack/stacks/neutron/router_interface.yaml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
heat_template_version: newton
|
||||||
|
|
||||||
|
|
||||||
|
description: Add a router interface on given network
|
||||||
|
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
router:
|
||||||
|
description: Router to add port to
|
||||||
|
type: string
|
||||||
|
constraints:
|
||||||
|
- custom_constraint: neutron.router
|
||||||
|
|
||||||
|
network:
|
||||||
|
description: Network where to create router port
|
||||||
|
type: string
|
||||||
|
constraints:
|
||||||
|
- custom_constraint: neutron.network
|
||||||
|
|
||||||
|
has_subnet:
|
||||||
|
description: Whenever to specify subnet for router interface
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
subnet:
|
||||||
|
description: Subnet to be used for router interface
|
||||||
|
type: string
|
||||||
|
default: '<NO-SUBNET>'
|
||||||
|
|
||||||
|
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
has_subnet:
|
||||||
|
{get_param: has_subnet}
|
||||||
|
|
||||||
|
create_port:
|
||||||
|
not: {get_param: has_subnet}
|
||||||
|
|
||||||
|
|
||||||
|
resources:
|
||||||
|
|
||||||
|
_router:
|
||||||
|
type: OS::Neutron::Router
|
||||||
|
external_id: {get_param: router}
|
||||||
|
|
||||||
|
_network:
|
||||||
|
type: OS::Neutron::Net
|
||||||
|
external_id: {get_param: network}
|
||||||
|
|
||||||
|
_subnet:
|
||||||
|
type: OS::Neutron::Subnet
|
||||||
|
condition: has_subnet
|
||||||
|
external_id: {get_param: subnet}
|
||||||
|
|
||||||
|
_router_interface_subnet:
|
||||||
|
type: OS::Neutron::RouterInterface
|
||||||
|
condition: has_subnet
|
||||||
|
properties:
|
||||||
|
router: {get_resource: _router}
|
||||||
|
subnet: {get_resource: _subnet}
|
||||||
|
|
||||||
|
_port:
|
||||||
|
type: OS::Neutron::Port
|
||||||
|
condition: create_port
|
||||||
|
properties:
|
||||||
|
network: {get_resource: _network}
|
||||||
|
|
||||||
|
_router_interface_port:
|
||||||
|
type: OS::Neutron::RouterInterface
|
||||||
|
condition: create_port
|
||||||
|
properties:
|
||||||
|
router: {get_resource: _router}
|
||||||
|
port: {get_resource: _port}
|
||||||
|
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
|
||||||
|
router_id:
|
||||||
|
description: Router ID
|
||||||
|
value: {get_resource: _router}
|
||||||
|
|
||||||
|
network_id:
|
||||||
|
description: Tenant network ID
|
||||||
|
value: {get_resource: _network}
|
||||||
|
|
||||||
|
subnet_id:
|
||||||
|
description: Tenant subnet ID
|
||||||
|
condition: has_subnet
|
||||||
|
value: {get_resource: _subnet}
|
||||||
|
|
||||||
|
port_id:
|
||||||
|
description: Router tenant port ID
|
||||||
|
condition: create_port
|
||||||
|
value: {get_resource: _port}
|
||||||
|
|
||||||
|
external_network_id:
|
||||||
|
description: External router network ID
|
||||||
|
value: {get_attr: [_router, external_gateway_info, network_id]}
|
||||||
|
|
||||||
|
mtu:
|
||||||
|
description: Network MTU value (integer)
|
||||||
|
value: {get_attr: [_network, mtu]}
|
@ -15,6 +15,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
import tobiko
|
import tobiko
|
||||||
@ -23,6 +24,9 @@ from tobiko.openstack import neutron
|
|||||||
from tobiko.openstack import stacks
|
from tobiko.openstack import stacks
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@keystone.skip_unless_has_keystone_credentials()
|
@keystone.skip_unless_has_keystone_credentials()
|
||||||
class NetworkTest(testtools.TestCase):
|
class NetworkTest(testtools.TestCase):
|
||||||
"""Tests network creation"""
|
"""Tests network creation"""
|
||||||
@ -140,3 +144,78 @@ class RouterTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_has_router(self):
|
def test_has_router(self):
|
||||||
self.assertTrue(stacks.has_router())
|
self.assertTrue(stacks.has_router())
|
||||||
|
|
||||||
|
|
||||||
|
class RouterInterfaceTestRouter(stacks.RouterStackFixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RouterInterfaceTestNetwork(stacks.NetworkStackFixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@keystone.skip_unless_has_keystone_credentials()
|
||||||
|
@stacks.skip_unless_has_floating_network
|
||||||
|
class RouterInterfaceTest(testtools.TestCase):
|
||||||
|
|
||||||
|
router_stack = tobiko.required_fixture(RouterInterfaceTestRouter)
|
||||||
|
network_stack = tobiko.required_fixture(RouterInterfaceTestNetwork)
|
||||||
|
|
||||||
|
required_fixtures = [router_stack, network_stack]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
for fixture in cls.required_fixtures:
|
||||||
|
try:
|
||||||
|
tobiko.cleanup_fixture(fixture.fixture)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception(f'Error cleaning up fixture: {fixture.fixture}')
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_subnet(self,
|
||||||
|
ip_version=4):
|
||||||
|
network = neutron.create_network()
|
||||||
|
subnet = neutron.create_subnet(network=network,
|
||||||
|
ip_version=ip_version)
|
||||||
|
self._test_ensure_router(subnet=subnet)
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_ipv6_subnet(self):
|
||||||
|
self.test_ensure_router_interface_with_subnet(ip_version=6)
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_routed_ipv4_subnet(self):
|
||||||
|
self._test_ensure_router(subnet=self.network_stack.ipv4_subnet_id)
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_routed_ipv6_subnet(self):
|
||||||
|
self._test_ensure_router(subnet=self.network_stack.ipv6_subnet_id)
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_network(self):
|
||||||
|
network = neutron.create_network()
|
||||||
|
neutron.create_subnet(network=network)
|
||||||
|
self._test_ensure_router(network=network)
|
||||||
|
|
||||||
|
def test_ensure_router_interface_with_routed_network(self):
|
||||||
|
self._test_ensure_router(network=self.network_stack.network_id)
|
||||||
|
|
||||||
|
def _test_ensure_router(self,
|
||||||
|
network: neutron.NetworkIdType = None,
|
||||||
|
subnet: neutron.SubnetIdType = None):
|
||||||
|
router = self.router_stack.router_details
|
||||||
|
self.assertRaises(tobiko.ObjectNotFound,
|
||||||
|
neutron.find_port,
|
||||||
|
device=router,
|
||||||
|
subnet=subnet,
|
||||||
|
network=network)
|
||||||
|
port = stacks.ensure_router_interface(router=router,
|
||||||
|
network=network,
|
||||||
|
subnet=subnet,
|
||||||
|
add_cleanup=True)
|
||||||
|
self.assertEqual(router['id'], port['device_id'])
|
||||||
|
self.assertEqual(neutron.find_port(device=router,
|
||||||
|
network=network,
|
||||||
|
subnet=subnet),
|
||||||
|
port)
|
||||||
|
self.assertEqual(port,
|
||||||
|
stacks.ensure_router_interface(router=router,
|
||||||
|
network=network,
|
||||||
|
subnet=subnet,
|
||||||
|
add_cleanup=True))
|
||||||
|
return port
|
||||||
|
Loading…
Reference in New Issue
Block a user