Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Commit

Permalink
Tripleo routed networks ironic inspector, and Undercloud
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hjensas committed Feb 6, 2018
1 parent 983c2be commit 46a5df2
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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')}"
Expand Down
112 changes: 109 additions & 3 deletions instack_undercloud/tests/test_undercloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ def test_configure_ssh_keys_missing(self, mock_eui, _):
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')
Expand Down Expand Up @@ -1430,6 +1430,8 @@ def test_post_config_mistral_with_tags(self, mock_create, mock_cmce,
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()
Expand All @@ -1445,12 +1447,36 @@ def test_network_create(self):
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',
Expand All @@ -1459,12 +1485,12 @@ def test_subnet_create(self):
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()
Expand All @@ -1478,6 +1504,86 @@ def test_subnet_update(self):
'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')
Expand Down
119 changes: 102 additions & 17 deletions instack_undercloud/undercloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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']:
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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')
Expand All @@ -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)

# 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)
for name in CONF.subnets:
s = CONF.get(name)

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):
Expand Down

0 comments on commit 46a5df2

Please sign in to comment.