Add static network configuration support
Adds config drive based static network configuration to tinyipa. Prerequisites: * ramdisk is booted from a CD drive * boot ISO image contains a config drive image * OpenStack network meta data is found on the config drive Then attempt to apply static network configuration to the NICs. Fall back to DHCP configuration if not a single NIC has been configured successfully. Change-Id: I01956d7b71362b288fdb8ef06852043f9c8290a2 Story: 2006691 Task: 36993
This commit is contained in:
parent
2380586d2f
commit
fd7bcfab72
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds config drive based static network configuration to tinyipa. If ramdisk
|
||||||
|
is booted from a CD drive, and boot ISO image also contains a config drive
|
||||||
|
image, and OpenStack network meta data is found on the config drive then
|
||||||
|
attempt to apply static network configuration to the NICs. Fall back to
|
||||||
|
DHCP configuration if not a single NIC has been configured successfully.
|
@ -6,6 +6,10 @@
|
|||||||
# This waits until all devices have registered
|
# This waits until all devices have registered
|
||||||
/sbin/udevadm settle --timeout=%UDEV_SETTLE_TIMEOUT%
|
/sbin/udevadm settle --timeout=%UDEV_SETTLE_TIMEOUT%
|
||||||
|
|
||||||
|
# First try for static network configuration
|
||||||
|
/opt/static_network.sh && exit
|
||||||
|
|
||||||
|
# Otherwise fall back to DHCP
|
||||||
NETDEVICES="$(awk -F: '/eth.:|tr.:/{print $1}' /proc/net/dev 2>/dev/null)"
|
NETDEVICES="$(awk -F: '/eth.:|tr.:/{print $1}' /proc/net/dev 2>/dev/null)"
|
||||||
echo "$0: Discovered network devices: $NETDEVICES"
|
echo "$0: Discovered network devices: $NETDEVICES"
|
||||||
for DEVICE in $NETDEVICES; do
|
for DEVICE in $NETDEVICES; do
|
||||||
|
185
tinyipa/build_files/export_network_data.py
Executable file
185
tinyipa/build_files/export_network_data.py
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2019 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
DESCRIPTION = """\
|
||||||
|
This tool turns OpenStack network metadata, supplied in a form of JSON
|
||||||
|
document, into a collection of bash arrays.
|
||||||
|
|
||||||
|
Each array variable holds values belonging to a specific option (e.g.
|
||||||
|
IP address) indexed by entity number (e.g. NIC). This representation
|
||||||
|
is thought to be more convenient to use from bash scripts.
|
||||||
|
|
||||||
|
The caller of this tool is expected to `eval` its stdout to get bash
|
||||||
|
variables into caller's environment.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
This fragment of L2 configuration:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "interface2",
|
||||||
|
"type": "vif",
|
||||||
|
"ethernet_mac_address": "a0:36:9f:2c:e8:70",
|
||||||
|
"vif_id": "e1c90e9f-eafc-4e2d-8ec9-58b91cebb53d",
|
||||||
|
"mtu": 1500
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "interface0",
|
||||||
|
"type": "phy",
|
||||||
|
"ethernet_mac_address": "a0:36:9f:2c:e8:80",
|
||||||
|
"mtu": 9000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Will turn into these bash variables:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
ND_L2_ID=('interface2' 'interface0')
|
||||||
|
ND_L2_TYPE=('vif' 'phy')
|
||||||
|
ND_L2_ETHERNET_MAC_ADDRESS=('a0:36:9f:2c:e8:70' 'a0:36:9f:2c:e8:80')
|
||||||
|
ND_L2_VIF_ID=('e1c90e9f-eafc-4e2d-8ec9-58b91cebb53d' '')
|
||||||
|
ND_L2_MTU=(1500 9000)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--source',
|
||||||
|
metavar='<network_data.json>',
|
||||||
|
type=str,
|
||||||
|
required=True,
|
||||||
|
help='OpenStack network configuration meta data '
|
||||||
|
'(AKA network_data.json)')
|
||||||
|
|
||||||
|
parsed_args = parser.parse_args()
|
||||||
|
|
||||||
|
with open(parsed_args.source) as fl:
|
||||||
|
network_data = json.load(fl)
|
||||||
|
|
||||||
|
subnets = collections.defaultdict(collections.defaultdict)
|
||||||
|
|
||||||
|
for network in network_data.get('networks', []):
|
||||||
|
# remap IP address variable to facilitate further CLI usage
|
||||||
|
if network.get('type') == 'ipv4':
|
||||||
|
network['ipv4_address'] = network.pop('ip_address', '')
|
||||||
|
network['ipv4_netmask'] = network.pop('netmask', '')
|
||||||
|
elif network.get('type') == 'ipv6':
|
||||||
|
network['ipv6_address'] = network.pop('ip_address', '')
|
||||||
|
network['ipv6_netmask'] = network.pop('netmask', '')
|
||||||
|
|
||||||
|
subnet = subnets[network['link']]
|
||||||
|
subnet.update(network)
|
||||||
|
|
||||||
|
nics = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
for link in network_data.get('links', []):
|
||||||
|
|
||||||
|
nd_dev = link['id']
|
||||||
|
nic = nics[nd_dev]
|
||||||
|
|
||||||
|
nic.update({'ND_L2_%s' % k.upper(): v
|
||||||
|
for k, v in link.items()})
|
||||||
|
|
||||||
|
nic.update(
|
||||||
|
{'ND_L3_%s' % k.upper(): v
|
||||||
|
for k, v in subnets.get(nd_dev, {}).items()})
|
||||||
|
|
||||||
|
# transpose into positional form
|
||||||
|
|
||||||
|
variables = collections.defaultdict(dict)
|
||||||
|
routes = collections.defaultdict(list)
|
||||||
|
services = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
for index, nic_id in enumerate(nics):
|
||||||
|
nic = nics[nic_id]
|
||||||
|
|
||||||
|
for var, val in nic.items():
|
||||||
|
if isinstance(val, (str, int)):
|
||||||
|
variables[var][index] = val
|
||||||
|
|
||||||
|
# routing table is a special case
|
||||||
|
elif var == 'ND_L3_ROUTES':
|
||||||
|
for route in val:
|
||||||
|
for field in ('network', 'netmask', 'gateway'):
|
||||||
|
key = var + '_' + field.upper()
|
||||||
|
routes[key].append(route.get(field, ''))
|
||||||
|
|
||||||
|
routes[var + '_ETHERNET_MAC_ADDRESS'].append(nic.get(
|
||||||
|
'ND_L2_ETHERNET_MAC_ADDRESS', ''))
|
||||||
|
|
||||||
|
sr_count = 0
|
||||||
|
|
||||||
|
for index, service in enumerate(network_data.get('services', [])):
|
||||||
|
for var, val in service.items():
|
||||||
|
if isinstance(val, (str, int)):
|
||||||
|
var = 'ND_SERVICE_' + var.upper()
|
||||||
|
services[var][index] = val
|
||||||
|
|
||||||
|
sr_count = index + 1
|
||||||
|
|
||||||
|
# generate bash arrays of L2/L3 NIC info indexed by NIC number
|
||||||
|
|
||||||
|
nd_count = len(nics)
|
||||||
|
|
||||||
|
for var, values in variables.items():
|
||||||
|
print('%s=(%s)' % (
|
||||||
|
var, ' '.join(repr(values.get(idx, ''))
|
||||||
|
for idx in range(nd_count))))
|
||||||
|
|
||||||
|
print('ND_NIC_COUNT=%d' % nd_count)
|
||||||
|
|
||||||
|
# generate bash arrays of IPv4/v6 routing info indexed by route number
|
||||||
|
|
||||||
|
rt_count = 0
|
||||||
|
|
||||||
|
for var, values in routes.items():
|
||||||
|
print('%s=(%s)' % (
|
||||||
|
var, ' '.join(repr(v) for v in values)))
|
||||||
|
|
||||||
|
if not rt_count:
|
||||||
|
rt_count = len(values)
|
||||||
|
|
||||||
|
print('ND_L3_ROUTES_COUNT=%d' % rt_count)
|
||||||
|
|
||||||
|
# generate bash arrays of network service info indexed by service number
|
||||||
|
|
||||||
|
for var, values in services.items():
|
||||||
|
print('%s=(%s)' % (
|
||||||
|
var, ' '.join(repr(values.get(idx, ''))
|
||||||
|
for idx in range(sr_count))))
|
||||||
|
|
||||||
|
print('ND_L3_SERVICES_COUNT=%d' % sr_count)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
121
tinyipa/build_files/static_network.sh
Executable file
121
tinyipa/build_files/static_network.sh
Executable file
@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Try to configure NICs and routes from OpenStack metadata
|
||||||
|
#
|
||||||
|
# Exit with success if at least one NIC has been configured
|
||||||
|
#
|
||||||
|
|
||||||
|
. /etc/init.d/tc-functions
|
||||||
|
|
||||||
|
NETWORK_DATA=openstack/latest/metadata/network_data.json
|
||||||
|
|
||||||
|
if [ ! -b /dev/cdrom ]; then
|
||||||
|
echo "CDROM device not present - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /mnt
|
||||||
|
mount -o ro /dev/cdrom /mnt
|
||||||
|
if [ "$?" != 0 ]; then
|
||||||
|
echo "CDROM device cannot be mounted - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /mnt/config-2.img ]; then
|
||||||
|
echo "Config drive image not present - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /config-2
|
||||||
|
mount -o ro /mnt/config-2.img /config-2
|
||||||
|
if [ "$?" != 0 ]; then
|
||||||
|
echo "Config drive cannot be mounted - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /config-2/$NETWORK_DATA ]; then
|
||||||
|
echo "No network_data.json found on config drive - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If succeeded, this script will print out bash variables
|
||||||
|
eval $(/opt/export_network_data.py --source /config-2/$NETWORK_DATA)
|
||||||
|
|
||||||
|
eval_rc=$?
|
||||||
|
|
||||||
|
umount -f /config-2 /mnt
|
||||||
|
|
||||||
|
if [ "$eval_rc" != 0 ]; then
|
||||||
|
echo "Processing network data failed - no static network config"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rc=1
|
||||||
|
|
||||||
|
declare -A mac_to_dev
|
||||||
|
|
||||||
|
while [ $ND_NIC_COUNT -gt 0 ]; do
|
||||||
|
|
||||||
|
ND_NIC_COUNT=$((ND_NIC_COUNT - 1))
|
||||||
|
|
||||||
|
ND_MAC=${ND_L2_ETHERNET_MAC_ADDRESS[$ND_NIC_COUNT]}
|
||||||
|
ND_L2_ID=${ND_L2_IDS[$ND_NIC_COUNT]}
|
||||||
|
|
||||||
|
if [ ! -z "$ND_L2_ID" ]; then
|
||||||
|
ip link show $ND_L2_ID > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ND_L2_ID" -o "$?" != 0 ]; then
|
||||||
|
# NIC is not known, look up NIC name by MAC
|
||||||
|
if [ -z "$ND_MAC" ]; then
|
||||||
|
echo "Neither name nor MAC is configured for a L2 device, continuing"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
ND_L2_ID=$(ip -o link | awk "/.*?BROADCAST.*?LOWER_UP.*?${ND_MAC}.*?/{print \$2}")
|
||||||
|
ND_L2_ID=${ND_L2_ID/:/}
|
||||||
|
|
||||||
|
if [ -z "$ND_L2_ID" ]; then
|
||||||
|
echo "No NIC found by MAC $ND_MAC, continuing"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mac_to_dev[$ND_MAC] = $ND_L2_ID
|
||||||
|
|
||||||
|
ifconfig $ND_L2_ID \
|
||||||
|
${ND_L3_IPV4_ADDRESS[$ND_NIC_COUNT]} \
|
||||||
|
netmask ${ND_L3_IPV4_NETMASK[$ND_NIC_COUNT]} \
|
||||||
|
up
|
||||||
|
|
||||||
|
if [ "$?" != 0 ]; then
|
||||||
|
echo "Failed to assign IP address to NIC $ND_L2_ID, continuing"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# consider it a success if at least one NIC is brought up
|
||||||
|
rc=0
|
||||||
|
|
||||||
|
echo "Configured $ND_L2_ID IP ${ND_L3_IPV4_ADDRESS[$ND_NIC_COUNT]} netmask ${ND_L3_IPV4_NETMASK[$ND_NIC_COUNT]}"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
while [ $ND_L3_ROUTES_COUNT -gt 0 ]; do
|
||||||
|
ND_L3_ROUTES_COUNT=$((ND_L3_ROUTES_COUNT - 1))
|
||||||
|
|
||||||
|
route add -net ${ND_L3_ROUTES_NETWORK[ND_L3_ROUTES_COUNT]} \
|
||||||
|
netmask ${ND_L3_ROUTES_NETMASK[ND_L3_ROUTES_COUNT]} \
|
||||||
|
gw ${ND_L3_ROUTES_GATEWAY[ND_L3_ROUTES_COUNT]} \
|
||||||
|
dev ${mac_to_dev[$ND_L2_ID]}
|
||||||
|
|
||||||
|
if [ "$?" != 0 ]; then
|
||||||
|
echo "Failed to set route for network ${ND_L3_ROUTES_NETWORK[ND_L3_ROUTES_COUNT]}, continuing"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Set route ${ND_L3_ROUTES_NETWORK[ND_L3_ROUTES_COUNT]}"
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $rc
|
||||||
|
|
@ -150,6 +150,9 @@ cleanup_tce "$DST_DIR"
|
|||||||
# Copy bootlocal.sh to opt
|
# Copy bootlocal.sh to opt
|
||||||
sudo cp "$WORKDIR/build_files/bootlocal.sh" "$FINALDIR/opt/."
|
sudo cp "$WORKDIR/build_files/bootlocal.sh" "$FINALDIR/opt/."
|
||||||
|
|
||||||
|
# Copy static_network.sh to opt
|
||||||
|
sudo cp "$WORKDIR/build_files/static_network.sh" "$FINALDIR/opt/."
|
||||||
|
|
||||||
# Copy udhcpc.script to opt
|
# Copy udhcpc.script to opt
|
||||||
sudo cp "$WORKDIR/udhcpc.script" "$FINALDIR/opt/"
|
sudo cp "$WORKDIR/udhcpc.script" "$FINALDIR/opt/"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user