diff --git a/.gitignore b/.gitignore index 36c997b..455a402 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,12 @@ develop-eggs lib lib64 +# Exclude those directories, as they contain the DPM Guest Image Tools +# Don't ignore those directories, as they contain the DPM Guest Image Tools. +# '!' reverts the matching on 'bin' and 'lib' under guest_image_tools/usr/" +!guest_image_tools/usr/bin +!guest_image_tools/usr/lib + # Installer logs pip-log.txt diff --git a/doc/source/guest_image_tools.rst b/doc/source/guest_image_tools.rst new file mode 100644 index 0000000..691e202 --- /dev/null +++ b/doc/source/guest_image_tools.rst @@ -0,0 +1,121 @@ +===================== +DPM Guest Image Tools +===================== + +The DPM Guest Image Tools must be installed within a DPM OpenStack image. +The purpose of the tools are to dynamically configure the network interfaces. + +Doing IP configuration is not part of the tools. This is handled like usual +with cloud-init. + +autoconfigure_networking +------------------------ +Description ++++++++++++ +Is used to configure all network interfaces that are listed in the kernels +cmdline */proc/cmdline* with the given adapter port. All interfaces are +configured in layer2 mode. + +The format of the data in the cmdline must be + + ,[,]; + +Example + + 0001,1,0a0000000011;0004,0; + +This will result in + +* 0001 being configured with port 1 + +* 0004 being configured with port 0 + +Content ++++++++ +* systemd service autoconfigure_networking.service + +* shell script autoconfigure_networking.sh + +Trigger ++++++++ + +The systemd service autoconfigure_networking.service is configured to +run before cloud-init during boot. It's job is to trigger the shell script. + +Manual execution of the shell script + + /usr/bin/autoconfigure_networking.sh + +Installation +++++++++++++ + +* Place the following files in the guest image + + * dpm_guest_tools/usr/bin/autoconfigure_networking.sh + + -> /usr/bin/autoconfigure_networking.sh + + * dpm_guest_tools/usr/lib/systemd/system/autoconfigure_networking.service + + -> /usr/lib/systemd/systemd/autoconfigure_networking.service + +* Ensure permissions + + chmod 644 /usr/lib/systemd/system/autoconfigure_networking.service + +* Enable the service for autostart + + * systemctl enable autoconfigure_networking.service + +setmac +------ +Description ++++++++++++ + +Is used to reconfigure the MAC address of a network interface. The mapping +must be provided via the kernels cmdline */proc/cmdline*. + +The format of the data in the cmdline must be + + ,,; + +Example + + 0001,1,0a0000000011;0004,0; + +* 0001: corresponding interface will be set to mac 0a:00:00:00:00:11 + +* 0004: mac will not be changed + +Content ++++++++ + +* shell script setmac.sh + +* udev rule 80-setmac.rules + +Trigger ++++++++ + +If a new network interface gets configured (e.g. for device 0.0.0001), +the udev rules triggers the shell script passing in the device-bus-id. + +If a service instance for a certain device-bus-id already exists, it will not +get started again. + +Manual execution of the shell script + + /usr/bin/setmac.sh + +Installation +++++++++++++ + +* Place the following files in the guest image + + * dpm_guest_tools/usr/bin/setmac.sh + + -> /usr/bin/setmac.sh + + * dpm_guest_tools/etc/udev/rules.d/80-setmac.rules + + -> /etc/udev/rules.d/80-setmac.rules diff --git a/doc/source/index.rst b/doc/source/index.rst index 9d6f820..c381946 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -61,6 +61,7 @@ Using the driver installation configuration + guest_image_tools Contributing to the project =========================== diff --git a/guest_image_tools/etc/udev/rules.d/80-setmac.rules b/guest_image_tools/etc/udev/rules.d/80-setmac.rules new file mode 100644 index 0000000..a8df9b9 --- /dev/null +++ b/guest_image_tools/etc/udev/rules.d/80-setmac.rules @@ -0,0 +1,3 @@ +# React when an qeth device has been added +# %p is the device-path, e.g. /devices/qeth/0.0.0001/net/enc1 +SUBSYSTEM=="net", ACTION=="add", DRIVERS=="qeth", RUN+="/usr/bin/setmac.sh %p" diff --git a/guest_image_tools/usr/bin/autoconfigure_networking.sh b/guest_image_tools/usr/bin/autoconfigure_networking.sh new file mode 100755 index 0000000..b6c4486 --- /dev/null +++ b/guest_image_tools/usr/bin/autoconfigure_networking.sh @@ -0,0 +1,82 @@ +#! /bin/bash +# Copyright 2017 IBM Corp. 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. + +# This script configures all qeth network interfaces that are listed in the +# kernels cmdline */proc/cmdline* with the given adapter port. All interfaces +# are configured in layer2 mode. +# The format of the cmdline parameters must be either +# ,; +# or +# ,,; + +LOG_PREFIX=$(basename "$0") +REGEX_DEV_NO="[0-9A-Fa-f]{4}" +REGEX_MAC="[0-9A-Fa-f]{12}" + +# Regex to match +# ,; +# ,,; +REGEX="($REGEX_DEV_NO),([0-1])(,$REGEX_MAC)?;" + +#CMDLINE="some stuff 0001,1,000000000011;0004,;0007,0; more stuff" +CMDLINE=$(cat /proc/cmdline) + +function log { + # $1 = message to log + # This script usually gets called by systemd. Systemd takes care of writing + # stdout and stderr into the journal. Using "echo" here ensures, that + # all the messages show up under the corresponding systemd unit. + echo "$LOG_PREFIX: $1" +} + +log "Start" + +# Default return code +rc=0 + +# Bash does not support global matching, therefore +# we remove the current match from the cmdline to +# allow matching the next value +while [[ $CMDLINE =~ $REGEX ]]; do + dev_no="${BASH_REMATCH[1]}" + dev_bus_id="0.0.$dev_no" + log "Configuring dev_bus_id $dev_bus_id." + + # remove current match from variable, to allow next match in next iteration + CMDLINE=${CMDLINE#*"${dev_no}"} + + + # Check if device is already configured + path="/sys/bus/ccwgroup/devices/$dev_bus_id" + if [ -d "$path" ]; then + log "Interface for $dev_bus_id already configured. Skipping." + continue + fi + + # Determine port + port="${BASH_REMATCH[2]}" + log "Port $port used for dev_bus_id $dev_bus_id." + + # TODO(andreas_s): Do not depend on znetconf + # Errors of the following command are written to stderr, and therefore + # show up in the systemd units journal + znetconf -a $dev_bus_id -o portno=$port,layer2=1 + if [[ $? != 0 ]]; then + rc=1 + fi + +done +log "Finished with rc '$rc'" +exit $rc \ No newline at end of file diff --git a/guest_image_tools/usr/bin/setmac.sh b/guest_image_tools/usr/bin/setmac.sh new file mode 100755 index 0000000..6975899 --- /dev/null +++ b/guest_image_tools/usr/bin/setmac.sh @@ -0,0 +1,181 @@ +#! /bin/bash +# Copyright 2017 IBM Corp. 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. + +# Input parameter of this script is the device path of an existing qeth network +# interface. E.g. +# ./setmac.sh "/devices/qeth/0.0.0001/net/enc1" +# This script sets the MAC address of this qeth network interface to the MAC +# provided in the kernels cmdline */proc/cmdline*. +# The format of the cmdline parameters must be +# ,,; + + +# Matches MAC in format: xxxxxxxxxxxx +REGEX_MAC="[0-9A-Fa-f]{12}" +REGEX_EXTRACT_DEVNO="qeth/0\.0\.([0-9A-Fa-f]{4})/net" +REGEX_EXTRACT_IFNAME="/net/(.{1,15})" +LOG_PREFIX=$(basename "$0") + + +function log { + # $1 = message to log + + # Logging to syslog + logger "$LOG_PREFIX: $1" +} + +function extract_devno { + # Extracts the device number out of a device path + # $1 = the device path, e.g. "/devices/qeth/0.0.0001/net/enc1" + # Returns: device number, e.g. "0001" + + local dev_path="$1" + if [[ $dev_path =~ $REGEX_EXTRACT_DEVNO ]]; then + echo "${BASH_REMATCH[1]}" + else + log "Could not extract devno from '$dev_path'. Skipping!" + exit 1 + fi +} + +function extract_interface_name { + # Extracts the interface name out of a device path + # $1 = the device path, e.g. "/devices/qeth/0.0.0001/net/enc1" + # Returns: interface name, e.g. "enc1" + + local dev_path="$1" + if [[ $dev_path =~ $REGEX_EXTRACT_IFNAME ]]; then + echo "${BASH_REMATCH[1]}" + else + log "Could not find interface for device number '$devno' in path 'dev_path'. Skipping!" + exit 1 + fi +} + + +function extract_mac { + # Get the mac address to a given device number from the cmdline + # $1 = the device number, e.g. "0001" + # $2 = the cmdline, e.g. "0001,0,aabbccddeeff;" + # Returns: The MAC address in format xx:xx:xx:xx:xx:xx + + local devno="$1" + local cmdline="$2" + # Regex matches: ,,; + regex=${devno}",[0-1],("${REGEX_MAC}");" + if [[ $cmdline =~ $regex ]]; then + local mac_tmp="${BASH_REMATCH[1]}" + # Insert ':' into MAC again + echo ${mac_tmp:0:2}:${mac_tmp:2:2}:${mac_tmp:4:2}:${mac_tmp:6:2}:${mac_tmp:8:2}:${mac_tmp:10:2} + else + log "No MAC for devno '$devno' found in cmdline '$cmdline'. Exit." + exit 1 + fi +} + +function is_locally_administered_mac { + # Verifies if a MAC is a locally administered unicast MAC + # $1 = MAC, e.g. "00:11:22:33:44:55" + # Returns + # rc 0 = True, rc 1 = False + + local mac="$1" + + # Only Unicast MACs that have the "locally administered" bit set are allowed + # by OSA. The "locally administered" bit is the "second least significant" + # bit of the most significant MAC byte. In addition only unicast addresses + # are allowed. The unicast bit is the least significant bit of the most + # significant byte. + # Example: AA:BB:CC:DD:EE:FF + # Most significant Byte: ^^ + # In Binary: 1010 1010 + # Second least significant bit: ^ = locally administered = 1 + # Least significant bit: ^ = unicast = 0 + # Therefore the only MACs are allowed, that have the 10 as those 2 bits. + # This results in the following possible MACs (where X can be any hex char): + # X2:XX:XX:XX:XX:XX + # X6:XX:XX:XX:XX:XX + # XA:XX:XX:XX:XX:XX + # XE:XX:XX:XX:XX:XX + local regex="^[0-9A-Fa-f][26AaEe]" + if [[ $mac =~ $regex ]]; then + return 0 + else + return 1 + fi +} + +function get_ip_cmd { + # Determines the path of the ip cmd + # Returns: Path to ip cmd + + # When this script is called from a udev rule, it is not able to find + # the ip command. Also the 'which' command is not working. As different + # distros install it to different locations, we need to try out which + # path is working. + local paths=("/usr/sbin/ip" "/sbin/ip") + + for path in "${paths[@]}"; do + if [[ -x $path ]]; then + echo "$path" + return 0 + fi + done + + log "'ip' command not found. Exiting." + exit 1 +} + +function set_mac { + # This function sets the given MAC on the given interface + # $1 = Interface name to set the mac on, e.g. "enc1" + # $2 = The mac address, e.g. "00:11:22:33:44:55" + + local if_name="$1" + local mac="$2" + if ! is_locally_administered_mac "$mac" ; then + log "MAC $mac is not a locally administered MAC. Aborting!" + exit 1 + fi + + local ip_cmd=$(get_ip_cmd) + local cmd="$ip_cmd link set $if_name address $mac 2>&1 > /dev/null" + stderr=$(eval "$cmd") + local rc=$? + if [[ $rc != 0 ]]; then + log "Operation '$cmd' failed with exit rc '$rc': $stderr. Aborting!" + exit 1 + fi + log "Successfully set MAC of interface '$if_name' to '$mac'" + +} + +function get_cmdline { + #Example: "some stuff nics=0001,0,aabbccddeeff;abcd,1,001122334455;" + echo $(cat /proc/cmdline) +} + +# e.g. /devices/qeth/0.0.0001/net/enc1 +DEV_PATH="$1" +CMDLINE=$(get_cmdline) + +log "Start with device path '$DEV_PATH'" +devno=$(extract_devno $DEV_PATH) +if_name=$(extract_interface_name $DEV_PATH) +mac=$(extract_mac $devno "$CMDLINE") +log "Using device number '$devno', interface '$if_name', mac '$mac'." + +set_mac "$if_name" "$mac" +log "Finished" \ No newline at end of file diff --git a/guest_image_tools/usr/lib/systemd/system/autoconfigure_networking.service b/guest_image_tools/usr/lib/systemd/system/autoconfigure_networking.service new file mode 100644 index 0000000..a5a7344 --- /dev/null +++ b/guest_image_tools/usr/lib/systemd/system/autoconfigure_networking.service @@ -0,0 +1,13 @@ +[Unit] +Description=Autoconfigure Network Interfaces (pre-networking) +DefaultDependencies=no +Before=cloud-init-local.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/autoconfigure_networking.sh +RemainAfterExit=yes +# Output needs to appear in instance console output +StandardOutput=journal+console +[Install] +WantedBy=multi-user.target