Move ctlplane network/subnet setup to python

For routed networks deployment we need to create multiple
subnets on the controlplane network. The logic to implement
this is more involved and so doing this in python makes sense.

This change moves the current network config from
98-undercloud-setup to a python implementation.

Change-Id: I0168991a0fbd9dacf708ba2bdbc13248414affbe
Implements: blueprint tripleo-routed-networks-deployment
This commit is contained in:
Harald Jensas 2018-01-13 13:00:39 +01:00 committed by Harald Jensås
parent 5dd4e93f59
commit a7d289109d
3 changed files with 181 additions and 76 deletions

View File

@ -37,81 +37,6 @@ if [ -e /usr/sbin/getenforce ]; then
fi
fi
UNDERCLOUD_IP=$(os-apply-config --key local-ip --type netaddress)
export UNDERCLOUD_IP
DHCP_START=$(os-apply-config --key neutron.dhcp_start --type netaddress)
DHCP_END=$(os-apply-config --key neutron.dhcp_end --type netaddress)
NETWORK_CIDR=$(os-apply-config --key neutron.network_cidr --type raw)
NETWORK_GATEWAY=$(os-apply-config --key neutron.network_gateway --type netaddress)
METADATA_SERVER=$UNDERCLOUD_IP
PHYSICAL_NETWORK=ctlplane
# DHCP_START contains a ":" then assume a IPv6 subnet
SUBNET_VERSION_STRING="--ip-version 4"
SUBNET_ROUTE_STRING="--host-route destination=169.254.169.254/32,nexthop=$METADATA_SERVER"
if [[ $DHCP_START =~ : ]] ; then
SUBNET_VERSION_STRING="--ip-version 6 --ipv6-address-mode dhcpv6-stateless --ipv6-ra-mode dhcpv6-stateless"
SUBNET_ROUTE_STRING=""
fi
net_create=1
ctlplane_id=$(neutron net-list -f csv -c id -c name --quote none | tail -n +2 | grep ctlplane | cut -d, -f 1)
subnet_ids=$(neutron subnet-list -f csv -c id --quote none | tail -n +2)
subnet_id=
for subnet_id in $subnet_ids; do
network_id=$(neutron subnet-show -f value -c network_id $subnet_id)
if [ "$network_id" = "$ctlplane_id" ]; then
break
fi
done
if [ -n "$subnet_id" ]; then
cidr=$(neutron subnet-show $subnet_id -f value -c cidr)
# If the cidr's are equal, we can get by with just a network update
if [ "$cidr" = "$NETWORK_CIDR" ]; then
net_create=0
neutron subnet-update $subnet_id \
--allocation-pool start=$DHCP_START,end=$DHCP_END \
--gateway $NETWORK_GATEWAY $SUBNET_ROUTE_STRING
else
echo "New cidr $NETWORK_CIDR does not equal old cidr $cidr"
echo "Will attempt to delete and recreate subnet $subnet_id"
fi
fi
if [ "$net_create" -eq "1" ]; then
# Delete the route, subnet and network to make sure it doesn't already exist
if neutron router-show ctlplane-router ; then
neutron router-interface-delete ctlplane-router ctlplane-subnet
neutron router-delete ctlplane-router
fi
if neutron subnet-list | grep start; then
neutron subnet-delete $(neutron subnet-list | grep start | awk '{print $2}')
fi
if neutron net-show ctlplane; then
neutron net-delete ctlplane
fi
neutron net-create ctlplane \
--provider:network_type flat \
--provider:physical_network ctlplane
neutron subnet-create --name ctlplane-subnet \
--allocation-pool start=$DHCP_START,end=$DHCP_END \
--gateway $NETWORK_GATEWAY \
$SUBNET_VERSION_STRING $SUBNET_ROUTE_STRING \
ctlplane $NETWORK_CIDR
# If ctlplane-subnet is IPv6 we need to start a router so the router advertisments are sent out
# for statless IP addressing to work.
if [[ $DHCP_START =~ : ]] ; then
neutron router-create ctlplane-router
neutron router-interface-add ctlplane-router ctlplane-subnet
fi
fi
# Disable nova quotas
openstack quota set --cores -1 --instances -1 --ram -1 $(openstack project show admin | awk '$2=="id" {print $4}')

View File

