diff --git a/elements/undercloud-install/os-refresh-config/post-configure.d/98-undercloud-setup b/elements/undercloud-install/os-refresh-config/post-configure.d/98-undercloud-setup index 6853beb40..31816c493 100755 --- a/elements/undercloud-install/os-refresh-config/post-configure.d/98-undercloud-setup +++ b/elements/undercloud-install/os-refresh-config/post-configure.d/98-undercloud-setup @@ -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}') diff --git a/instack_undercloud/tests/test_undercloud.py b/instack_undercloud/tests/test_undercloud.py index 6b4d38507..1e6f4b24a 100644 --- a/instack_undercloud/tests/test_undercloud.py +++ b/instack_undercloud/tests/test_undercloud.py @@ -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') diff --git a/instack_undercloud/undercloud.py b/instack_undercloud/undercloud.py index 6367df0b0..fab244d21 100644 --- a/instack_undercloud/undercloud.py +++ b/instack_undercloud/undercloud.py @@ -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