tripleo-heat-templates/extraconfig/post_deploy/undercloud_ctlplane_network.py
Harald Jensas e947c7e610 Add ctlplane networking for routed networks
* Add a new post install software deployment which runs
a python script to configure the undercloud control
plane network. Replaces section in post shell script.

Change-Id: I1cd594564d1628a6e1fccb9eadf18b716ccc5c72
2018-03-29 23:32:45 +00:00

274 lines
10 KiB
Python

#!/usr/bin/python
import json
import netaddr
import os
import os_client_config
import subprocess
CTLPLANE_NETWORK_NAME = 'ctlplane'
AUTH_URL = os.environ['auth_url']
ADMIN_PASSWORD = os.environ['admin_password']
CONF = json.loads(os.environ['config'])
def _run_command(args, env=None, name=None):
"""Run the command defined by args and return its output
:param args: List of arguments for the command to be run.
:param env: Dict defining the environment variables. Pass None to use
the current environment.
:param name: User-friendly name for the command being run. A value of
None will cause args[0] to be used.
"""
if name is None:
name = args[0]
if env is None:
env = os.environ
env = env.copy()
# When running a localized python script, we need to tell it that we're
# using utf-8 for stdout, otherwise it can't tell because of the pipe.
env['PYTHONIOENCODING'] = 'utf8'
try:
return subprocess.check_output(args,
stderr=subprocess.STDOUT,
env=env).decode('utf-8')
except subprocess.CalledProcessError as ex:
print('ERROR: %s failed: %s' % (name, ex.output))
raise
def _ensure_neutron_network(sdk):
try:
network = list(sdk.network.networks(name=CTLPLANE_NETWORK_NAME))
if not network:
network = sdk.network.create_network(
name=CTLPLANE_NETWORK_NAME,
provider_network_type='flat',
provider_physical_network=CONF['physical_network'],
mtu=CONF['mtu'])
print('INFO: Network created %s' % network)
# (hjensas) Delete the default segment, we create a new segment
# per subnet later.
segments = list(sdk.network.segments(network_id=network.id))
sdk.network.delete_segment(segments[0].id)
print('INFO: Default segment on network %s deleted.' %
network.name)
else:
network = sdk.network.update_network(
network[0].id,
name=CTLPLANE_NETWORK_NAME,
mtu=CONF['mtu'])
print('INFO: Network updated %s' % network)
except Exception:
print('ERROR: Network create/update failed.')
raise
return network
def _neutron_subnet_create(sdk, network_id, cidr, gateway, host_routes,
allocation_pool, name, segment_id, dns_nameservers):
try:
if netaddr.IPNetwork(cidr).version == 6:
subnet = sdk.network.create_subnet(
name=name,
cidr=cidr,
gateway_ip=gateway,
enable_dhcp=True,
ip_version='6',
ipv6_address_mode='dhcpv6-stateless',
ipv6_ra_mode='dhcpv6-stateless',
allocation_pools=allocation_pool,
network_id=network_id,
segment_id=segment_id,
dns_nameservers=dns_nameservers)
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,
segment_id=segment_id,
dns_nameservers=dns_nameservers)
print('INFO: Subnet created %s' % subnet)
except Exception:
print('ERROR: Create subnet %s failed.' % name)
raise
return subnet
def _neutron_subnet_update(sdk, subnet_id, cidr, gateway, host_routes,
allocation_pool, name, dns_nameservers):
try:
if netaddr.IPNetwork(cidr).version == 6:
subnet = sdk.network.update_subnet(
subnet_id,
name=name,
gateway_ip=gateway,
allocation_pools=allocation_pool,
dns_nameservers=dns_nameservers)
else:
subnet = sdk.network.update_subnet(
subnet_id,
name=name,
gateway_ip=gateway,
host_routes=host_routes,
allocation_pools=allocation_pool,
dns_nameservers=dns_nameservers)
print('INFO: Subnet updated %s' % subnet)
except Exception:
print('ERROR: Update of subnet %s failed.' % name)
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')
print('INFO: Neutron Segment created %s' % segment)
except Exception as ex:
print('ERROR: Neutron Segment %s create failed.' % name)
raise
return segment
def _neutron_segment_update(sdk, segment_id, name):
try:
segment = sdk.network.update_segment(segment_id, name=name)
print('INFO: Neutron Segment updated %s', segment)
except Exception:
print('ERROR: Neutron Segment %s update failed.' % name)
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:
print('ERROR: Create router for subnet %s failed.' % name)
raise
def _get_subnet(sdk, cidr, network_id):
try:
subnet = list(sdk.network.subnets(cidr=cidr, network_id=network_id))
except Exception as ex:
print('ERROR: Get subnet with cidr %s failed.' % cidr)
raise
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:
print('ERROR: Get segment for physical_network %s on network_id %s '
'failed.' % (phy, network_id))
raise
return False if not segment else segment[0]
def config_neutron_segments_and_subnets(sdk, ctlplane_id):
s = CONF['subnets'][CONF['local_subnet']]
subnet = _get_subnet(sdk, s['NetworkCidr'], ctlplane_id)
if subnet and not subnet.segment_id:
print('WARNING: 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': CONF['local_ip']}]
allocation_pool = [{'start': s['DhcpRangeStart'],
'end': s['DhcpRangeEnd']}]
_neutron_subnet_update(
sdk, subnet.id, s['NetworkCidr'], s['NetworkGateway'], host_routes,
allocation_pool, CONF['local_subnet'], CONF['nameservers'])
# 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 netaddr.IPNetwork(s['NetworkCidr']).version == 6:
_ensure_neutron_router(sdk, CONF['local_subnet'], subnet.id)
else:
for name in CONF['subnets']:
s = CONF['subnets'][name]
phynet = name
metadata_nexthop = s['NetworkGateway']
if name == CONF['local_subnet']:
phynet = CONF['physical_network']
metadata_nexthop = CONF['local_ip']
host_routes = [{'destination': '169.254.169.254/32',
'nexthop': metadata_nexthop}]
allocation_pool = [{'start': s['DhcpRangeStart'],
'end': s['DhcpRangeEnd']}]
subnet = _get_subnet(sdk, s['NetworkCidr'], 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)):
raise RuntimeError(
'The cidr: %s of the local subnet is already used in '
'subnet: %s which is associated with segment_id: %s.' %
(s['NetworkCidr'], subnet.id, subnet.segment_id))
if subnet:
_neutron_segment_update(sdk, subnet.segment_id, name)
_neutron_subnet_update(
sdk, subnet.id, s['NetworkCidr'], s['NetworkGateway'],
host_routes, allocation_pool, name, CONF['nameservers'])
else:
if segment:
_neutron_segment_update(sdk, segment.id, name)
else:
segment = _neutron_segment_create(sdk, name,
ctlplane_id, phynet)
if CONF['enable_routed_networks']:
subnet = _neutron_subnet_create(
sdk, ctlplane_id, s['NetworkCidr'],
s['NetworkGateway'], host_routes, allocation_pool,
name, segment.id, CONF['nameservers'])
else:
subnet = _neutron_subnet_create(
sdk, ctlplane_id, s['NetworkCidr'],
s['NetworkGateway'], host_routes, allocation_pool,
name, None, CONF['nameservers'])
# 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 netaddr.IPNetwork(s['NetworkCidr']).version == 6:
_ensure_neutron_router(sdk, name, subnet.id)
if _run_command(['hiera', 'neutron_api_enabled'], name='hiera'):
sdk = os_client_config.make_sdk(auth_url=AUTH_URL,
project_name='admin',
username='admin',
password=ADMIN_PASSWORD,
project_domain_name='Default',
user_domain_name='Default')
network = _ensure_neutron_network(sdk)
config_neutron_segments_and_subnets(sdk, network.id)