@ -773,6 +773,9 @@ class TestPostConfig(base.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')
@mock.patch(
'instack_undercloud.undercloud._config_neutron_segments_and_subnets')
@mock.patch('instack_undercloud.undercloud._ensure_neutron_network')
@mock.patch('instack_undercloud.undercloud._member_role_exists')
@mock.patch('instack_undercloud.undercloud._get_session')
@mock.patch('ironicclient.client.get_client', autospec=True)
@ -790,6 +793,8 @@ class TestPostConfig(base.BaseTestCase):
mock_copy_stackrc, mock_delete, mock_mistral_client,
mock_swift_client, mock_nova_client, mock_ir_client,
mock_get_session, mock_member_role_exists,
mock_ensure_neutron_network,
mock_config_neutron_segments_and_subnets,
mock_resource_classes, mock_migrate_to_convergence,
mock_make_client):
instack_env = {
@ -1284,6 +1289,57 @@ class TestPostConfig(base.BaseTestCase):
mock_cmce.assert_called_once_with(instack_env, mock_mistral)
mock_create.assert_called_once_with(mock_mistral, ['hut8'])
def _neutron_mocks(self):
mock_sdk = mock.MagicMock()
mock_sdk.network.create_network = mock.Mock()
mock_sdk.network.delete_segment = mock.Mock()
mock_sdk.network.create_subnet = mock.Mock()
mock_sdk.network.update_subnet = mock.Mock()
return mock_sdk
def test_network_create(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.create_network.assert_called_with(
name='ctlplane', provider_network_type='flat',
provider_physical_network='ctlplane')
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_subnet_create(self):
mock_sdk = self._neutron_mocks()
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'}]
undercloud._neutron_subnet_create(mock_sdk, 'network_id',
'192.168.24.0/24', '192.168.24.1',
host_routes, allocation_pool,
'ctlplane-subnet')
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')
def test_subnet_update(self):
mock_sdk = self._neutron_mocks()
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'}]
undercloud._neutron_subnet_update(mock_sdk, 'subnet_id',
'192.168.24.1', host_routes,
allocation_pool, 'ctlplane-subnet')
mock_sdk.network.update_subnet.assert_called_with(
'subnet_id', name='ctlplane-subnet', gateway_ip='192.168.24.1',
host_routes=host_routes, allocation_pools=allocation_pool)
class TestUpgradeFact(base.BaseTestCase):
@mock.patch('instack_undercloud.undercloud._run_command')

View File

@ -21,6 +21,7 @@ import glob
import hashlib
import json
import logging
import netaddr
import os
import platform
import re
@ -111,7 +112,9 @@ log can be found at %(log_file)s.
# We need 8 GB, leave a little room for variation in what 8 GB means on
# different platforms.
REQUIRED_MB = 7680
# Control plane network name
PHYSICAL_NETWORK = 'ctlplane'
CTLPLANE_SUBNET_NAME = 'ctlplane-subnet'
# When adding new options to the lists below, make sure to regenerate the
# sample config by running "tox -e genconfig" in the project root.
@ -1808,6 +1811,16 @@ def _post_config(instack_env, upgrade):
ironic = ir_client.get_client(1, session=sess,
os_ironic_api_version='1.21')
sdk = os_client_config.make_sdk(auth_url=auth_url,
project_name=project,
username=user,
password=password,
project_domain_name='Default',
user_domain_name='Default')
network = _ensure_neutron_network(sdk)
_config_neutron_segments_and_subnets(sdk, network.id)
_configure_ssh_keys(nova)
_ensure_ssh_selinux_permission()
_delete_default_flavors(nova)
@ -1848,6 +1861,117 @@ def _post_config(instack_env, upgrade):
_migrate_to_convergence(heat)
def _ensure_neutron_network(sdk):
try:
network = list(sdk.network.networks(name=PHYSICAL_NETWORK))
if not network:
network = sdk.network.create_network(
name=PHYSICAL_NETWORK, provider_network_type='flat',
provider_physical_network=PHYSICAL_NETWORK)
LOG.info("Network created %s", network)
else:
LOG.info("Not creating %s network, because it already exists.",
PHYSICAL_NETWORK)
network = network[0]
except Exception as e:
LOG.info("Network create/update failed %s", e)
raise
return network
def _neutron_subnet_create(sdk, network_id, cidr, gateway, host_routes,
allocation_pool, name):
try:
# DHCP_START contains a ":" then assume a IPv6 subnet
if ':' in allocation_pool[0]['start']:
host_routes = ''
subnet = sdk.network.create_subnet(
name=name,
cidr=cidr,
gateway_ip=gateway,
host_routes=host_routes,
enable_dhcp=True,
ip_version='6',
ipv6_address_mode='dhcpv6-stateless',
ipv6_ra_mode='dhcpv6-stateless',
allocation_pools=allocation_pool,
network_id=network_id)
else:
subnet = sdk.network.create_subnet(
name=name,
cidr=cidr,
gateway_ip=gateway,
host_routes=host_routes,
enable_dhcp=True,
ip_version='4',
allocation_pools=allocation_pool,
network_id=network_id)
LOG.info("Subnet created %s", subnet)
except Exception as e:
LOG.error("Create subnet %s failed: %s", name, e)
raise
return subnet
def _neutron_subnet_update(sdk, subnet_id, gateway, host_routes,
allocation_pool, name):
try:
# DHCP_START contains a ":" then assume a IPv6 subnet
if ':' in allocation_pool[0]['start']:
host_routes = ''
subnet = sdk.network.update_subnet(
subnet_id,
name=name,
gateway_ip=gateway,
host_routes=host_routes,
allocation_pools=allocation_pool)
LOG.info("Subnet updated %s", subnet)
except Exception as e:
LOG.error("Update subnet %s 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')
sdk.network.add_interface_to_router(router.id, subnet_id=subnet_id)
except Exception as e:
LOG.error("Create router for subnet %s failed: %s", name, e)
raise
def _get_subnet(sdk, cidr, network_id):
try:
subnet = list(sdk.network.subnets(cidr=cidr, network_id=network_id))
except Exception:
raise
return False if not subnet else subnet[0]
def _config_neutron_segments_and_subnets(sdk, ctlplane_id):
host_routes = [{'destination': '169.254.169.254/32',
'nexthop': str(netaddr.IPNetwork(CONF.local_ip).ip)}]
allocation_pool = [{'start': CONF.dhcp_start, 'end': CONF.dhcp_end}]
subnet = _get_subnet(sdk, CONF.network_cidr, ctlplane_id)
if subnet:
_neutron_subnet_update(sdk, subnet.id, CONF.network_gateway,
host_routes, allocation_pool,
CTLPLANE_SUBNET_NAME)
else:
subnet = _neutron_subnet_create(sdk, ctlplane_id, CONF.network_cidr,
CONF.network_gateway, host_routes,
allocation_pool, CTLPLANE_SUBNET_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, CTLPLANE_SUBNET_NAME, subnet.id)
def _handle_upgrade_fact(upgrade=False):
"""Create an upgrade fact for use in puppet