Skip to content

Commit 61ef30b

Browse files
committed
[update] Add complete IPv6 support to workload launch script
Add comprehensive IPv6 support for RHOSO deployments in the update workload launch infrastructure. This enables workload testing on `IPv6` environments that were previously unsupported. Here IPv6-only means that no IPv4 routing is defined at all on the `controller-0` and on the `openstackclient` pod. This does support dual stack `IPv6/IPv4` public network. For `IPv6` we directly attach to the `IPv6` public subnet as there is no `FIP` in `IPv6` OpenStack. To make sure that OVN distribute the `RA` to the `cirros` instance we create a dummy router that create the necessary structure in `ovn`. Limitation: SRIOV validation is not supported in IPv6 only setup. Maintains full backward compatibility with existing IPv4 workflows. Signed-off-by: Sofer Athlan-Guyot <sathlang@redhat.com> Partially-Closes: [OSPCIX-1114](https://issues.redhat.com/browse/OSPCIX-1114)
1 parent d790c49 commit 61ef30b

File tree

2 files changed

+198
-71
lines changed

2 files changed

+198
-71
lines changed

roles/update/templates/l3_agent_start_ping.sh.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ fi
2525
# we will make the awk wait until this ping exits as it will hold
2626
# the output pipes. So again &>> instead of >> is necessary.
2727

28+
# Modern ping can handle both IPv4 and IPv6 addresses automatically
2829
ping -D "${VM_IP}" &>> "${PING_LOG}" &

roles/update/templates/workload_launch.sh.j2

Lines changed: 197 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,18 @@ function os_cmd {
4343
function set_vm_ip {
4444
## assign floating ip or external ip
4545
local workload_sriov={{workload_sriov|default(false) | bool | ternary("True", "")}}
46+
4647
if [ -n "${workload_sriov}" ]; then
4748
EXTERNAL_IP=$(os_cmd port show "${SRIOV_PORT}" -f yaml -c fixed_ips | awk '/ip_address/{print $3;exit}')
4849
VM_IP=${EXTERNAL_IP}
50+
elif [ "${IS_IPV6}" = "True" ]; then
51+
# For IPv6, get the direct IP from the instance (no floating IPs)
52+
VM_IP=$(os_cmd server show ${INSTANCE_NAME} -f value -c addresses | grep -oE "2620:[^']+" | head -1)
53+
if [ -z "${VM_IP}" ]; then
54+
echo "Cannot get IPv6 address from instance"
55+
exit 66
56+
fi
57+
echo "Using direct IPv6 address: ${VM_IP}"
4958
else
5059
INSTANCE_FIP=$(os_cmd floating ip create -f value -c floating_ip_address "${EXTERNAL_NET_NAME}")
5160
if [ -z "${INSTANCE_FIP}" ]; then
@@ -146,7 +155,19 @@ function prepare_env {
146155
export INSTANCE_FILE="${HOME}/{{ cifmw_update_artifacts_basedir_suffix }}/vm_info.sh"
147156
export WORKLOAD_FILE="${HOME}/{{ cifmw_update_artifacts_basedir_suffix }}/workload_suffix"
148157
export SSH_KEY_FILE="${HOME}/.ssh/${KEYPAIR_NAME}"
158+
# Detect IPv6 subnet dynamically at runtime
159+
export IPV6_SUBNET_ID=$(openstack subnet list --network "${EXTERNAL_NET_NAME}" --ip-version 6 -f value -c ID | head -1)
160+
export IS_IPV6=$([ -n "${IPV6_SUBNET_ID}" ] && echo "True" || echo "False")
161+
export IPV6_PORT_NAME="ipv6_port_${SUFFIX}"
162+
export IPV6_PORT_ID="" # Track port ID for IPv6 cleanup
149163
mkdir -p "${HOME}/{{ cifmw_update_artifacts_basedir_suffix }}"
164+
165+
# Validate SRIOV is not used with IPv6 (untested configuration)
166+
local workload_sriov={{workload_sriov|default(false) | bool | ternary("True", "")}}
167+
if [ "${IS_IPV6}" = "True" ] && [ -n "${workload_sriov}" ]; then
168+
echo "ERROR: SRIOV configuration is not supported with IPv6 deployments (untested)"
169+
exit 1
170+
fi
150171
}
151172

152173
function sanity_check {
@@ -187,20 +208,42 @@ function sanity_teardown {
187208

188209
local timeout_seconds=${1:-180}
189210
local elapsed_seconds=0
211+
190212
{% if workload_sriov|default(false) | bool -%}
191-
openstack port delete "${SRIOV_PORT}"
213+
if [ -n "${SRIOV_PORT}" ]; then
214+
echo "Deleting SRIOV port ${SRIOV_PORT}"
215+
openstack port delete "${SRIOV_PORT}" || echo "Warning: Failed to delete SRIOV port ${SRIOV_PORT}"
216+
fi
192217
{% elif workload_dpdk|default(false) | bool -%}
193-
openstack port delete "${DPDK_PORT}"
218+
if [ -n "${DPDK_PORT}" ]; then
219+
echo "Deleting DPDK port ${DPDK_PORT}"
220+
openstack port delete "${DPDK_PORT}" || echo "Warning: Failed to delete DPDK port ${DPDK_PORT}"
221+
fi
194222
{% else -%}
195-
if [ -n "${INSTANCE_FIP}" ]; then
196-
echo "Remove ${INSTANCE_FIP} from ${INSTANCE_NAME}"
197-
openstack server remove floating ip ${INSTANCE_NAME} ${INSTANCE_FIP}
198-
grep "${INSTANCE_FIP}" "${INSTANCE_FILE}" && rm "${INSTANCE_FILE}"
223+
if [ "${IS_IPV6}" = "True" ]; then
224+
# For IPv6, clean up the dedicated port
225+
if [ -n "${IPV6_PORT_ID}" ]; then
226+
echo "IPv6 deployment - deleting port ${IPV6_PORT_NAME} (${IPV6_PORT_ID})"
227+
openstack port delete "${IPV6_PORT_ID}" || echo "Warning: Failed to delete port ${IPV6_PORT_ID}"
228+
elif [ -n "${IPV6_PORT_NAME}" ]; then
229+
echo "IPv6 deployment - deleting port ${IPV6_PORT_NAME} by name"
230+
openstack port delete "${IPV6_PORT_NAME}" || echo "Warning: Failed to delete port ${IPV6_PORT_NAME}"
231+
else
232+
echo "IPv6 deployment - no port to clean up"
233+
fi
234+
else
235+
# For IPv4, clean up floating IP
236+
if [ -n "${INSTANCE_FIP}" ]; then
237+
echo "Remove ${INSTANCE_FIP} from ${INSTANCE_NAME}"
238+
openstack server remove floating ip ${INSTANCE_NAME} ${INSTANCE_FIP} || echo "Warning: Failed to remove floating IP from instance"
239+
if [ -f "${INSTANCE_FILE}" ] && grep -q "${INSTANCE_FIP}" "${INSTANCE_FILE}"; then
240+
rm "${INSTANCE_FILE}"
241+
fi
199242

200-
echo "Delete floating ip ${INSTANCE_FIP}"
201-
openstack floating ip delete ${INSTANCE_FIP}
243+
echo "Delete floating ip ${INSTANCE_FIP}"
244+
openstack floating ip delete ${INSTANCE_FIP} || echo "Warning: Failed to delete floating IP ${INSTANCE_FIP}"
245+
fi
202246
fi
203-
204247
{%- endif %}
205248

206249
{% if cifmw_update_create_volume | default(True) | bool -%}
@@ -248,20 +291,25 @@ function sanity_teardown {
248291
fi
249292
{%- endif %}
250293

251-
echo "Clear default gateway from ${TENANT_NET_NAME}_router"
252-
openstack router unset --external-gateway "${TENANT_NET_NAME}"_router
294+
# Only clean up tenant network resources if they were created (IPv4 mode)
295+
if [ "${IS_IPV6}" != "True" ]; then
296+
echo "Clear default gateway from ${TENANT_NET_NAME}_router"
297+
openstack router unset --external-gateway "${TENANT_NET_NAME}"_router || echo "Warning: Failed to clear gateway"
253298

254-
echo "Remove subnet ${TENANT_NET_NAME}_subnet from router ${TENANT_NET_NAME}_router"
255-
openstack router remove subnet "${TENANT_NET_NAME}"_router "${TENANT_NET_NAME}"_subnet
299+
echo "Remove subnet ${TENANT_NET_NAME}_subnet from router ${TENANT_NET_NAME}_router"
300+
openstack router remove subnet "${TENANT_NET_NAME}"_router "${TENANT_NET_NAME}"_subnet || echo "Warning: Failed to remove subnet from router"
256301

257-
echo "Remove router ${TENANT_NET_NAME}_router"
258-
openstack router delete "${TENANT_NET_NAME}_router"
302+
echo "Remove router ${TENANT_NET_NAME}_router"
303+
openstack router delete "${TENANT_NET_NAME}_router" || echo "Warning: Failed to delete router"
259304

260-
echo "Remove subnet ${TENANT_NET_NAME}_subnet"
261-
openstack subnet delete "${TENANT_NET_NAME}_subnet"
305+
echo "Remove subnet ${TENANT_NET_NAME}_subnet"
306+
openstack subnet delete "${TENANT_NET_NAME}_subnet" || echo "Warning: Failed to delete subnet"
262307

263-
echo "Remove network ${TENANT_NET_NAME}"
264-
openstack network delete "${TENANT_NET_NAME}"
308+
echo "Remove network ${TENANT_NET_NAME}"
309+
openstack network delete "${TENANT_NET_NAME}" || echo "Warning: Failed to delete network"
310+
else
311+
echo "IPv6 deployment - skipping tenant network cleanup (using public network directly, bootstrap router persists)"
312+
fi
265313

266314
echo "Remove security group ${SECGROUP_NAME}"
267315
openstack security group delete "${SECGROUP_NAME}"
@@ -283,7 +331,7 @@ function sanity_teardown {
283331
function workload_launch {
284332
# create workload
285333
timeout_seconds=${1:-120}
286-
ssh_timeout_seconds=${1:-180}
334+
ssh_timeout_seconds=${2:-180}
287335
local workload_sriov={{workload_sriov|default(false) | bool | ternary("True", "")}}
288336
local workload_dpdk={{workload_dpdk|default(false) | bool | ternary("True", "")}}
289337

@@ -338,47 +386,82 @@ function workload_launch {
338386
fi
339387
fi
340388

341-
## create networking
342-
openstack network list | grep ${TENANT_NET_NAME}
343-
if [ $? -ne 0 ]; then
344-
NAMESERVER=$(grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /etc/resolv.conf | head -1)
345-
echo "Creating router ${TENANT_NET_NAME}_router"
346-
os_cmd router create ${TENANT_NET_NAME}_router
389+
## create networking for IPv4
390+
if [ "${IS_IPV6}" != "True" ]; then
391+
openstack network list | grep ${TENANT_NET_NAME}
392+
if [ $? -ne 0 ]; then
393+
# Collect all nameservers from resolv.conf
394+
NAMESERVERS=$(awk '/^nameserver[[:space:]]/ {print $2}' /etc/resolv.conf)
395+
396+
# Build DNS arguments
397+
DNS_ARGS=""
398+
if [ -n "$NAMESERVERS" ]; then
399+
for ns in $NAMESERVERS; do
400+
DNS_ARGS="$DNS_ARGS --dns-nameserver $ns"
401+
done
402+
else
403+
echo "Warning: No nameservers found, using public DNS fallback"
404+
DNS_ARGS="--dns-nameserver 8.8.8.8"
405+
fi
406+
echo "Creating router ${TENANT_NET_NAME}_router"
407+
os_cmd router create ${TENANT_NET_NAME}_router
408+
409+
echo "Creating network ${TENANT_NET_NAME}"
410+
if [ -n "${workload_dpdk}" ]; then
411+
os_cmd network create ${TENANT_NET_NAME} --provider-network-type geneve
412+
else
413+
os_cmd network create ${TENANT_NET_NAME}
414+
fi
347415

348-
echo "Creating network ${TENANT_NET_NAME}"
349-
if [ -n "${workload_dpdk}" ]; then
350-
os_cmd network create ${TENANT_NET_NAME} --provider-network-type geneve
351-
else
352-
os_cmd network create ${TENANT_NET_NAME}
353-
fi
416+
echo "Creating subnet ${TENANT_NET_NAME}_subnet"
417+
os_cmd subnet create \
418+
--subnet-range 192.168.0.0/24 \
419+
--allocation-pool start=192.168.0.10,end=192.168.0.100 \
420+
--gateway 192.168.0.254 \
421+
$DNS_ARGS \
422+
--network ${TENANT_NET_NAME} \
423+
${TENANT_NET_NAME}_subnet
354424

355-
echo "Creating subnet ${TENANT_NET_NAME}_subnet"
356-
os_cmd subnet create \
357-
--subnet-range 192.168.0.0/24 \
358-
--allocation-pool start=192.168.0.10,end=192.168.0.100 \
359-
--gateway 192.168.0.254 \
360-
--dns-nameserver ${NAMESERVER} \
361-
--network ${TENANT_NET_NAME} \
362-
${TENANT_NET_NAME}_subnet
425+
echo "Add subnet ${TENANT_NET_NAME}_subnet to router ${TENANT_NET_NAME}_router"
426+
os_cmd router add subnet ${TENANT_NET_NAME}_router ${TENANT_NET_NAME}_subnet
363427

364-
echo "Add subnet ${TENANT_NET_NAME}_subnet to router ${TENANT_NET_NAME}_router"
365-
os_cmd router add subnet ${TENANT_NET_NAME}_router ${TENANT_NET_NAME}_subnet
428+
echo "Set external-gateway for ${TENANT_NET_NAME}_router"
429+
os_cmd router set --external-gateway ${EXTERNAL_NET_NAME} ${TENANT_NET_NAME}_router
430+
fi
431+
else
432+
# For IPv6, ensure OVN Router Advertisement bootstrap router exists
433+
# This is infrastructure-level and persists across all workload operations
434+
openstack router list | grep "ovn-ipv6-bootstrap-router"
435+
if [ $? -ne 0 ]; then
436+
echo "Creating OVN IPv6 bootstrap router (infrastructure-level, required for SLAAC)"
437+
os_cmd router create ovn-ipv6-bootstrap-router
366438

367-
echo "Set external-gateway for ${TENANT_NET_NAME}_router"
368-
os_cmd router set --external-gateway ${EXTERNAL_NET_NAME} ${TENANT_NET_NAME}_router
439+
echo "Set external-gateway for OVN IPv6 bootstrap router"
440+
os_cmd router set --external-gateway ${EXTERNAL_NET_NAME} ovn-ipv6-bootstrap-router
441+
fi
369442
fi
370-
371443
## create security group
372444
openstack security group list | grep ${SECGROUP_NAME}
373445
if [ $? -ne 0 ]; then
374446
echo "Creating security group ${SECGROUP_NAME}"
375447
os_cmd security group create ${SECGROUP_NAME}
376448

449+
# Set protocol and ethertype based on IP version
450+
if [ "${IS_IPV6}" = "True" ]; then
451+
ICMP_PROTO="ipv6-icmp"
452+
ETHERTYPE_ARG="--ethertype IPv6"
453+
echo "Creating IPv6 rules for ports 22,80,443 in security group ${SECGROUP_NAME}"
454+
else
455+
ICMP_PROTO="icmp"
456+
ETHERTYPE_ARG=""
457+
echo "Creating IPv4 rules for ports 22,80,443 in security group ${SECGROUP_NAME}"
458+
fi
459+
377460
echo "Creating rules for ports 22,80,443 in security group ${SECGROUP_NAME}"
378-
os_cmd security group rule create --proto icmp ${SECGROUP_NAME}
379-
os_cmd security group rule create --proto tcp --dst-port 22 ${SECGROUP_NAME}
380-
os_cmd security group rule create --proto tcp --dst-port 80 ${SECGROUP_NAME}
381-
os_cmd security group rule create --proto tcp --dst-port 443 ${SECGROUP_NAME}
461+
os_cmd security group rule create --proto ${ICMP_PROTO} ${ETHERTYPE_ARG} ${SECGROUP_NAME}
462+
os_cmd security group rule create --proto tcp --dst-port 22 ${ETHERTYPE_ARG} ${SECGROUP_NAME}
463+
os_cmd security group rule create --proto tcp --dst-port 80 ${ETHERTYPE_ARG} ${SECGROUP_NAME}
464+
os_cmd security group rule create --proto tcp --dst-port 443 ${ETHERTYPE_ARG} ${SECGROUP_NAME}
382465
fi
383466

384467
## create sriov port
@@ -392,27 +475,55 @@ function workload_launch {
392475
openstack port create --vnic-type normal --network "${TENANT_NET_NAME=}" "${DPDK_PORT}"
393476
{%- endif %}
394477

478+
## create IPv6 port for dual-stack deployments
479+
if [ "${IS_IPV6}" = "True" ]; then
480+
echo "Creating IPv6 port ${IPV6_PORT_NAME} on public network"
481+
IPV6_PORT_ID=$(os_cmd port create \
482+
--network "${EXTERNAL_NET_NAME}" \
483+
--fixed-ip subnet="${IPV6_SUBNET_ID}" \
484+
--security-group "${SECGROUP_NAME}" \
485+
"${IPV6_PORT_NAME}" -f value -c id)
486+
487+
if [ -z "${IPV6_PORT_ID}" ]; then
488+
echo "Failed to create IPv6 port"
489+
exit 1
490+
fi
491+
echo "Created IPv6 port ${IPV6_PORT_NAME} with ID ${IPV6_PORT_ID}"
492+
fi
395493

396-
## create instance
397-
TENANT_NET_ID=$( openstack network show -f value -c id "${TENANT_NET_NAME}" )
398494

399-
echo "Creating overcloud instance ${INSTANCE_NAME}"
400-
{% if workload_sriov|default(false) | bool -%}
401-
os_cmd server create \
402-
--image "${IMAGE_NAME}" \
403-
--flavor "${FLAVOR_NAME}" \
404-
--key-name "${KEYPAIR_NAME}" \
405-
--port "${SRIOV_PORT}" \
406-
"${INSTANCE_NAME}"
407-
{% else -%}
408-
os_cmd server create \
409-
--image "${IMAGE_NAME}" \
410-
--flavor "${FLAVOR_NAME}" \
411-
--security-group "${SECGROUP_NAME}" \
412-
--key-name "${KEYPAIR_NAME}" \
413-
--nic net-id="${TENANT_NET_ID}" \
414-
"${INSTANCE_NAME}"
415-
{%- endif %}
495+
## create instance
496+
if [ "${IS_IPV6}" = "True" ]; then
497+
# For IPv6, use the pre-created port with IPv6 subnet constraint
498+
echo "Creating overcloud instance ${INSTANCE_NAME} with IPv6 port (dual-stack)"
499+
# Note: SRIOV unsupported in IPv6 only setup (validated in prepare_env)
500+
os_cmd server create \
501+
--image "${IMAGE_NAME}" \
502+
--flavor "${FLAVOR_NAME}" \
503+
--key-name "${KEYPAIR_NAME}" \
504+
--port "${IPV6_PORT_NAME}" \
505+
"${INSTANCE_NAME}"
506+
else
507+
# For IPv4, use tenant network (original behavior)
508+
TENANT_NET_ID=$( openstack network show -f value -c id "${TENANT_NET_NAME}" )
509+
echo "Creating overcloud instance ${INSTANCE_NAME} on tenant network (IPv4)"
510+
{% if workload_sriov|default(false) | bool -%}
511+
os_cmd server create \
512+
--image "${IMAGE_NAME}" \
513+
--flavor "${FLAVOR_NAME}" \
514+
--key-name "${KEYPAIR_NAME}" \
515+
--port "${SRIOV_PORT}" \
516+
"${INSTANCE_NAME}"
517+
{% else -%}
518+
os_cmd server create \
519+
--image "${IMAGE_NAME}" \
520+
--flavor "${FLAVOR_NAME}" \
521+
--security-group "${SECGROUP_NAME}" \
522+
--key-name "${KEYPAIR_NAME}" \
523+
--nic net-id="${TENANT_NET_ID}" \
524+
"${INSTANCE_NAME}"
525+
{% endif %}
526+
fi
416527

417528
elapsed_seconds=0
418529
while true; do
@@ -498,7 +609,12 @@ if [[ "${MODE}" == "workload" ]]; then
498609
{% if cifmw_update_create_volume | default(true) | bool -%}
499610
echo "export CINDER_VOL_ID=${CINDER_VOL_ID}" >> "${WORKLOAD_FILE}"
500611
{% endif -%}
501-
echo "export INSTANCE_FIP=${INSTANCE_FIP}" >> "${WORKLOAD_FILE}"
612+
if [ "${IS_IPV6}" = "True" ]; then
613+
echo "export IPV6_PORT_ID=${IPV6_PORT_ID}" >> "${WORKLOAD_FILE}"
614+
echo "export IPV6_PORT_NAME=${IPV6_PORT_NAME}" >> "${WORKLOAD_FILE}"
615+
else
616+
echo "export INSTANCE_FIP=${INSTANCE_FIP}" >> "${WORKLOAD_FILE}"
617+
fi
502618
fi
503619

504620
if [[ "${MODE}" == "workload_traffic" ]]; then
@@ -508,7 +624,12 @@ if [[ "${MODE}" == "workload_traffic" ]]; then
508624
workload_launch
509625
echo "export SUFFIX=${SUFFIX}" > ~/workload_suffix
510626
echo "export CINDER_VOL_ID=${CINDER_VOL_ID}" >> ~/workload_suffix
511-
echo "export INSTANCE_FIP=${INSTANCE_FIP}" >> ~/workload_suffix
627+
if [ "${IS_IPV6}" = "True" ]; then
628+
echo "export IPV6_PORT_ID=${IPV6_PORT_ID}" >> ~/workload_suffix
629+
echo "export IPV6_PORT_NAME=${IPV6_PORT_NAME}" >> ~/workload_suffix
630+
else
631+
echo "export INSTANCE_FIP=${INSTANCE_FIP}" >> ~/workload_suffix
632+
fi
512633
echo "export VM_IP=${VM_IP}" > ~/vm_ip.sh
513634
generate_traffic
514635
fi
@@ -536,5 +657,10 @@ if [[ "${MODE}" == "sanityfast" ]]; then
536657
SUFFIX=$(openssl rand -hex 5)
537658
prepare_env
538659
sanity_check $FAST
539-
workload_launch $FAST $FAST
660+
if [ "${IS_IPV6}" = "True" ]; then
661+
# IPv6 needs longer SSH timeout due to DHCP timeout, but keep boot timeout fast
662+
workload_launch $FAST 180
663+
else
664+
workload_launch $FAST $FAST
665+
fi
540666
fi

0 commit comments

Comments
 (0)