Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ functionality:
`PROVISIONING_MACS` is provided)
- `PROVISIONING_IP` - the specific IP to use (instead of calculating it based on
the `PROVISIONING_INTERFACE`)
- `IRONIC_URL_HOSTNAME` - a fully qualified name resolving to an IPv4 and/or IPv6
address, used for both binding and forming the required URLs; for the latter
purpose only, it can be used in combination with `PROVISIONING_INTERFACE`, which
would instead be used for the former. If the hostname has both IPv4 and IPv6
records, and both addresses are correctly assigned on the same network interface,
`IRONIC_URL_HOSTNAME` enables a dual-stack ironic image configuration.
- `DNSMASQ_EXCEPT_INTERFACE` - interfaces to exclude when providing DHCP address
(default `lo`)
- `HTTP_PORT` - port used by http server (default `80`)
Expand Down
3 changes: 2 additions & 1 deletion ironic-config/apache2-ipxe.conf.j2
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Listen {{ env.IPXE_TLS_PORT }}
Listen 0.0.0.0:{{ env.IPXE_TLS_PORT }}
Listen [::]:{{ env.IPXE_TLS_PORT }}

<VirtualHost *:{{ env.IPXE_TLS_PORT }}>
ErrorLog /dev/stderr
Expand Down
3 changes: 2 additions & 1 deletion ironic-config/apache2-vmedia.conf.j2
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Listen {{ env.VMEDIA_TLS_PORT }}
Listen 0.0.0.0:{{ env.VMEDIA_TLS_PORT }}
Listen [::]:{{ env.VMEDIA_TLS_PORT }}

<VirtualHost *:{{ env.VMEDIA_TLS_PORT }}>
ErrorLog /dev/stderr
Expand Down
16 changes: 13 additions & 3 deletions ironic-config/httpd-ironic-api.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@


{% if env.LISTEN_ALL_INTERFACES | lower == "true" %}
Listen {{ env.IRONIC_LISTEN_PORT }}
Listen 0.0.0.0:{{ env.IRONIC_LISTEN_PORT }}
Listen [::]:{{ env.IRONIC_LISTEN_PORT }}
<VirtualHost *:{{ env.IRONIC_LISTEN_PORT }}>
{% else %}
Listen {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_LISTEN_PORT }}
<VirtualHost {{ env.IRONIC_URL_HOST }}:{{ env.IRONIC_LISTEN_PORT }}>
{% if env.ENABLE_IPV4 %}
Listen {{ env.IRONIC_IP }}:{{ env.IRONIC_LISTEN_PORT }}
{% endif %}
{% if env.ENABLE_IPV6 %}
Listen [{{ env.IRONIC_IPV6 }}]:{{ env.IRONIC_LISTEN_PORT }}
{% endif %}
{% if env.IRONIC_URL_HOSTNAME is defined and env.IRONIC_URL_HOSTNAME|length %}
<VirtualHost {{ env.IRONIC_URL_HOSTNAME }}:{{ env.IRONIC_LISTEN_PORT }}>
{% else %}
<VirtualHost {% if env.ENABLE_IPV4 %}{{ env.IRONIC_IP }}:{{ env.IRONIC_LISTEN_PORT }}{% endif %} {% if env.ENABLE_IPV6 %}[{{ env.IRONIC_IPV6 }}]:{{ env.IRONIC_LISTEN_PORT }}{% endif %}>
{% endif %}
{% endif %}

