nixos-test-driver: drop unneeded qemu dep (#523313)

This commit is contained in:
Maximilian Bosch 2026-06-25 22:56:27 +00:00 committed by GitHub
commit 534768de33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 326 additions and 323 deletions

View file

@ -18,8 +18,6 @@
netpbm,
vhost-device-vsock,
nixosTests,
qemu_pkg ? qemu_test,
qemu_test,
setuptools,
socat,
systemd,
@ -56,7 +54,6 @@ buildPythonApplication {
propagatedBuildInputs = [
coreutils
netpbm
qemu_pkg
socat
util-linux
vde2

View file

@ -20,7 +20,6 @@ let
# the respective qemu version and with or without ocr support
testDriver = config.pythonTestDriverPackage.override {
inherit (config) enableOCR extraPythonPackages;
qemu_pkg = config.qemu.package;
enableNspawn = config.containers != { };
};

View file

@ -207,7 +207,7 @@ in
import os
# Create a mutable linked image backed by the read-only SD image
if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
if os.system("${pkgs.qemu}/bin/qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
raise RuntimeError("Could not create mutable linked image")
machine = create_machine("${startCommand}")

View file

@ -51,9 +51,11 @@ in
)
os.makedirs(image_dir, mode=0o700, exist_ok=True)
disk_image = os.path.join(image_dir, "machine.qcow2")
QEMU_BIN = "${pkgs.qemu}"
QEMU_IMG = f"{QEMU_BIN}/bin/qemu-img"
subprocess.check_call(
[
"qemu-img",
QEMU_IMG,
"create",
"-f",
"qcow2",
@ -64,7 +66,7 @@ in
disk_image,
]
)
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"])
# Note: we use net=169.0.0.0/8 rather than
# net=169.254.0.0/16 to prevent dhcpcd from getting horribly
@ -74,7 +76,7 @@ in
# turn off the DHCP server, but qemu does not have an option
# to do that.
start_command = (
"qemu-kvm -m 1024"
f"{QEMU_BIN}/bin/qemu-kvm -m 1024"
+ " -device virtio-net-pci,netdev=vlan0"
+ " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${getExe imdsServer} ${metaData}'"
+ f" -drive file={disk_image},if=virtio,werror=report"

View file

@ -88,294 +88,298 @@ in
};
};
testScript = ''
import os
import re
import subprocess
import tempfile
testScript =
{ nodes, ... }:
''
import os
import re
import subprocess
import tempfile
# Instance Metadata Service (IMDSv2 with 1.0 metadata version)
# TODO: Use 'latest' metadata version instead of '1.0'
# TODO: [Test matrix] also test providing the host key through IMDS
# - i.e. a test module argument to select between writing or reading the host key
def create_ec2_metadata_dir(temp_dir, client_pubkey):
"""Create fake EC2 metadata directory structure with mock data"""
metadata_dir = os.path.join(temp_dir.name, "ec2-metadata")
# Instance Metadata Service (IMDSv2 with 1.0 metadata version)
# TODO: Use 'latest' metadata version instead of '1.0'
# TODO: [Test matrix] also test providing the host key through IMDS
# - i.e. a test module argument to select between writing or reading the host key
def create_ec2_metadata_dir(temp_dir, client_pubkey):
"""Create fake EC2 metadata directory structure with mock data"""
metadata_dir = os.path.join(temp_dir.name, "ec2-metadata")
# Create directory structure
os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True)
os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True)
# Create directory structure
os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True)
os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True)
# Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches)
with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f:
f.write("test-instance")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f:
f.write("(test)")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f:
f.write("i-1234567890abcdef0")
with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f:
f.write("")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f:
f.write(client_pubkey)
# Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches)
with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f:
f.write("test-instance")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f:
f.write("(test)")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f:
f.write("i-1234567890abcdef0")
with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f:
f.write("")
with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f:
f.write(client_pubkey)
# IMDSv2 token endpoint - return a fake token
with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f:
f.write("test-token-12345")
# IMDSv2 token endpoint - return a fake token
with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f:
f.write("test-token-12345")
return metadata_dir
return metadata_dir
def generate_client_ssh_key():
"""Generate SSH key pair on VM host for client authentication"""
# Use temporary directory for key generation
import tempfile
with tempfile.TemporaryDirectory() as key_dir:
private_key = os.path.join(key_dir, "id_ed25519")
public_key = os.path.join(key_dir, "id_ed25519.pub")
def generate_client_ssh_key():
"""Generate SSH key pair on VM host for client authentication"""
# Use temporary directory for key generation
import tempfile
with tempfile.TemporaryDirectory() as key_dir:
private_key = os.path.join(key_dir, "id_ed25519")
public_key = os.path.join(key_dir, "id_ed25519.pub")
# Generate key pair using host SSH tools
ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"")
if ret != 0:
raise Exception("Failed to generate SSH key pair")
# Generate key pair using host SSH tools
ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"")
if ret != 0:
raise Exception("Failed to generate SSH key pair")
# Read the generated public key
with open(public_key, "r") as f:
client_pubkey = f.read().strip()
# Read the generated public key
with open(public_key, "r") as f:
client_pubkey = f.read().strip()
# Read the private key
with open(private_key, "r") as f:
client_private_key = f.read()
# Read the private key
with open(private_key, "r") as f:
client_private_key = f.read()
return client_pubkey, client_private_key
return client_pubkey, client_private_key
def setup_client_ssh_key(client, client_private_key):
"""Install the pre-generated SSH private key on client"""
client.succeed("mkdir -p /root/.ssh")
client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
def setup_client_ssh_key(client, client_private_key):
"""Install the pre-generated SSH private key on client"""
client.succeed("mkdir -p /root/.ssh")
client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF")
client.succeed("chmod 600 /root/.ssh/id_ed25519")
def setup_machine(temp_dir, client_pubkey):
"""Initialize EC2 machine with disk image, metadata server, and networking"""
# Set up disk image
image_dir = os.path.join(
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
)
os.makedirs(image_dir, mode=0o700, exist_ok=True)
disk_image = os.path.join(image_dir, "machine.qcow2")
subprocess.check_call([
"qemu-img", "create", "-f", "qcow2", "-F", "qcow2",
"-o", "backing_file=${image}", disk_image
])
subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
def setup_machine(temp_dir, client_pubkey):
"""Initialize EC2 machine with disk image, metadata server, and networking"""
# Set up disk image
image_dir = os.path.join(
os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
)
os.makedirs(image_dir, mode=0o700, exist_ok=True)
disk_image = os.path.join(image_dir, "machine.qcow2")
QEMU = "${nodes.machine.virtualisation.qemu.package}"
QEMU_IMG = f"{QEMU}/bin/qemu-img"
subprocess.check_call([
QEMU_IMG, "create", "-f", "qcow2", "-F", "qcow2",
"-o", "backing_file=${image}", disk_image
])
subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"])
# Create fake EC2 metadata in temporary directory with client's public key
metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey)
# Create fake EC2 metadata in temporary directory with client's public key
metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey)
# Add both VLAN networking (matching test framework) and EC2 metadata server
vlan_net = (
" -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02"
+ ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"'
)
metadata_net = (
" -device virtio-net-pci,netdev=ec2meta"
+ f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'"
)
# Add both VLAN networking (matching test framework) and EC2 metadata server
vlan_net = (
" -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02"
+ ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"'
)
metadata_net = (
" -device virtio-net-pci,netdev=ec2meta"
+ f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'"
)
start_command = (
"qemu-kvm -m 1024"
+ f" -drive file={disk_image},if=virtio,werror=report"
+ vlan_net
+ metadata_net
+ " $QEMU_OPTS"
)
start_command = (
f"{QEMU}/bin/qemu-kvm -m 1024"
+ f" -drive file={disk_image},if=virtio,werror=report"
+ vlan_net
+ metadata_net
+ " $QEMU_OPTS"
)
return create_machine(start_command), metadata_dir
return create_machine(start_command), metadata_dir
def test_userdata_decompression(machine, user_data_path, compressed_data, format_name):
"""Test that compressed user-data is decompressed by fetch-ec2-metadata"""
test_marker = f"{format_name}-decompression-test"
with open(user_data_path, "wb") as f:
f.write(compressed_data)
machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata")
result = machine.succeed("cat /etc/ec2-metadata/user-data")
assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}"
journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b")
assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}"
def test_userdata_decompression(machine, user_data_path, compressed_data, format_name):
"""Test that compressed user-data is decompressed by fetch-ec2-metadata"""
test_marker = f"{format_name}-decompression-test"
with open(user_data_path, "wb") as f:
f.write(compressed_data)
machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata")
result = machine.succeed("cat /etc/ec2-metadata/user-data")
assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}"
journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b")
assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}"
# Create temporary directory for metadata (scoped for cleanup)
temp_dir = tempfile.TemporaryDirectory()
# Create temporary directory for metadata (scoped for cleanup)
temp_dir = tempfile.TemporaryDirectory()
# Start client first (but don't wait for it to boot)
client.start()
# Start client first (but don't wait for it to boot)
client.start()
# Generate SSH key pair on VM host before starting machine
client_pubkey, client_private_key = generate_client_ssh_key()
# Generate SSH key pair on VM host before starting machine
client_pubkey, client_private_key = generate_client_ssh_key()
# Set up machine with client's public key in metadata service
machine, metadata_dir = setup_machine(temp_dir, client_pubkey)
user_data_path = os.path.join(metadata_dir, "1.0", "user-data")
# Set up machine with client's public key in metadata service
machine, metadata_dir = setup_machine(temp_dir, client_pubkey)
user_data_path = os.path.join(metadata_dir, "1.0", "user-data")
try:
machine.start()
try:
machine.start()
# Wait for services to be ready
machine.wait_for_unit("sshd.service")
machine.wait_for_unit("print-host-key.service")
machine.wait_for_unit("apply-ec2-data.service")
# Wait for services to be ready
machine.wait_for_unit("sshd.service")
machine.wait_for_unit("print-host-key.service")
machine.wait_for_unit("apply-ec2-data.service")
# Extract shared variables outside subtests
machine_ip = "${config.nodes.machine.networking.primaryIPAddress}"
# Extract shared variables outside subtests
machine_ip = "${config.nodes.machine.networking.primaryIPAddress}"
with subtest("EC2 metadata service connectivity"):
# Obtain an IMDSv2 token, then use it to fetch metadata
imds_token = machine.succeed(
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
" http://169.254.169.254/latest/api/token"
).strip()
assert imds_token, "Failed to obtain IMDSv2 token"
hostname_response = machine.succeed(
f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'"
" http://169.254.169.254/1.0/meta-data/hostname"
)
assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}"
with subtest("EC2 metadata service connectivity"):
# Obtain an IMDSv2 token, then use it to fetch metadata
imds_token = machine.succeed(
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
" http://169.254.169.254/latest/api/token"
).strip()
assert imds_token, "Failed to obtain IMDSv2 token"
hostname_response = machine.succeed(
f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'"
" http://169.254.169.254/1.0/meta-data/hostname"
)
assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}"
with subtest("SSH host key extraction from console"):
console_log = machine.get_console_log()
assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log
assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log
assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log
assert "-----END SSH HOST KEY KEYS-----" in console_log
with subtest("SSH host key extraction from console"):
console_log = machine.get_console_log()
assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log
assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log
assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log
assert "-----END SSH HOST KEY KEYS-----" in console_log
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
assert keys_match, "Could not find SSH host keys section"
keys_content = keys_match.group(1).strip()
assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix"
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
assert keys_match, "Could not find SSH host keys section"
keys_content = keys_match.group(1).strip()
assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix"
with subtest("Network connectivity"):
client.succeed(f"ping -c 1 {machine_ip}")
with subtest("Network connectivity"):
client.succeed(f"ping -c 1 {machine_ip}")
with subtest("SSH connectivity with strict host key checking"):
# Install the pre-generated private key on client
setup_client_ssh_key(client, client_private_key)
with subtest("SSH connectivity with strict host key checking"):
# Install the pre-generated private key on client
setup_client_ssh_key(client, client_private_key)
# Get console log and extract host keys
console_log = machine.get_console_log()
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
assert keys_match, "Could not find SSH host keys section"
# Get console log and extract host keys
console_log = machine.get_console_log()
keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----"
keys_match = re.search(keys_pattern, console_log, re.DOTALL)
assert keys_match, "Could not find SSH host keys section"
# Create known_hosts file from console-extracted host keys
keys_content = keys_match.group(1).strip()
known_hosts_entries = []
for line in keys_content.split('\n'):
if line.strip() and line.startswith('ssh-'):
known_hosts_entries.append(f"{machine_ip} {line.strip()}")
# Create known_hosts file from console-extracted host keys
keys_content = keys_match.group(1).strip()
known_hosts_entries = []
for line in keys_content.split('\n'):
if line.strip() and line.startswith('ssh-'):
known_hosts_entries.append(f"{machine_ip} {line.strip()}")
assert known_hosts_entries, "No SSH host keys found for known_hosts generation"
assert known_hosts_entries, "No SSH host keys found for known_hosts generation"
known_hosts_content = '\n'.join(known_hosts_entries)
client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF")
known_hosts_content = '\n'.join(known_hosts_entries)
client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF")
# Test SSH connectivity with strict host key checking
ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'")
assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}"
# Test SSH connectivity with strict host key checking
ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'")
assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}"
with subtest("Basic EC2 functionality"):
machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'")
with subtest("Basic EC2 functionality"):
machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'")
with subtest("Decompression of gzip-compressed user-data"):
import gzip as gzip_mod
test_data = b"#!/bin/bash\necho gzip-decompression-test\n"
test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip")
with subtest("Decompression of gzip-compressed user-data"):
import gzip as gzip_mod
test_data = b"#!/bin/bash\necho gzip-decompression-test\n"
test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip")
with subtest("Decompression of bzip2-compressed user-data"):
import bz2
test_data = b"#!/bin/bash\necho bzip2-decompression-test\n"
test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2")
with subtest("Decompression of bzip2-compressed user-data"):
import bz2
test_data = b"#!/bin/bash\necho bzip2-decompression-test\n"
test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2")
with subtest("Decompression of xz-compressed user-data"):
import lzma
test_data = b"#!/bin/bash\necho xz-decompression-test\n"
test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz")
with subtest("Decompression of xz-compressed user-data"):
import lzma
test_data = b"#!/bin/bash\necho xz-decompression-test\n"
test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz")
with subtest("Decompression of zstd-compressed user-data"):
test_data = b"#!/bin/bash\necho zstd-decompression-test\n"
proc = subprocess.run(
["${hostPkgs.zstd}/bin/zstd", "-c"],
input=test_data, capture_output=True, check=True,
)
test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd")
with subtest("Decompression of zstd-compressed user-data"):
test_data = b"#!/bin/bash\necho zstd-decompression-test\n"
proc = subprocess.run(
["${hostPkgs.zstd}/bin/zstd", "-c"],
input=test_data, capture_output=True, check=True,
)
test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd")
with subtest("Decompression of lzip-compressed user-data"):
test_data = b"#!/bin/bash\necho lzip-decompression-test\n"
proc = subprocess.run(
["${hostPkgs.lzip}/bin/lzip", "-c"],
input=test_data, capture_output=True, check=True,
)
test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip")
with subtest("Decompression of lzip-compressed user-data"):
test_data = b"#!/bin/bash\necho lzip-decompression-test\n"
proc = subprocess.run(
["${hostPkgs.lzip}/bin/lzip", "-c"],
input=test_data, capture_output=True, check=True,
)
test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip")
with subtest("IPv6 IMDS fallback"):
# Save hostname fetched via IPv4 for later comparison
original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
with subtest("IPv6 IMDS fallback"):
# Save hostname fetched via IPv4 for later comparison
original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
# Assign the EC2 IPv6 IMDS address to loopback
machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo")
# Assign the EC2 IPv6 IMDS address to loopback
machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo")
# Create metadata directory structure for the IPv6 endpoint
machine.succeed(
"mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0"
" && mkdir -p /tmp/ipv6-metadata/latest/api"
" && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname"
" && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path"
" && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id"
" && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token"
" && touch /tmp/ipv6-metadata/1.0/user-data"
)
machine.execute(
"test -f /etc/ec2-metadata/public-keys-0-openssh-key"
" && cp /etc/ec2-metadata/public-keys-0-openssh-key"
" /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key"
)
# Create metadata directory structure for the IPv6 endpoint
machine.succeed(
"mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0"
" && mkdir -p /tmp/ipv6-metadata/latest/api"
" && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname"
" && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path"
" && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id"
" && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token"
" && touch /tmp/ipv6-metadata/1.0/user-data"
)
machine.execute(
"test -f /etc/ec2-metadata/public-keys-0-openssh-key"
" && cp /etc/ec2-metadata/public-keys-0-openssh-key"
" /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key"
)
# Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style)
machine.succeed(
"systemd-run --unit=ipv6-imds --"
" socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr"
" SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'"
)
# Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style)
machine.succeed(
"systemd-run --unit=ipv6-imds --"
" socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr"
" SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'"
)
# Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth)
machine.wait_until_succeeds(
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
" http://[fd00:ec2::254]/latest/api/token"
)
# Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth)
machine.wait_until_succeeds(
"curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'"
" http://[fd00:ec2::254]/latest/api/token"
)
# Block IPv4 IMDS to force fallback to IPv6
machine.succeed(
"iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
)
# Block IPv4 IMDS to force fallback to IPv6
machine.succeed(
"iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
)
# Verify IPv4 IMDS is now unreachable
machine.fail(
"curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname"
)
# Verify IPv4 IMDS is now unreachable
machine.fail(
"curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname"
)
# Clear fetched metadata and re-run the fetcher
machine.succeed("rm -f /etc/ec2-metadata/*")
machine.succeed("systemctl restart fetch-ec2-metadata")
# Clear fetched metadata and re-run the fetcher
machine.succeed("rm -f /etc/ec2-metadata/*")
machine.succeed("systemctl restart fetch-ec2-metadata")
# Verify metadata was successfully re-fetched via IPv6
hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'"
# Verify metadata was successfully re-fetched via IPv6
hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip()
assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'"
# Clean up: restore IPv4 IMDS access
machine.succeed(
"iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
)
machine.succeed("systemctl stop ipv6-imds")
# Clean up: restore IPv4 IMDS access
machine.succeed(
"iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT"
)
machine.succeed("systemctl stop ipv6-imds")
finally:
machine.shutdown()
temp_dir.cleanup()
'';
finally:
machine.shutdown()
temp_dir.cleanup()
'';
}

