start:makedhcp_n
description:Create a new dhcp configuration file with a network statement for each network the dhcp daemon should listen on
os:Linux
label:mn_only,ci_test,dhcp
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then cp -f /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak ; elif [ -f "/etc/dhcpd.conf" ]; then cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak; fi; if [ -f "/etc/kea/kea-dhcp4.conf" ]; then cp -f /etc/kea/kea-dhcp4.conf /etc/kea/kea-dhcp4.conf.bak; fi
cmd:makedhcp -n
check:rc==0
cmd:cat /etc/dhcp/dhcpd.conf 2>/dev/null || cat /etc/dhcpd.conf 2>/dev/null || cat /etc/kea/kea-dhcp4.conf 2>/dev/null
check:rc==0
cmd:sleep 20
cmd:ps -e | grep -E "dhcpd|kea-dhcp4" | grep -v grep
check:rc==0
#The Kea unit is kea-dhcp4 on EL and kea-dhcp4-server on Debian. Probe both names.
cmd:for u in kea-dhcp4 kea-dhcp4-server; do if systemctl is-active $u >/dev/null 2>&1; then systemctl status $u; exit 0; fi; done; if cat /etc/*release | grep Ubuntu >/dev/null; then service isc-dhcp-server status; else service dhcpd status; fi
check:output=~running
cmd:if [ -f "/etc/dhcp/dhcpd.conf.bak" ]; then mv -f /etc/dhcp/dhcpd.conf.bak /etc/dhcp/dhcpd.conf; elif [ -f "/etc/dhcpd.conf.bak" ]; then mv -f /etc/dhcpd.conf.bak /etc/dhcpd.conf; fi; if [ -f "/etc/kea/kea-dhcp4.conf.bak" ]; then mv -f /etc/kea/kea-dhcp4.conf.bak /etc/kea/kea-dhcp4.conf; fi
end

start:makedhcp_h
description:help
label:mn_only,ci_test,dhcp
cmd:makedhcp -h
check:rc==0
check:output=~Usage
end

start:makedhcp_help
description:help
label:mn_only,ci_test,dhcp
cmd:makedhcp -h
check:rc==0
check:output=~Usage
end

start:makedhcp_n_linux
description:Create a new dhcp configuration file with a network statement for each network the dhcp daemon should listen on
label:others
os:Linux
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then cp -f /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak ; elif [ -f "/etc/dhcpd.conf" ]; then cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak; fi
cmd:makedhcp -n
check:rc==0
cmd:ls /etc/dhcp/dhcpd.conf || ls /etc/dhcpd.conf
check:rc==0
cmd:ps -e | grep dhcpd
check:rc==0
check:output=~dhcpd
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then mv -f /etc/dhcp/dhcpd.conf.bak /etc/dhcp/dhcpd.conf ; elif [ -f "/etc/dhcpd.conf" ]; then mv -f /etc/dhcpd.conf.bak /etc/dhcpd.conf; fi
end

start:makedhcp_a_linux
description:Define all nodes to the DHCP server. (Will only add nodes that can be reached, network-wise, by this DHCP server.) The dhcp daemon does not have to be restarted after this.
os:Linux
label:mn_only,dhcp
cmd:mkdef -t node -o testnode1 groups=compute mac=11:22:33:55:66:88 arch=ppc64
cmd:chdef -t node -o testnode1 netboot=yaboot tftpserver=192.16.10.0 nfsserver=192.16.10.0 monserver=192.16.10.0 xcatmaster=192.16.10.0 installnic="mac" primarynic="mac"
cmd:lsdef -l testnode1 -z | tee /tmp/CN.stanza
cmd:chdef -t node -o testnode1 mac=11:22:33:44:55:66
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then cp -f /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak ; elif [ -f "/etc/dhcpd.conf" ]; then cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak; fi
cmd:makedhcp -n
cmd:makedhcp -a
check:rc==0
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then cat /var/lib/dhcpd/dhcpd.leases; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then cat /var/lib/dhcp/db/dhcpd.leases; elif [ -f "/var/lib/dhcp/dhcpd.leases" ]; then cat /var/lib/dhcp/dhcpd.leases; fi
check:output=~testnode1
check:output=~11:22:33:44:55:66
cmd:cat /tmp/CN.stanza | chdef -z
cmd:rmdef testnode1
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then mv -f /etc/dhcp/dhcpd.conf.bak /etc/dhcp/dhcpd.conf ; elif [ -f "/etc/dhcpd.conf" ]; then mv -f /etc/dhcpd.conf.bak /etc/dhcpd.conf; fi
end

start:makedhcp_a_linux_check_invalid_mac
description:Define all nodes to the DHCP server. If mac format is invalid, makedhcp -a print error and return 1
os:Linux
label:mn_only,dhcp
cmd:lsdef -l $$CN -z > /tmp/$$CN.stanza
cmd:chdef -t node -o $$CN mac=11:22:33
cmd:makedhcp $$CN
check:rc!=0
check:output!~ ^11:22:33$
cmd:chdef -t node -o $$CN mac=
cmd:cat /tmp/$$CN.stanza | chdef -z
end

start:makedhcp_a_d_linux
os:Linux
label:mn_only,dhcp
cmd:lsdef -t node -l -z > /tmp/all.nodes
cmd:mkdef -t node -o testnode1 groups=compute mac=11:22:33:55:66:88 arch=ppc64
cmd:mkdef -t node -o testnode2 groups=compute mac=11:22:33:55:66:99 arch=ppc64
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then cp -f /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak ; elif [ -f "/etc/dhcpd.conf" ]; then cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak; fi
cmd:makedhcp -n
cmd:makedhcp -a
check:rc==0
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv > /tmp/1; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then cat /var/lib/dhcpd/dhcpd.leases > /tmp/1; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then cat /var/lib/dhcp/db/dhcpd.leases > /tmp/1;elif [ -f /var/lib/dhcp/dhcpd.leases ]; then cat /var/lib/dhcp/dhcpd.leases > /tmp/1; fi
cmd:makedhcp -a -d
check:rc==0
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv > /tmp/2; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then cat /var/lib/dhcpd/dhcpd.leases > /tmp/2; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then cat /var/lib/dhcp/db/dhcpd.leases > /tmp/2;elif [ -f /var/lib/dhcp/dhcpd.leases ]; then cat /var/lib/dhcp/dhcpd.leases > /tmp/2; fi
cmd:diff /tmp/1 /tmp/2
check:output=~testnode1
check:output=~deleted
check:output=~testnode2
cmd:cat /tmp/all.nodes | chdef -z
cmd:rmdef testnode1
cmd:rmdef testnode2
cmd:rm -f /tmp/1 /tmp/2
end

start:makedhcp_d_linux
description:Delete all node entries, added by xCAT, in the noderange from the DHCP server configuration.
os:Linux
label:mn_only,dhcp
cmd:mkdef -t node -o testnode1 groups=compute mac=11:22:33:55:66:88 arch=ppc64
cmd:chdef -t node -o testnode1 netboot=yaboot tftpserver=192.16.10.0 nfsserver=192.16.10.0 monserver=192.16.10.0 xcatmaster=192.16.10.0 installnic="mac" primarynic="mac"
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ];then cp -f /etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf.bak ; elif [ -f "/etc/dhcpd.conf" ]; then cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak; fi
cmd:lsdef -l testnode1 -z > /tmp/CN.stanza
cmd:chdef -t node -o testnode1 mac=11:22:33:44:55:66
cmd:makedhcp -n
cmd:makedhcp testnode1
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv > /tmp/1; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then cat /var/lib/dhcpd/dhcpd.leases > /tmp/1; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then cat /var/lib/dhcp/db/dhcpd.leases > /tmp/1;elif [ -f /var/lib/dhcp/dhcpd.leases ]; then cat /var/lib/dhcp/dhcpd.leases > /tmp/1; fi
cmd:makedhcp -d testnode1
check:rc==0
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv > /tmp/2; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then cat /var/lib/dhcpd/dhcpd.leases > /tmp/2; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then cat /var/lib/dhcp/db/dhcpd.leases > /tmp/2;elif [ -f /var/lib/dhcp/dhcpd.leases ]; then cat /var/lib/dhcp/dhcpd.leases > /tmp/2; fi
cmd:diff /tmp/1 /tmp/2
check:output=~testnode1
check:output=~deleted
cmd:cat /tmp/CN.stanza | chdef -z
cmd:rmdef testnode1
cmd:if [ -f "/etc/dhcp/dhcpd.conf" ]; then mv -f /etc/dhcp/dhcpd.conf.bak /etc/dhcp/dhcpd.conf; elif [ -f "/etc/dhcpd.conf" ]; then mv -f /etc/dhcpd.conf.bak /etc/dhcpd.conf; fi
cmd:rm -f /tmp/1 /tmp/2
end

start:makedhcp_a
os:Linux
label:cn_os_ready,dhcp
cmd:lsdef -l $$CN -z > /tmp/$$CN.stanza
cmd:chdef -t node -o $$CN mac=11:22:33:44:55:66
cmd:makedhcp -a
check:rc==0
cmd:makedhcp -q $$CN
check:rc==0
check:output=~$$CN
check:output=~11:22:33:44:55:66
cmd:chdef -t node -o $$CN mac=
cmd:cat /tmp/$$CN.stanza | chdef -z
end

start:makedhcp_a_ubuntu
os:ubuntu
label:cn_os_ready,dhcp
cmd:lsdef -l $$CN -z > /tmp/$$CN.stanza
cmd:chdef -t node -o $$CN mac=11:22:33:44:55:66
cmd:makedhcp -a
check:rc==0
#makedhcp -a writes a reservation into the server configuration, never into a lease file.
cmd:if [ -f /etc/kea/kea-dhcp4.conf ]; then cat /etc/kea/kea-dhcp4.conf; elif [ -f /etc/dhcp/dhcpd.conf ]; then cat /etc/dhcp/dhcpd.conf; fi
check:output=~$$CN
check:output=~11:22:33:44:55:66
cmd:chdef -t node -o $$CN mac=
cmd:cat /tmp/$$CN.stanza | chdef -z
end

start:makedhcp_a_d
os:Linux
label:cn_os_ready,dhcp
cmd:makedhcp -a -d
check:rc==0
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/restart_dhcp.sh
cmd:makedhcp -q $$CN
check:rc==0
check:output!~$$CN
cmd:makedhcp -a
end

start:makedhcp_a_d_ubuntu
os:ubuntu
label:cn_os_ready,dhcp
cmd:makedhcp -a -d
check:rc==0
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/restart_dhcp.sh
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then cat /var/lib/kea/kea-leases4.csv | grep $$CN; elif [ -f /var/lib/dhcp/dhcpd.leases ]; then cat /var/lib/dhcp/dhcpd.leases | grep $$CN;  fi
check:output!~$$CN
cmd:makedhcp -a
end

start:makedhcp_d
os:Linux
label:cn_os_ready,dhcp
cmd:makedhcp -d $$CN
check:rc==0
cmd:/opt/xcat/share/xcat/tools/autotest/testcase/commoncmd/restart_dhcp.sh
cmd:makedhcp -q $$CN
check:output!~$$CN
cmd:makedhcp -a
end

start:makedhcp_remote_network
descriptiion:This case is to test when there is mgtifname='!remote!<nicname>', makedhcp could work correctly and create entrys in dhcp lease file.
os:linux
label:mn_only,dhcp
cmd:mkdef -t network -o testnetwork net=100.100.100.0 mask=255.255.255.0 mgtifname='!remote!eth0' gateway=100.100.100.1
check:rc==0
cmd:lsdef -t network
check:rc==0
check:output=~testnetwork
cmd:mkdef -t node -o testnode ip=100.100.100.2 groups=all mac=42:3d:0a:05:27:0b
check:rc==0
cmd:cat /etc/hosts
cmd:echo -e "\n100.100.100.2 testnode" >> /etc/hosts
check:rc==0
cmd:cat /etc/hosts
cmd:makedhcp -n
check:rc==0
cmd:makedhcp -d testnode
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then a="/var/lib/kea/kea-leases4.csv"; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then a="/var/lib/dhcpd/dhcpd.leases"; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then a="/var/lib/dhcp/db/dhcpd.leases"; elif [ -f "/var/lib/dhcp/dhcpd.leases" ]; then a="/var/lib/dhcp/dhcpd.leases";fi; ls -l $a; cat $a
cmd:makedhcp testnode
check:rc==0
cmd:if [ -f /var/lib/kea/kea-leases4.csv ]; then a="/var/lib/kea/kea-leases4.csv"; elif [ -f /var/lib/dhcpd/dhcpd.leases ]; then a="/var/lib/dhcpd/dhcpd.leases"; elif [ -f /var/lib/dhcp/db/dhcpd.leases ]; then a="/var/lib/dhcp/db/dhcpd.leases"; elif [ -f "/var/lib/dhcp/dhcpd.leases" ]; then a="/var/lib/dhcp/dhcpd.leases";fi; ls -l $a; cat $a
cmd:a=2;while true; do [ $a -eq 64 ] && exit 1;output=$(makedhcp -q testnode);[ $? -ne 0 ] && exit 1;echo $output|grep testnode 2>/dev/null && exit 0;a=$[$a*2]; makedhcp testnode; sleep $a;done
check:rc==0
check:output=~testnode: ip-address = 100.100.100.2
cmd:makedhcp -d testnode
check:rc==0
cmd:makedhcp -n
check:rc==0
cmd:noderm testnode
check:rc==0
cmd:chtab -d netname=testnetwork networks
check:rc==0
cmd:cp -f /etc/hosts.bak /etc/hosts
end

start:makedhcp_dhcpv6_boot_intent
description:makedhcp writes a DHCPv6 reservation whose DUID and bootfile-url a UEFI client can use
label:mn_only,ipv6
# PRE-CLEAN, because xcattest stops a case at its first failed check and the cleanup at the end
# would then not run. Without this a failed run leaves an address, a network and a node behind and
# the next run measures the leftovers.
cmd:sh -c 'makedhcp -d ci-ipv6-cn >/dev/null 2>&1; makehosts -d ci-ipv6-cn >/dev/null 2>&1; chtab -d node=ci-ipv6-cn vpd >/dev/null 2>&1; rmdef -t node ci-ipv6-cn >/dev/null 2>&1; rmdef -t network ci-ipv6 >/dev/null 2>&1; ip -6 addr del 2001:db8:176::1/64 dev $$SECONDNIC >/dev/null 2>&1; true'
check:rc==0
# An IPv6 address on the provisioning NIC, and the xCAT network that matches it. The prefix is
# documentation-reserved (RFC 3849), so nothing it names can be reached off this bridge.
cmd:ip -6 addr add 2001:db8:176::1/64 dev $$SECONDNIC
check:rc==0
cmd:mkdef -t network -o ci-ipv6 net=2001:db8:176:: mask=/64 mgtifname=$$SECONDNIC dynamicrange=2001:db8:176::1000-2001:db8:176::1fff
check:rc==0
# A node that answers only on IPv6. Its uuid is fixed, because the DUID asserted below is derived
# from it and a generated uuid would make the expected value unknowable.
cmd:mkdef -t node ci-ipv6-cn groups=all mgt=kvm netboot=grub2 ip=2001:db8:176::20 mac=aa:bb:cc:dd:ee:01
check:rc==0
cmd:chtab node=ci-ipv6-cn vpd.uuid=c78c76bf-3286-46f3-b80d-8fe079dd6de7
check:rc==0
cmd:makehosts ci-ipv6-cn
check:rc==0
# makedhcp MUST SUCCEED on an IPv6-only node. It exited 1 while writing the reservation correctly,
# because the IPv4 next-server resolver ran before the loop that skips such a node.
cmd:makedhcp -n
check:rc==0
cmd:makedhcp ci-ipv6-cn
check:rc==0
# Kea must accept its own configuration. A reservation xCAT renders and Kea rejects serves nobody.
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
# The DUID, read out of the JSON and compared EXACTLY. Without the duid attribute it is the one
# that EDK2 firmware sends: SMBIOS stores the first three UUID fields little-endian, so these
# octets are the uuid above with those fields reversed. A grep for "duid" would pass on the key
# alone.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print("".join(r.get("duid","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-cn"))'
check:rc==0
check:output==00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7
# RFC 5970 bootfile-url, with the authority bracketed. The address is this management node's own
# IPv6 address on the provisioning NIC.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print("".join(o.get("data","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-cn" for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==tftp://[2001:db8:176::1]/boot/grub2/grub2-ci-ipv6-cn
# duid=canonical reserves the address for a client that sends the uuid in RFC 4122 byte order.
cmd:chdef -t node ci-ipv6-cn duid=canonical
check:rc==0
cmd:makedhcp ci-ipv6-cn
check:rc==0
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print(" ".join(r.get("duid","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-cn"))'
check:rc==0
check:output==00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7
# A value that is neither a policy nor a DUID fails, and the reservation stays as it was.
cmd:chdef -t node ci-ipv6-cn duid=not-a-duid
check:rc==0
cmd:makedhcp ci-ipv6-cn
check:rc!=0
check:output=~The duid attribute of ci-ipv6-cn is 'not-a-duid'
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print(" ".join(r.get("duid","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-cn"))'
check:rc==0
check:output==00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7
cmd:chdef -t node ci-ipv6-cn duid=
check:rc==0
# And the IPv4 side must carry NOTHING for this node: an IPv6-only node gets no DHCPv4
# reservation and no client class, not even a DROP class.
cmd:python3 -c 'import json,os;p="/etc/kea/kea-dhcp4.conf";d=json.load(open(p)) if os.path.exists(p) else {"Dhcp4":{}};print(sum(1 for s in d["Dhcp4"].get("subnet4",[]) for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-cn")+sum(1 for c in d["Dhcp4"].get("client-classes",[]) if "ci-ipv6-cn" in c.get("name","")))'
check:rc==0
check:output==0
# Leave the management node as it was found.
cmd:sh -c 'makedhcp -d ci-ipv6-cn >/dev/null 2>&1; makehosts -d ci-ipv6-cn; chtab -d node=ci-ipv6-cn vpd; rmdef -t node ci-ipv6-cn; rmdef -t network ci-ipv6; ip -6 addr del 2001:db8:176::1/64 dev $$SECONDNIC; true'
check:rc==0
end

start:makedhcp_dhcpv6_ipxe_boot_intent
description:makedhcp reserves a netboot=ipxe node for both of its DHCPv6 clients, each with its own boot file
label:mn_only,ipv6
# A netboot=ipxe node is TWO DHCPv6 clients. The firmware solicits first and is given the
# Microsoft-signed shim over TFTP; the iPXE it chainloads solicits again with a DUID built from
# the same vpd.uuid in RFC 4122 order and is given the boot script of the node over HTTP. The
# shim reads ipxe.efi from the directory it came from, which iPXE cannot do, so an iPXE client
# given the shim again stops at "Failed to open ipxe.efi".
#
# Its sibling makedhcp_dhcpv6_boot_intent defines its node netboot=grub2, so no case asserted the
# pair and the cell could stay green with every iPXE line of dhcp.pm broken.
#
# ipxe-xcat is packaged for x86_64 only, so a bundle must name this case for an x86_64 cell.
# The first check below is what says so out loud.
#
# THE LOADER FIRST. dhcp6_bootfile_url_for_node returns empty when the UEFI loader is not under
# the tftpdir of the node, and makedhcp still exits 0, so every assertion after it would pass
# vacuously on a management node that has no ipxe-xcat.
cmd:sh -c 'd=$(lsdef -t site -i tftpdir -c 2>/dev/null | sed -n "s/.*tftpdir=//p"); d=${d:-/tftpboot}; echo looking under "$d"; test -e "$d/xcat/ipxe/x86_64-sb/snponly-shim.efi"'
check:rc==0
# PRE-CLEAN, because xcattest stops a case at its first failed check and the cleanup at the end
# would then not run. Without this a failed run leaves an address, a network and a node behind
# and the next run measures the leftovers.
cmd:sh -c 'makedhcp -d ci-ipxe-cn >/dev/null 2>&1; makehosts -d ci-ipxe-cn >/dev/null 2>&1; chtab -d node=ci-ipxe-cn vpd >/dev/null 2>&1; rmdef -t node ci-ipxe-cn >/dev/null 2>&1; rmdef -t network ci-ipxe >/dev/null 2>&1; ip -6 addr del 2001:db8:178::1/64 dev $$SECONDNIC >/dev/null 2>&1; true'
check:rc==0
# A prefix of its own, documentation-reserved (RFC 3849). The sibling cases hold 2001:db8:176::/64
# and 2001:db8:177::/64, and two xCAT network rows on one prefix make Kea reject the whole file.
cmd:ip -6 addr add 2001:db8:178::1/64 dev $$SECONDNIC
check:rc==0
cmd:mkdef -t network -o ci-ipxe net=2001:db8:178:: mask=/64 mgtifname=$$SECONDNIC dynamicrange=2001:db8:178::1000-2001:db8:178::1fff
check:rc==0
# The uuid is fixed, because both DUIDs below are derived from it and a generated uuid would make
# them unknowable.
cmd:mkdef -t node ci-ipxe-cn groups=all mgt=kvm netboot=ipxe ip=2001:db8:178::20 mac=aa:bb:cc:dd:ee:03
check:rc==0
cmd:chtab node=ci-ipxe-cn vpd.uuid=c78c76bf-3286-46f3-b80d-8fe079dd6de7
check:rc==0
cmd:makehosts ci-ipxe-cn
check:rc==0
# makedhcp -n first: makedhcp <node> only writes into a subnet that is already in the file, and
# without the subnet it SKIPS the node with a warning and still exits 0.
cmd:makedhcp -n
check:rc==0
cmd:makedhcp ci-ipxe-cn
check:rc==0
# Kea must accept its own configuration. A reservation xCAT renders and Kea rejects serves nobody.
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
# THE COUNT. This is the check a one-reservation regression fails: a node that solicits twice and
# is reserved once leaves its second client unanswered.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));w={"00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7","00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7"};print("IPXE"+"_PAIR="+str(sum(1 for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("duid") in w)))'
check:rc==0
check:output==IPXE_PAIR=2
# THE TWO DUIDS DIFFER. A bug that derives both from one byte order writes two IDENTICAL entries,
# which the count above still reads as two and kea-dhcp6 -t may still accept. The set is printed
# sorted, so it names both values and nothing else.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));w={"00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7","00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7"};print("DU"+"IDS="+" ".join(sorted({r["duid"] for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("duid") in w})))'
check:rc==0
check:output==DUIDS=00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7 00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7
# RESERVATION ONE, byte-exact. Without a duid attribute the firmware DUID is the primary, so this
# entry keeps the hostname and the reserved address. EDK2 stores the first three UUID fields
# little-endian in SMBIOS and copies them into its DUID-UUID in that order, which is why these
# octets are the uuid above with those fields reversed.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));f="00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7";r=([x for s in d["Dhcp6"]["subnet6"] for x in s.get("reservations",[]) if x.get("duid")==f]+[{}])[0];print("FIRM"+"WARE="+r.get("hostname","")+"|"+",".join(r.get("ip-addresses",[]))+"|"+"".join(o.get("data","") for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==FIRMWARE=ci-ipxe-cn|2001:db8:178::20|tftp://[2001:db8:178::1]/xcat/ipxe/x86_64-sb/snponly-shim.efi
# RESERVATION TWO, byte-exact. It carries NO hostname and NO address: Kea refuses a second
# reservation for one address, and a shared hostname makes upsert_reservations replace one entry
# with the other. The iPXE client takes an address from the dynamic range and needs only its
# boot file, which is the per-node script over HTTP.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));c="00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7";r=([x for s in d["Dhcp6"]["subnet6"] for x in s.get("reservations",[]) if x.get("duid")==c]+[{}])[0];print("CHAIN"+"LOAD="+r.get("hostname","")+"|"+",".join(r.get("ip-addresses",[]))+"|"+"".join(o.get("data","") for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==CHAINLOAD=||http://[2001:db8:178::1]/tftpboot/xcat/ipxe/nodes/ci-ipxe-cn.uefi
# WHICH CLIENT GETS WHICH FILE. duid=canonical makes the RFC 4122 order the primary, so the
# address moves to the client that sends it -- and that client is iPXE, because iPXE converts the
# SMBIOS bytes to RFC 4122 order before it builds its DUID. So the canonical entry takes the
# hostname, the address AND the HTTP script, and the firmware entry keeps the TFTP shim. The pair
# is still two entries with the same two DUIDs.
cmd:chdef -t node ci-ipxe-cn duid=canonical
check:rc==0
cmd:makedhcp ci-ipxe-cn
check:rc==0
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));w={"00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7","00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7"};print("CANON"+"_PAIR="+" ".join(sorted({r["duid"] for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("duid") in w})))'
check:rc==0
check:output==CANON_PAIR=00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7 00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));c="00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7";r=([x for s in d["Dhcp6"]["subnet6"] for x in s.get("reservations",[]) if x.get("duid")==c]+[{}])[0];print("CANON"+"ICAL="+r.get("hostname","")+"|"+",".join(r.get("ip-addresses",[]))+"|"+"".join(o.get("data","") for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==CANONICAL=ci-ipxe-cn|2001:db8:178::20|http://[2001:db8:178::1]/tftpboot/xcat/ipxe/nodes/ci-ipxe-cn.uefi
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));f="00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7";r=([x for s in d["Dhcp6"]["subnet6"] for x in s.get("reservations",[]) if x.get("duid")==f]+[{}])[0];print("SECOND"+"ARY="+r.get("hostname","")+"|"+",".join(r.get("ip-addresses",[]))+"|"+"".join(o.get("data","") for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==SECONDARY=||tftp://[2001:db8:178::1]/xcat/ipxe/x86_64-sb/snponly-shim.efi
cmd:chdef -t node ci-ipxe-cn duid=
check:rc==0
cmd:makedhcp ci-ipxe-cn
check:rc==0
# THE GRUB2 DISCRIMINATOR. A case that cannot tell the two netboot methods apart is measuring
# "a reservation exists". grub2 is loaded by the firmware and asks for nothing more, so a grub2
# node is ONE client with the grub2 loader over TFTP.
#
# The delete runs while the node is still netboot=ipxe. The match list of a grub2 node names only
# its first DUID, so a delete after the change would leave the chainload entry behind.
cmd:makedhcp -d ci-ipxe-cn
check:rc==0
cmd:chdef -t node ci-ipxe-cn netboot=grub2
check:rc==0
cmd:makedhcp ci-ipxe-cn
check:rc==0
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));w={"00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7","00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7"};print("GRUB2"+"_ONE="+str(sum(1 for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("duid") in w)))'
check:rc==0
check:output==GRUB2_ONE=1
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print("GRUB2"+"_URL="+"".join(o.get("data","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipxe-cn" for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==GRUB2_URL=tftp://[2001:db8:178::1]/boot/grub2/grub2-ci-ipxe-cn
# Back to iPXE, and the pair comes back. A one-way change would leave the cell measuring grub2.
cmd:makedhcp -d ci-ipxe-cn
check:rc==0
cmd:chdef -t node ci-ipxe-cn netboot=ipxe
check:rc==0
cmd:makedhcp ci-ipxe-cn
check:rc==0
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));w={"00:04:bf:76:8c:c7:86:32:f3:46:b8:0d:8f:e0:79:dd:6d:e7","00:04:c7:8c:76:bf:32:86:46:f3:b8:0d:8f:e0:79:dd:6d:e7"};print("BACK"+"_PAIR="+str(sum(1 for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("duid") in w)))'
check:rc==0
check:output==BACK_PAIR=2
# NOTHING ON IPv4. An IPv6-only node gets no DHCPv4 reservation and no client class, not even a
# DROP class, whichever netboot method it carries.
cmd:python3 -c 'import json,os;p="/etc/kea/kea-dhcp4.conf";d=json.load(open(p)) if os.path.exists(p) else {"Dhcp4":{}};print("V4"+"_COUNT="+str(sum(1 for s in d["Dhcp4"].get("subnet4",[]) for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipxe-cn")+sum(1 for c in d["Dhcp4"].get("client-classes",[]) if "ci-ipxe-cn" in c.get("name",""))))'
check:rc==0
check:output==V4_COUNT=0
# Leave the management node as it was found.
cmd:sh -c 'makedhcp -d ci-ipxe-cn >/dev/null 2>&1; makehosts -d ci-ipxe-cn; chtab -d node=ci-ipxe-cn vpd; rmdef -t node ci-ipxe-cn; rmdef -t network ci-ipxe; ip -6 addr del 2001:db8:178::1/64 dev $$SECONDNIC; true'
check:rc==0
end

start:makedhcp_dhcpv6_dual_stack_boot_server
description:makedhcp keeps the DHCPv6 boot URL for a grub2 node whose tftpserver is IPv4
label:mn_only,ipv6
# PRE-CLEAN, for the same reason as makedhcp_dhcpv6_boot_intent: a case stops at its first failed
# check, so the cleanup at the end does not run and the next run measures the leftovers.
cmd:sh -c 'makedhcp -d ci-ipv6-ds >/dev/null 2>&1; makehosts -d ci-ipv6-ds >/dev/null 2>&1; rmdef -t node ci-ipv6-ds >/dev/null 2>&1; rmdef -t network ci-ipv6ds >/dev/null 2>&1; ip -6 addr del 2001:db8:177::1/64 dev $$SECONDNIC >/dev/null 2>&1; true'
check:rc==0
# A second documentation prefix (RFC 3849), so this case cannot read the address the IPv6-only
# case adds. The node IP below shares this /64 and no other.
cmd:ip -6 addr add 2001:db8:177::1/64 dev $$SECONDNIC
check:rc==0
cmd:mkdef -t network -o ci-ipv6ds net=2001:db8:177:: mask=/64 mgtifname=$$SECONDNIC dynamicrange=2001:db8:177::1000-2001:db8:177::1fff
check:rc==0
# A dual-stack node keeps an IPv4 tftpserver. The address is TEST-NET-1, so it has no IPv6 form
# and nothing resolves it off this host.
cmd:mkdef -t node ci-ipv6-ds groups=all mgt=kvm netboot=grub2 ip=2001:db8:177::20 mac=aa:bb:cc:dd:ee:02 tftpserver=192.0.2.10
check:rc==0
cmd:makehosts ci-ipv6-ds
check:rc==0
# REGENERATE THE SERVER CONFIG. makedhcp <node> only writes a reservation into a subnet that is
# already in kea-dhcp6.conf, so without this the node has no subnet, makedhcp SKIPS it with a
# warning and still exits 0 -- and an assertion on the exit status alone then passes while
# nothing was measured.
cmd:makedhcp -n
check:rc==0
# The subnet must be there, or the next assertions cannot tell a skipped node from a wrong URL.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print(" ".join(s["subnet"] for s in d["Dhcp6"]["subnet6"]))'
check:rc==0
check:output=~2001:db8:177::/64
# makedhcp MUST SUCCEED. It resolved the tftpserver as IPv6, got no answer, and reported an error
# with errorcode 1, so the whole command exited 1 for a node that boots over IPv4.
cmd:makedhcp ci-ipv6-ds
check:rc==0
# The node MUST get a reservation. A skip leaves none and says so only in a warning.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print(sum(1 for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-ds"))'
check:rc==0
check:output==1
cmd:kea-dhcp6 -t /etc/kea/kea-dhcp6.conf
check:rc==0
# The boot URL survives, and its authority is the address of this management node that faces the
# node, which is what <xcatmaster> already resolved to. Compared EXACTLY: an IPv4 tftpserver that
# reached the URL, or a dropped option, both differ from this string.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print("".join(o.get("data","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-ds" for o in r.get("option-data",[]) if o.get("name")=="bootfile-url"))'
check:rc==0
check:output==tftp://[2001:db8:177::1]/boot/grub2/grub2-ci-ipv6-ds
# The reservation still identifies the client. Without a uuid or a duid attribute that is the MAC.
cmd:python3 -c 'import json;d=json.load(open("/etc/kea/kea-dhcp6.conf"));print("".join(r.get("hw-address","") for s in d["Dhcp6"]["subnet6"] for r in s.get("reservations",[]) if r.get("hostname")=="ci-ipv6-ds"))'
check:rc==0
check:output==aa:bb:cc:dd:ee:02
# Leave the management node as it was found.
cmd:sh -c 'makedhcp -d ci-ipv6-ds >/dev/null 2>&1; makehosts -d ci-ipv6-ds; rmdef -t node ci-ipv6-ds; rmdef -t network ci-ipv6ds; ip -6 addr del 2001:db8:177::1/64 dev $$SECONDNIC; true'
check:rc==0
end
