Tripleo routed networks ironic inspector, and Undercloud
* Enable the neutron segments service_plugin for routed provider networks. * Update controlplane network code to create segments for each subnet. A number of options related to ctlplane network is deprecated. More details in release notes. Implements: blueprint tripleo-routed-networks-ironic-inspector Implements: blueprint tripleo-routed-networks-deployment Depends-On: I33804bfd105a13c25d6057e8414e09957939e8af Change-Id: I4b384bab2af9f6ba07a137a37f4098a00ce18bc0
This commit is contained in:
parent
983c2be88e
commit
46a5df2625
|
@ -275,6 +275,7 @@ neutron::keystone::auth::admin_url: {{UNDERCLOUD_ENDPOINT_NEUTRON_ADMIN}}
|
|||
neutron::keystone::auth::password: {{UNDERCLOUD_NEUTRON_PASSWORD}}
|
||||
neutron::keystone::auth::region: "%{hiera('keystone_region')}"
|
||||
neutron::plugins::ml2::extension_drivers: 'port_security'
|
||||
neutron::service_plugins: ['segments']
|
||||
|
||||
# Ceilometer
|
||||
ceilometer::debug: "%{hiera('debug')}"
|
||||
|
|
|
@ -907,7 +907,7 @@ class TestConfigureSshKeys(base.BaseTestCase):
|
|||
self._test_configure_ssh_keys(mock_eui, False)
|
||||
|
||||
|
||||
class TestPostConfig(base.BaseTestCase):
|
||||
class TestPostConfig(BaseTestCase):
|
||||
@mock.patch('os_client_config.make_client')
|
||||
@mock.patch('instack_undercloud.undercloud._migrate_to_convergence')
|
||||
@mock.patch('instack_undercloud.undercloud._ensure_node_resource_classes')
|
||||
|
@ -1430,6 +1430,8 @@ class TestPostConfig(base.BaseTestCase):
|
|||
def _neutron_mocks(self):
|
||||
mock_sdk = mock.MagicMock()
|
||||
mock_sdk.network.create_network = mock.Mock()
|
||||
mock_sdk.network.create_segment = mock.Mock()
|
||||
mock_sdk.network.update_segment = mock.Mock()
|
||||
mock_sdk.network.delete_segment = mock.Mock()
|
||||
mock_sdk.network.create_subnet = mock.Mock()
|
||||
mock_sdk.network.update_subnet = mock.Mock()
|
||||
|
@ -1445,12 +1447,36 @@ class TestPostConfig(base.BaseTestCase):
|
|||
name='ctlplane', provider_network_type='flat',
|
||||
provider_physical_network='ctlplane')
|
||||
|
||||
def test_delete_default_segment(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_sdk.network.networks.return_value = iter([])
|
||||
segment_mock = mock.Mock()
|
||||
mock_sdk.network.segments.return_value = iter([segment_mock])
|
||||
undercloud._ensure_neutron_network(mock_sdk)
|
||||
mock_sdk.network.delete_segment.assert_called_with(
|
||||
segment_mock.id)
|
||||
|
||||
def test_network_exists(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_sdk.network.networks.return_value = iter(['ctlplane'])
|
||||
undercloud._ensure_neutron_network(mock_sdk)
|
||||
mock_sdk.network.create_network.assert_not_called()
|
||||
|
||||
def test_segment_create(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
undercloud._neutron_segment_create(mock_sdk, 'ctlplane-subnet',
|
||||
'network_id', 'ctlplane')
|
||||
mock_sdk.network.create_segment.assert_called_with(
|
||||
name='ctlplane-subnet', network_id='network_id',
|
||||
physical_network='ctlplane', network_type='flat')
|
||||
|
||||
def test_segment_update(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
undercloud._neutron_segment_update(mock_sdk,
|
||||
'network_id', 'ctlplane-subnet')
|
||||
mock_sdk.network.update_segment.assert_called_with(
|
||||
'network_id', name='ctlplane-subnet')
|
||||
|
||||
def test_subnet_create(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
|
@ -1459,12 +1485,12 @@ class TestPostConfig(base.BaseTestCase):
|
|||
undercloud._neutron_subnet_create(mock_sdk, 'network_id',
|
||||
'192.168.24.0/24', '192.168.24.1',
|
||||
host_routes, allocation_pool,
|
||||
'ctlplane-subnet')
|
||||
'ctlplane-subnet', 'segment_id')
|
||||
mock_sdk.network.create_subnet.assert_called_with(
|
||||
name='ctlplane-subnet', cidr='192.168.24.0/24',
|
||||
gateway_ip='192.168.24.1', host_routes=host_routes, enable_dhcp=True,
|
||||
ip_version='4', allocation_pools=allocation_pool,
|
||||
network_id='network_id')
|
||||
network_id='network_id', segment_id='segment_id')
|
||||
|
||||
def test_subnet_update(self):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
|
@ -1478,6 +1504,86 @@ class TestPostConfig(base.BaseTestCase):
|
|||
'subnet_id', name='ctlplane-subnet', gateway_ip='192.168.24.1',
|
||||
host_routes=host_routes, allocation_pools=allocation_pool)
|
||||
|
||||
@mock.patch('instack_undercloud.undercloud._neutron_subnet_update')
|
||||
@mock.patch('instack_undercloud.undercloud._get_subnet')
|
||||
def test_no_neutron_segments_if_pre_segments_undercloud(
|
||||
self, mock_get_subnet, mock_neutron_subnet_update):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_subnet = mock.Mock()
|
||||
mock_subnet.segment_id = None
|
||||
mock_get_subnet.return_value = mock_subnet
|
||||
undercloud._config_neutron_segments_and_subnets(mock_sdk,
|
||||
'ctlplane_id')
|
||||
mock_sdk.network.create_segment.assert_not_called()
|
||||
mock_sdk.network.update_segment.assert_not_called()
|
||||
mock_neutron_subnet_update.called_once()
|
||||
|
||||
@mock.patch('instack_undercloud.undercloud._neutron_segment_create')
|
||||
@mock.patch('instack_undercloud.undercloud._neutron_subnet_create')
|
||||
@mock.patch('instack_undercloud.undercloud._get_segment')
|
||||
@mock.patch('instack_undercloud.undercloud._get_subnet')
|
||||
def test_segment_and_subnet_create(self, mock_get_subnet, mock_get_segment,
|
||||
mock_neutron_subnet_create,
|
||||
mock_neutron_segment_create):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_get_subnet.return_value = None
|
||||
mock_get_segment.return_value = None
|
||||
undercloud._config_neutron_segments_and_subnets(mock_sdk,
|
||||
'ctlplane_id')
|
||||
mock_neutron_segment_create.assert_called_with(
|
||||
mock_sdk, 'ctlplane-subnet', 'ctlplane_id', 'ctlplane')
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
'nexthop': '192.168.24.1'}]
|
||||
allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
|
||||
mock_neutron_subnet_create.assert_called_with(
|
||||
mock_sdk, 'ctlplane_id', '192.168.24.0/24', '192.168.24.1',
|
||||
host_routes, allocation_pool, 'ctlplane-subnet',
|
||||
mock_neutron_segment_create().id)
|
||||
|
||||
@mock.patch('instack_undercloud.undercloud._neutron_segment_update')
|
||||
@mock.patch('instack_undercloud.undercloud._neutron_subnet_update')
|
||||
@mock.patch('instack_undercloud.undercloud._get_segment')
|
||||
@mock.patch('instack_undercloud.undercloud._get_subnet')
|
||||
def test_segment_and_subnet_update(self, mock_get_subnet, mock_get_segment,
|
||||
mock_neutron_subnet_update,
|
||||
mock_neutron_segment_update):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_subnet = mock.Mock()
|
||||
mock_subnet.id = 'subnet_id'
|
||||
mock_subnet.segment_id = 'segment_id'
|
||||
mock_get_subnet.return_value = mock_subnet
|
||||
mock_segment = mock.Mock()
|
||||
mock_get_segment.return_value = mock_segment
|
||||
mock_segment.id = 'segment_id'
|
||||
undercloud._config_neutron_segments_and_subnets(mock_sdk,
|
||||
'ctlplane_id')
|
||||
mock_neutron_segment_update.assert_called_with(
|
||||
mock_sdk, mock_subnet.segment_id, 'ctlplane-subnet')
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
'nexthop': '192.168.24.1'}]
|
||||
allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
|
||||
mock_neutron_subnet_update.assert_called_with(
|
||||
mock_sdk, 'subnet_id', '192.168.24.1', host_routes,
|
||||
allocation_pool, 'ctlplane-subnet')
|
||||
|
||||
@mock.patch('instack_undercloud.undercloud._get_segment')
|
||||
@mock.patch('instack_undercloud.undercloud._get_subnet')
|
||||
def test_local_subnet_cidr_conflict(self, mock_get_subnet,
|
||||
mock_get_segment):
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_sdk = self._neutron_mocks()
|
||||
mock_subnet = mock.Mock()
|
||||
mock_subnet.id = 'subnet_id'
|
||||
mock_subnet.segment_id = 'existing_segment_id'
|
||||
mock_get_subnet.return_value = mock_subnet
|
||||
mock_segment = mock.Mock()
|
||||
mock_get_segment.return_value = mock_segment
|
||||
mock_segment.id = 'segment_id'
|
||||
self.assertRaises(
|
||||
RuntimeError,
|
||||
undercloud._config_neutron_segments_and_subnets, [mock_sdk],
|
||||
['ctlplane_id'])
|
||||
|
||||
|
||||
class TestUpgradeFact(base.BaseTestCase):
|
||||
@mock.patch('instack_undercloud.undercloud._run_command')
|
||||
|
|
|
@ -2033,6 +2033,11 @@ def _ensure_neutron_network(sdk):
|
|||
name=PHYSICAL_NETWORK, provider_network_type='flat',
|
||||
provider_physical_network=PHYSICAL_NETWORK)
|
||||
LOG.info("Network created %s", network)
|
||||
# (hjensas) Delete the default segment, we create a new segment
|
||||
# per subnet later.
|
||||
segments = list(sdk.network.segments(network=network.id))
|
||||
sdk.network.delete_segment(segments[0].id)
|
||||
LOG.info("Default segment on network %s deleted.", network.name)
|
||||
else:
|
||||
LOG.info("Not creating %s network, because it already exists.",
|
||||
PHYSICAL_NETWORK)
|
||||
|
@ -2045,7 +2050,7 @@ def _ensure_neutron_network(sdk):
|
|||
|
||||
|
||||
def _neutron_subnet_create(sdk, network_id, cidr, gateway, host_routes,
|
||||
allocation_pool, name):
|
||||
allocation_pool, name, segment_id):
|
||||
try:
|
||||
# DHCP_START contains a ":" then assume a IPv6 subnet
|
||||
if ':' in allocation_pool[0]['start']:
|
||||
|
@ -2060,7 +2065,8 @@ def _neutron_subnet_create(sdk, network_id, cidr, gateway, host_routes,
|
|||
ipv6_address_mode='dhcpv6-stateless',
|
||||
ipv6_ra_mode='dhcpv6-stateless',
|
||||
allocation_pools=allocation_pool,
|
||||
network_id=network_id)
|
||||
network_id=network_id,
|
||||
segment_id=segment_id)
|
||||
else:
|
||||
subnet = sdk.network.create_subnet(
|
||||
name=name,
|
||||
|
@ -2070,7 +2076,8 @@ def _neutron_subnet_create(sdk, network_id, cidr, gateway, host_routes,
|
|||
enable_dhcp=True,
|
||||
ip_version='4',
|
||||
allocation_pools=allocation_pool,
|
||||
network_id=network_id)
|
||||
network_id=network_id,
|
||||
segment_id=segment_id)
|
||||
LOG.info("Subnet created %s", subnet)
|
||||
except Exception as e:
|
||||
LOG.error("Create subnet %s failed: %s", name, e)
|
||||
|
@ -2097,6 +2104,30 @@ def _neutron_subnet_update(sdk, subnet_id, gateway, host_routes,
|
|||
raise
|
||||
|
||||
|
||||
def _neutron_segment_create(sdk, name, network_id, phynet):
|
||||
try:
|
||||
segment = sdk.network.create_segment(
|
||||
name=name,
|
||||
network_id=network_id,
|
||||
physical_network=phynet,
|
||||
network_type='flat')
|
||||
LOG.info("Neutron Segment created %s", segment)
|
||||
except Exception as e:
|
||||
LOG.info("Neutron Segment %s create failed %s", name, e)
|
||||
raise
|
||||
|
||||
return segment
|
||||
|
||||
|
||||
def _neutron_segment_update(sdk, segment_id, name):
|
||||
try:
|
||||
segment = sdk.network.update_segment(segment_id, name=name)
|
||||
LOG.info("Neutron Segment updated %s", segment)
|
||||
except Exception as e:
|
||||
LOG.info("Neutron Segment %s update failed %s", name, e)
|
||||
raise
|
||||
|
||||
|
||||
def _ensure_neutron_router(sdk, name, subnet_id):
|
||||
try:
|
||||
router = sdk.network.create_router(name=name, admin_state_up='true')
|
||||
|
@ -2115,25 +2146,79 @@ def _get_subnet(sdk, cidr, network_id):
|
|||
return False if not subnet else subnet[0]
|
||||
|
||||
|
||||
def _get_segment(sdk, phy, network_id):
|
||||
try:
|
||||
segment = list(sdk.network.segments(physical_network=phy,
|
||||
network_id=network_id))
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
return False if not segment else segment[0]
|
||||
|
||||
|
||||
def _config_neutron_segments_and_subnets(sdk, ctlplane_id):
|
||||
s = CONF.get(CONF.local_subnet)
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
'nexthop': str(netaddr.IPNetwork(CONF.local_ip).ip)}]
|
||||
allocation_pool = [{'start': s.dhcp_start, 'end': s.dhcp_end}]
|
||||
|
||||
subnet = _get_subnet(sdk, CONF.network_cidr, ctlplane_id)
|
||||
if subnet:
|
||||
subnet = _get_subnet(sdk, s.cidr, ctlplane_id)
|
||||
if subnet and not subnet.segment_id:
|
||||
LOG.warn("Local subnet %s already exists and is not associated with a "
|
||||
"network segment. Any additional subnets will be ignored.",
|
||||
CONF.local_subnet)
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
'nexthop': str(netaddr.IPNetwork(CONF.local_ip).ip)}]
|
||||
allocation_pool = [{'start': s.dhcp_start, 'end': s.dhcp_end}]
|
||||
_neutron_subnet_update(sdk, subnet.id, s.gateway, host_routes,
|
||||
allocation_pool, CONF.local_subnet)
|
||||
# If the subnet is IPv6 we need to start a router so that router
|
||||
# advertisments are sent out for stateless IP addressing to work.
|
||||
if ':' in s.dhcp_start:
|
||||
_ensure_neutron_router(sdk, CONF.local_subnet, subnet.id)
|
||||
else:
|
||||
subnet = _neutron_subnet_create(sdk, ctlplane_id, s.cidr, s.gateway,
|
||||
host_routes, allocation_pool,
|
||||
CONF.local_subnet)
|
||||
for name in CONF.subnets:
|
||||
s = CONF.get(name)
|
||||
|
||||
# If the subnet is IPv6 we need to start a router so that router
|
||||
# advertisments are sent out for stateless IP addressing to work.
|
||||
if ':' in CONF.dhcp_start:
|
||||
_ensure_neutron_router(sdk, CONF.local_subnet, subnet.id)
|
||||
phynet = name
|
||||
if name == CONF.local_subnet:
|
||||
phynet = PHYSICAL_NETWORK
|
||||
|
||||
metadata_nexthop = s.gateway
|
||||
if str(netaddr.IPNetwork(CONF.local_ip).ip) in s.cidr:
|
||||
metadata_nexthop = str(netaddr.IPNetwork(CONF.local_ip).ip)
|
||||
|
||||
host_routes = [{'destination': '169.254.169.254/32',
|
||||
'nexthop': metadata_nexthop}]
|
||||
allocation_pool = [{'start': s.dhcp_start, 'end': s.dhcp_end}]
|
||||
|
||||
subnet = _get_subnet(sdk, s.cidr, ctlplane_id)
|
||||
segment = _get_segment(sdk, phynet, ctlplane_id)
|
||||
|
||||
if name == CONF.local_subnet:
|
||||
if ((subnet and not segment) or
|
||||
(subnet and segment and subnet.segment_id != segment.id)):
|
||||
LOG.error(
|
||||
'The cidr: %s of the local subnet is already used in '
|
||||
'subnet: %s associated with segment_id: %s.' %
|
||||
(s.cidr, subnet.id, subnet.segment_id))
|
||||
raise RuntimeError('Local subnet cidr already associated.')
|
||||
|
||||
if subnet:
|
||||
_neutron_segment_update(sdk, subnet.segment_id, name)
|
||||
_neutron_subnet_update(sdk, subnet.id, s.gateway, host_routes,
|
||||
allocation_pool, name)
|
||||
else:
|
||||
if segment:
|
||||
_neutron_segment_update(sdk, segment.id, name)
|
||||
else:
|
||||
segment = _neutron_segment_create(sdk, name,
|
||||
ctlplane_id, phynet)
|
||||
subnet = _neutron_subnet_create(sdk, ctlplane_id, s.cidr,
|
||||
s.gateway, host_routes,
|
||||
allocation_pool, name,
|
||||
segment.id)
|
||||
|
||||
# If the subnet is IPv6 we need to start a router so that router
|
||||
# advertisments are sent out for stateless IP addressing to work.
|
||||
if ':' in s.dhcp_start:
|
||||
_ensure_neutron_router(sdk, name, subnet.id)
|
||||
|
||||
|
||||
def _handle_upgrade_fact(upgrade=False):
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
prelude: >
|
||||
With support for routed networks, several options are deprecated and the
|
||||
way undercloud networking is defined in the configuration file has several
|
||||
changes. Please refer to the **Deprecation Notes** and **Upgrade notes**
|
||||
section for details.
|
||||
features:
|
||||
- Routed networks support adds the ability to configure Ironic Inspector
|
||||
and Neutron provisioning network in the undercloud to enable provisioning
|
||||
of nodes via DHCP-relay to the undercloud from remote routed network
|
||||
segments.
|
||||
upgrade:
|
||||
- |
|
||||
With support for routed network segments, several options are deprecated
|
||||
and the way undercloud networking is defined in the configuration file has
|
||||
several changes.
|
||||
|
||||
**New option:** ``subnets`` A list of subnets. One entry for each routed
|
||||
network segment used for provisioning and introspection. For each network
|
||||
segment a section/group needs to be added to the configuration file
|
||||
specifying the following subnet options:
|
||||
|
||||
====================== ================================================
|
||||
option Description
|
||||
====================== ================================================
|
||||
``cidr`` Network CIDR for the subnet.
|
||||
``dhcp_start`` Start of DHCP allocation range for PXE and DHCP.
|
||||
``dhcp_end`` End of DHCP allocation range for PXE and DHCP.
|
||||
``inspection_iprange`` Temporary IP range that will be given to nodes
|
||||
during the inspection process.
|
||||
``gateway`` Network(subnet) gateway/router.
|
||||
``masquerade`` (Boolean) If ``True`` the undercloud will
|
||||
masquerade this network for external access.
|
||||
====================== ================================================
|
||||
|
||||
**New option:** ``local_subnet`` The name of the local subnet, where the
|
||||
PXE boot and DHCP interfaces for overcloud instances is located. The IP
|
||||
address of the local_ip/local_interface should reside in this subnet.
|
||||
|
||||
.. Note:: Upgrade with migration to routed networks support is not
|
||||
possible.
|
||||
|
||||
Routed networks use the neutron segments service_plugin, this
|
||||
plugin adds functionality that allows subnet to be associated
|
||||
with a network segment. It is currently not possible to add
|
||||
segment association to an existing subnet, because of this we
|
||||
cannot add segment association to the existing ctlplane subnet
|
||||
on the upgraded undercloud. The existing ctlplane network and
|
||||
subnet will still be in place after an upgrade and the upgraded
|
||||
undercloud can continue to manage the existing overcloud.
|
||||
|
||||
The following example shows what changes to make to the configuration to
|
||||
move to the new model.
|
||||
|
||||
Replace usage of deprecated options::
|
||||
|
||||
[DEFAULT]
|
||||
network_gateway = 192.168.24.1
|
||||
network_cidr = 192.168.24.0/24
|
||||
dhcp_start = 192.168.24.5
|
||||
dhcp_end = 192.168.24.24
|
||||
inspection_iprange = 192.168.24.100,192.168.24.120
|
||||
masquerade_network = 192.168.24.0/24
|
||||
|
||||
replace with::
|
||||
|
||||
[DEFAULT]
|
||||
subnets = subnet0
|
||||
local_subnet = subnet0
|
||||
|
||||
[subnet0]
|
||||
cidr = 192.168.24.0/24
|
||||
dhcp_start = 192.168.24.5
|
||||
dhcp_end = 192.168.24.24
|
||||
inspection_iprange = 192.168.24.100,192.168.24.120
|
||||
gateway = 192.168.24.1
|
||||
masquerade = True
|
||||
deprecations:
|
||||
- With support for routed networks/subnets the ``network_gateway`` option in
|
||||
the ``[DEFAULT]`` section is deprecated. Moved to per-subnet options
|
||||
group.
|
||||
- With support for routed networks/subnets the ``network_cidr`` option in
|
||||
the ``[DEFAULT]`` section is deprecated. Moved to per-subnet options
|
||||
group.
|
||||
- With support for routed networks/subnets the ``dhcp_start`` and
|
||||
``dhcp_end`` options in the ``[DEFAULT]`` section are deprecated. Moved to
|
||||
per-subnet options group.
|
||||
- With support for routed networks/subnets the ``inspection_iprange`` option
|
||||
in the ``[DEFAULT]`` section is deprecated. Moved to per-subnet options
|
||||
group.
|
||||
- With support for routed networks/subnets the ``masquerade_network``
|
||||
option in the ``[DEFAULT]`` section is deprecated. Use the boolean option
|
||||
in each subnet group.
|
Loading…
Reference in New Issue