View file

@ -1,15 +1,15 @@
{ pkgs, ... }:
{
name = "systemd-initrd-simple";
nodes.machine =
{ pkgs, ... }:
{
testing.initrdBackdoor = true;
boot.initrd.systemd.enable = true;
virtualisation.fileSystems."/".autoResize = true;
};
nodes.machine = {
testing.initrdBackdoor = true;
boot.initrd.systemd.enable = true;
virtualisation.fileSystems."/".autoResize = true;
};
testScript =
{ nodes, ... }:
# python
''
import subprocess
@ -43,7 +43,7 @@
oldAvail = machine.succeed("df --output=avail / | sed 1d")
machine.shutdown()
subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
subprocess.check_call(["${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
machine.start()
machine.switch_root()

View file

@ -38,21 +38,23 @@
services.journald.extraConfig = "Storage=volatile";
test-support.displayManager.auto.user = "alice";
systemd.shutdown.test = pkgs.writeScript "test.shutdown" ''
#!${pkgs.runtimeShell}
PATH=${
lib.makeBinPath (
with pkgs;
[
util-linux
coreutils
]
)
}
mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared
touch /tmp/shared/shutdown-test
umount /tmp/shared
'';
systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/test".source =
pkgs.writeShellScript "test.shutdown" ''
PATH=${
lib.makeBinPath (
with pkgs;
[
util-linux
coreutils
]
)
}
mkdir -p /tmp/shared
mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared
touch /tmp/shared/shutdown-test
umount /tmp/shared
'';
systemd.shutdownRamfs.storePaths = [ "${pkgs.util-linux}/bin" ];
systemd.services.oncalendar-test = {
description = "calendar test";
@ -147,7 +149,7 @@
subprocess.check_call(
[
"qemu-img",
"${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
"convert",
"-O",
"raw",

View file

@ -196,7 +196,6 @@ with pkgs;
inherit (nix-update) nix-update-script;
nixos-test-driver = pkgs.python3Packages.callPackage ../../nixos/lib/test-driver {
qemu_pkg = pkgs.qemu;
imagemagick_light = pkgs.imagemagick_light.override { inherit (pkgs) libtiff; };
tesseract4 = pkgs.tesseract4.override { enableLanguages = [ "eng" ]; };
# We want `pkgs.systemd`, *not* `python3Packages.system`.