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:
Harald Jensas 2017-02-23 18:56:49 +01:00 committed by Harald Jensas
parent 983c2be88e
commit 46a5df2625
4 changed files with 304 additions and 19 deletions

View File

@ -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')}"

View File

@ -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')

View File

@ -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):

View File

@ -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.