{% if env.IRONIC_PRIVATE_PORT == "unix" %}
Expand Down
10 changes: 8 additions & 2 deletions ironic-config/httpd.conf.j2
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
ServerRoot {{ env.HTTPD_DIR }}
{%- if env.LISTEN_ALL_INTERFACES | lower == "true" %}
Listen {{ env.HTTP_PORT }}
Listen 0.0.0.0:{{ env.HTTP_PORT }}
Listen [::]:{{ env.HTTP_PORT }}
{% else %}
Listen {{ env.IRONIC_URL_HOST }}:{{ env.HTTP_PORT }}
{% if env.ENABLE_IPV4 %}
Listen {{ env.IRONIC_IP }}:{{ env.HTTP_PORT }}
{% endif %}
{% if env.ENABLE_IPV6 %}
Listen [{{ env.IRONIC_IPV6 }}]:{{ env.HTTP_PORT }}
{% endif %}
{% endif %}
Include /etc/httpd/conf.modules.d/*.conf
User apache
Expand Down
10 changes: 8 additions & 2 deletions ironic-config/ironic.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ rpc_transport = none
use_stderr = true
# NOTE(dtantsur): the default md5 is not compatible with FIPS mode
hash_ring_algorithm = sha256
{% if env.ENABLE_IPV4 %}
my_ip = {{ env.IRONIC_IP }}
{% endif %}
{% if env.ENABLE_IPV6 %}
my_ipv6 = {{ env.IRONIC_IPV6 }}
{% endif %}

host = {{ env.IRONIC_CONDUCTOR_HOST }}

# If a path to a certificate is defined, use that first for webserver
Expand Down Expand Up @@ -64,7 +70,7 @@ port = {{ env.IRONIC_PRIVATE_PORT }}
{% endif %}
public_endpoint = {{ env.IRONIC_BASE_URL }}
{% else %}
host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %}
host_ip = {{ env.IRONIC_HOST_IP }}
port = {{ env.IRONIC_LISTEN_PORT }}
{% if env.IRONIC_TLS_SETUP == "true" %}
enable_ssl_api = true
Expand Down Expand Up @@ -175,7 +181,7 @@ cipher_suite_versions = 3,17
# containers are in host networking.
auth_strategy = http_basic
http_basic_auth_user_file = {{ env.IRONIC_RPC_HTPASSWD_FILE }}
host_ip = {% if env.LISTEN_ALL_INTERFACES | lower == "true" %}::{% else %}{{ env.IRONIC_IP }}{% endif %}
host_ip = {{ env.IRONIC_HOST_IP }}
{% if env.IRONIC_TLS_SETUP == "true" %}
use_ssl = true
cafile = {{ env.IRONIC_CACERT_FILE }}
Expand Down
1 change: 1 addition & 0 deletions main-packages-list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ sqlite
syslinux-nonlinux
util-linux
xorriso
bind-utils
17 changes: 16 additions & 1 deletion scripts/configure-ironic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export IRONIC_IPA_COLLECTORS=${IRONIC_IPA_COLLECTORS:-default,logs}

wait_for_interface_or_ip

if [[ "$(echo "$LISTEN_ALL_INTERFACES" | tr '[:upper:]' '[:lower:]')" == "true" ]]; then
export IRONIC_HOST_IP="::"
elif [[ -n env.ENABLE_IPV6 ]]; then
export IRONIC_HOST_IP="$IRONIC_IPV6"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC host_ip allows the use of a hostname, so we could consider using IRONIC_URL_HOSTNAME here if set.

else
export IRONIC_HOST_IP="$IRONIC_IP"
fi

# Hostname to use for the current conductor instance.
export IRONIC_CONDUCTOR_HOST=${IRONIC_CONDUCTOR_HOST:-${IRONIC_URL_HOST}}

Expand Down Expand Up @@ -94,4 +102,11 @@ render_j2_config "/etc/ironic/ironic.conf.j2" \
configure_json_rpc_auth

# Make sure ironic traffic bypasses any proxies
export NO_PROXY="${NO_PROXY:-},$IRONIC_IP"
export NO_PROXY="${NO_PROXY:-}"

if [[ -n "$IRONIC_IPV6" ]]; then
export NO_PROXY="${NO_PROXY},${IRONIC_IPV6}"
fi
if [[ -n "$IRONIC_IP" ]]; then
export NO_PROXY="${NO_PROXY},${IRONIC_IP}"
fi
196 changes: 171 additions & 25 deletions scripts/ironic-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ set -euxo pipefail
# Export IRONIC_IP to avoid needing to lean on IRONIC_URL_HOST for consumption in
# e.g. dnsmasq configuration
export IRONIC_IP="${IRONIC_IP:-}"
export IRONIC_IPV6="${IRONIC_IPV6:-}"
PROVISIONING_INTERFACE="${PROVISIONING_INTERFACE:-}"
PROVISIONING_IP="${PROVISIONING_IP:-}"
PROVISIONING_MACS="${PROVISIONING_MACS:-}"
IRONIC_URL_HOSTNAME="${IRONIC_URL_HOSTNAME:-}"
IPXE_CUSTOM_FIRMWARE_DIR="${IPXE_CUSTOM_FIRMWARE_DIR:-/shared/custom_ipxe_firmware}"
CUSTOM_CONFIG_DIR="${CUSTOM_CONFIG_DIR:-/conf}"
CUSTOM_DATA_DIR="${CUSTOM_DATA_DIR:-/data}"
Expand All @@ -33,6 +35,85 @@ export LOCAL_DB_URI="sqlite:///${IRONIC_DB_DIR}/ironic.sqlite"

export IRONIC_USE_MARIADB=${IRONIC_USE_MARIADB:-false}


get_ip_of_hostname()
{
if [[ "$#" -ne 2 ]]; then
echo "${FUNCNAME}: two parameters required, $# provided" >&2
return 1
fi

case $2 in
4)
QUERY="a";;
6)
QUERY="aaaa";;
*)
echo "${FUNCNAME}: the second parameter should be <4|6> for A and AAAA records"
return 1;;
esac

local HOSTNAME=$1

echo "$(nslookup -type=${QUERY} $HOSTNAME | tail -n2 | grep -w "Address:" | cut -d " " -f2)"
}

get_interface_of_ip()
{
local IP_VERS=""

if [[ "$#" -gt 2 ]]; then
echo "${FUNCNAME}: too many parameters" >&2
return 1
fi

if [[ "$#" -eq 2 ]]; then
case $2 in
4|6)
local IP_VERS="-${2}"
;;
*)
echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2
return 2
;;
esac
fi

local IP_ADDR=$1

# Convert the address using ipcalc which strips out the subnet.
# For IPv6 addresses, this will give the short-form address
IP_ADDR="$(ipcalc "${IP_ADDR}" | grep "^Address:" | awk '{print $2}')"

echo "$(ip $IP_VERS -br addr show scope global | grep -i " ${IP_ADDR}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')"
}

get_ip_of_interface()
{
local IP_VERS=""

if [[ "$#" -gt 2 ]]; then
echo "${FUNCNAME}: too many parameters" >&2
return 1
fi

if [[ "$#" -eq 2 ]]; then
case $2 in
4|6)
local IP_VERS="-${2}"
;;
*)
echo "${FUNCNAME}: the second parameter should be [4|6] (or missing for both)" >&2
return 2
;;
esac
fi

local IFACE=$1

echo "$(ip $IP_VERS -br addr show scope global up dev $IFACE | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)"
}

get_provisioning_interface()
{
if [[ -n "$PROVISIONING_INTERFACE" ]]; then
Expand All @@ -41,13 +122,7 @@ get_provisioning_interface()
return
fi

local interface="provisioning"

if [[ -n "${PROVISIONING_IP}" ]]; then
if ip -br addr show | grep -i " ${PROVISIONING_IP}/" &>/dev/null; then
interface="$(ip -br addr show | grep -i " ${PROVISIONING_IP}/" | cut -f 1 -d ' ' | cut -f 1 -d '@')"
fi
fi
local interface=""

for mac in ${PROVISIONING_MACS//,/ }; do
if ip -br link show up | grep -i "$mac" &>/dev/null; then
Expand All @@ -71,32 +146,103 @@ wait_for_interface_or_ip()
# available on an interface, otherwise we look at $PROVISIONING_INTERFACE
# for an IP
if [[ -n "${PROVISIONING_IP}" ]]; then
# Convert the address using ipcalc which strips out the subnet.
# For IPv6 addresses, this will give the short-form address
IRONIC_IP="$(ipcalc "${PROVISIONING_IP}" | grep "^Address:" | awk '{print $2}')"
export IRONIC_IP
until grep -F " ${IRONIC_IP}/" <(ip -br addr show); do
echo "Waiting for ${IRONIC_IP} to be configured on an interface"
local IFACE_OF_IP=""

until [[ -n "$IFACE_OF_IP" ]]; do
echo "Waiting for ${PROVISIONING_IP} to be configured on an interface..."
IFACE_OF_IP="$(get_interface_of_ip $PROVISIONING_IP)"
sleep 1
done
else
until [[ -n "$IRONIC_IP" ]]; do
echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured"
IRONIC_IP="$(ip -br add show scope global up dev "${PROVISIONING_INTERFACE}" | awk '{print $3}' | sed -e 's%/.*%%' | head -n 1)"
export IRONIC_IP

echo "Found $PROVISIONING_IP on interface \"${IFACE_OF_IP}\"!"

export PROVISIONING_INTERFACE="$IFACE_OF_IP"
# If the IP contains a colon, then it's an IPv6 address
if [[ "$PROVISIONING_IP" =~ .*:.* ]]; then
export IRONIC_IPV6="$PROVISIONING_IP"
else
export IRONIC_IP="$PROVISIONING_IP"
fi
elif [[ -n "${PROVISIONING_INTERFACE}" ]]; then
until [[ -n "$IRONIC_IPV6" ]] || [[ -n "$IRONIC_IP" ]]; do
echo "Waiting for ${PROVISIONING_INTERFACE} interface to be configured..."

export IRONIC_IPV6="$(get_ip_of_interface $PROVISIONING_INTERFACE 6)"
sleep 1

export IRONIC_IP="$(get_ip_of_interface $PROVISIONING_INTERFACE 4)"
sleep 1
done
fi

# If the IP contains a colon, then it's an IPv6 address, and the HTTP
# host needs surrounding with brackets
if [[ "$IRONIC_IP" =~ .*:.* ]]; then
export IPV=6
export IRONIC_URL_HOST="[$IRONIC_IP]"
if [[ -n "$IRONIC_IPV6" ]]; then
echo "Found $IRONIC_IPV6 on interface \"${PROVISIONING_INTERFACE}\"!"
fi
if [[ -n "$IRONIC_IP" ]]; then
echo "Found $IRONIC_IP on interface \"${PROVISIONING_INTERFACE}\"!"
fi
elif [[ -n "$IRONIC_URL_HOSTNAME" ]]; then
local IPV6_IFACE=""
local IPV4_IFACE=""

# we should get at least one IP address
until [[ -n "$IPV6_IFACE" ]] || [[ -n "$IPV4_IFACE" ]]; do
local IPV6_RECORD=""
local IPV4_RECORD=""

IPV6_RECORD="$(get_ip_of_hostname $IRONIC_URL_HOSTNAME 6)"
IPV4_RECORD="$(get_ip_of_hostname $IRONIC_URL_HOSTNAME 4)"

# We couldn't get any IP
if [[ -z "$IPV4_RECORD" ]] && [[ -z "$IPV6_RECORD" ]]; then
echo "${FUNCNAME}: no valid IP found for hostname $IRONIC_URL_HOSTNAME" >&2
return 1
fi

echo "Waiting for ${IPV6_RECORD} to be configured on an interface"
IPV6_IFACE="$(get_interface_of_ip $IPV6_RECORD 6)"
sleep 1

echo "Waiting for ${IPV4_RECORD} to be configured on an interface"
IPV4_IFACE="$(get_interface_of_ip $IPV4_RECORD 4)"
sleep 1
done

# Add some debugging output
if [[ -n "$IPV6_IFACE" ]]; then
echo "Found $IPV6_RECORD on interface \"${IPV6_IFACE}\"!"
export IRONIC_IPV6="$IPV6_RECORD"
fi
if [[ -n "$IPV4_IFACE" ]]; then
echo "Found $IPV4_RECORD on interface \"${IPV4_IFACE}\"!"
export IRONIC_IP="$IPV4_RECORD"
fi

# Make sure both IPs are asigned to the same interface
if [[ -n "$IPV6_IFACE" ]] && [[ -n "$IPV4_IFACE" ]] && [[ "$IPV6_IFACE" != "$IPV4_IFACE" ]]; then
echo "Warning, the IPv4 and IPv6 addresses from \"${HOSTNAME}\" are assigned to different " \
"interfaces (\"${IPV6_IFACE}\" and \"${IPV4_IFACE}\")" >&2
fi

else
export IPV=4
echo "Cannot determine an interface or an IP for binding and creating URLs"
return 1
fi

# Define the URLs based on the what we have found,
# prioritize IPv6 for IRONIC_URL_HOST
if [[ -n "$IRONIC_IP" ]]; then
export ENABLE_IPV4=yes
export IRONIC_URL_HOST="$IRONIC_IP"
fi
if [[ -n "$IRONIC_IPV6" ]]; then
export ENABLE_IPV6=yes
export IRONIC_URL_HOST="[$IRONIC_IPV6]" # The HTTP host needs surrounding with brackets
fi

# Once determined if we have IPv4 and/or IPv6, override the hostname if provided
if [[ -n "$IRONIC_URL_HOSTNAME" ]]; then
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both PROVISIONING_INTERFACE and IRONIC_URL_HOSTNAME are provided we could (or maybe should) check that the IP addresses returned by both are coherent.

IRONIC_URL_HOST=$IRONIC_URL_HOSTNAME
fi

# Avoid having to construct full URL multiple times while allowing
# the override of IRONIC_HTTP_URL for environments in which IRONIC_IP
Expand Down
Loading