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
|
||||
/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)"
|
||||
echo "$0: Discovered network devices: $NETDEVICES"
|
||||
for DEVICE in $NETDEVICES; do
|
||||
|
|
|
@ -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())
|
|
@ -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
|
||||
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
|
||||
sudo cp "$WORKDIR/udhcpc.script" "$FINALDIR/opt/"
|
||||
|
||||
|
|
Loading…
Reference in New Issue