diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-07 17:32:59 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-07 17:32:59 +0000 |
| commit | 31baaea4ab87aa2a42579b526b5c167796e60097 (patch) | |
| tree | 5cdaad61bf9f720fe1930389e056727c2ea8e20b /tests/test_k3s_runtime.py | |
| parent | 6b4415876f617b5afe17d1525a555a120df78bc0 (diff) | |
| download | meta-virtualization-31baaea4ab87aa2a42579b526b5c167796e60097.tar.gz | |
tests: update k3s multi-node to use kernel cmdline role setup
Update the multi-node test fixture to use kernel cmdline parameters
(k3s.role, k3s.node-ip, k3s.node-name) instead of manual IP
configuration and k3s restart. The k3s-role-setup.service handles
networking and role switching automatically on boot.
- Pass kernel_append to K3sRunner for k3s.role and k3s.node-ip
- Remove manual ip-addr-add and k3s stop/restart from fixture
- Use k3s-get-token helper to extract join token on server
- Agent starts k3s agent manually with extracted token (token
not known at boot time)
- Remove _QEMU_ARCH_CONFIG dict (moved to run-qemu-vm.sh script)
All 10 tests pass: 5 single-node + 5 multi-node.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'tests/test_k3s_runtime.py')
| -rw-r--r-- | tests/test_k3s_runtime.py | 84 |
1 files changed, 31 insertions, 53 deletions
diff --git a/tests/test_k3s_runtime.py b/tests/test_k3s_runtime.py index 1e5460a4..aa0d443f 100644 --- a/tests/test_k3s_runtime.py +++ b/tests/test_k3s_runtime.py | |||
| @@ -75,7 +75,8 @@ class K3sRunner: | |||
| 75 | def __init__(self, poky_dir, build_dir, machine, use_kvm=True, | 75 | def __init__(self, poky_dir, build_dir, machine, use_kvm=True, |
| 76 | timeout=120, image="container-image-host", | 76 | timeout=120, image="container-image-host", |
| 77 | extra_qemu_params="", log_suffix="", | 77 | extra_qemu_params="", log_suffix="", |
| 78 | use_runqemu=True, rootfs_path=None): | 78 | use_runqemu=True, rootfs_path=None, |
| 79 | kernel_append=""): | ||
| 79 | self.poky_dir = Path(poky_dir) | 80 | self.poky_dir = Path(poky_dir) |
| 80 | self.build_dir = Path(build_dir) | 81 | self.build_dir = Path(build_dir) |
| 81 | self.machine = machine | 82 | self.machine = machine |
| @@ -86,6 +87,7 @@ class K3sRunner: | |||
| 86 | self.log_suffix = log_suffix | 87 | self.log_suffix = log_suffix |
| 87 | self.use_runqemu = use_runqemu | 88 | self.use_runqemu = use_runqemu |
| 88 | self.rootfs_path = rootfs_path | 89 | self.rootfs_path = rootfs_path |
| 90 | self.kernel_append = kernel_append | ||
| 89 | self.child = None | 91 | self.child = None |
| 90 | self.booted = False | 92 | self.booted = False |
| 91 | self._rootfs_copy = None | 93 | self._rootfs_copy = None |
| @@ -121,6 +123,9 @@ class K3sRunner: | |||
| 121 | if port: | 123 | if port: |
| 122 | cmd += f" --role agent --socket-port {port.group(1)}" | 124 | cmd += f" --role agent --socket-port {port.group(1)}" |
| 123 | 125 | ||
| 126 | if self.kernel_append: | ||
| 127 | cmd += f' --append "{self.kernel_append}"' | ||
| 128 | |||
| 124 | return cmd | 129 | return cmd |
| 125 | 130 | ||
| 126 | def start(self): | 131 | def start(self): |
| @@ -395,6 +400,7 @@ def k3s_multinode(request, poky_dir, build_dir, machine): | |||
| 395 | extra_qemu_params=server_params, | 400 | extra_qemu_params=server_params, |
| 396 | use_runqemu=False, | 401 | use_runqemu=False, |
| 397 | rootfs_path=rootfs_orig, | 402 | rootfs_path=rootfs_orig, |
| 403 | kernel_append="k3s.role=server k3s.node-ip=192.168.50.1", | ||
| 398 | log_suffix="-server") | 404 | log_suffix="-server") |
| 399 | 405 | ||
| 400 | # Agent VM: socket connect on second NIC, uses rootfs copy | 406 | # Agent VM: socket connect on second NIC, uses rootfs copy |
| @@ -407,6 +413,7 @@ def k3s_multinode(request, poky_dir, build_dir, machine): | |||
| 407 | extra_qemu_params=agent_params, | 413 | extra_qemu_params=agent_params, |
| 408 | use_runqemu=False, | 414 | use_runqemu=False, |
| 409 | rootfs_path=rootfs_agent, | 415 | rootfs_path=rootfs_agent, |
| 416 | kernel_append="k3s.role=agent k3s.node-ip=192.168.50.2 k3s.node-name=k3s-agent", | ||
| 410 | log_suffix="-agent") | 417 | log_suffix="-agent") |
| 411 | agent._rootfs_copy = str(rootfs_agent) | 418 | agent._rootfs_copy = str(rootfs_agent) |
| 412 | 419 | ||
| @@ -415,13 +422,9 @@ def k3s_multinode(request, poky_dir, build_dir, machine): | |||
| 415 | server.start() | 422 | server.start() |
| 416 | agent.start() | 423 | agent.start() |
| 417 | 424 | ||
| 418 | # Configure static IPs on eth1 (the socket NIC) | 425 | # Wait for networkd to configure IPs from kernel cmdline |
| 419 | server.run_command('ip addr add 192.168.50.1/24 dev eth1') | 426 | # (k3s-role-setup.service writes networkd drop-ins) |
| 420 | server.run_command('ip link set eth1 up') | 427 | time.sleep(5) |
| 421 | agent.run_command('ip addr add 192.168.50.2/24 dev eth1') | ||
| 422 | agent.run_command('ip link set eth1 up') | ||
| 423 | # Brief pause for link to come up | ||
| 424 | time.sleep(2) | ||
| 425 | 428 | ||
| 426 | yield {"server": server, "agent": agent} | 429 | yield {"server": server, "agent": agent} |
| 427 | 430 | ||
| @@ -566,38 +569,15 @@ class TestK3sMultiNode: | |||
| 566 | f"Agent cannot ping server:\n{output}" | 569 | f"Agent cannot ping server:\n{output}" |
| 567 | 570 | ||
| 568 | def test_k3s_agent_join(self, k3s_multinode, k3s_timeout): | 571 | def test_k3s_agent_join(self, k3s_multinode, k3s_timeout): |
| 569 | """Start k3s server on VM1, join agent VM2.""" | 572 | """Wait for k3s server Ready, extract token, start agent.""" |
| 570 | server = k3s_multinode["server"] | 573 | server = k3s_multinode["server"] |
| 571 | agent = k3s_multinode["agent"] | 574 | agent = k3s_multinode["agent"] |
| 572 | 575 | ||
| 573 | # Stop default k3s service and wipe TLS state so the server | 576 | # The server VM booted with k3s.role=server and k3s.node-ip=192.168.50.1 |
| 574 | # generates new certs that include the cluster IP (192.168.50.1). | 577 | # on the kernel cmdline. k3s-role-setup.service configured networking |
| 575 | # Without this, certs are only valid for the DHCP IP (10.0.2.15). | 578 | # and k3s.service auto-started. Wait for it to become Ready. |
| 576 | server.run_command('systemctl stop k3s 2>/dev/null') | ||
| 577 | server.run_command( | ||
| 578 | 'rm -rf /var/lib/rancher/k3s/server/tls ' | ||
| 579 | '/var/lib/rancher/k3s/server/cred ' | ||
| 580 | '/var/lib/rancher/k3s/server/token ' | ||
| 581 | '/var/lib/rancher/k3s/server/agent-token ' | ||
| 582 | '/var/lib/rancher/k3s/server/node-token ' | ||
| 583 | '/var/lib/rancher/k3s/server/db ' | ||
| 584 | '/etc/rancher/k3s/k3s.yaml') | ||
| 585 | server.run_command( | ||
| 586 | 'export PATH=$PATH:/opt/cni/bin:/usr/libexec/cni && ' | ||
| 587 | 'k3s server ' | ||
| 588 | '--write-kubeconfig-mode 644 ' | ||
| 589 | '--disable-cloud-controller ' | ||
| 590 | '--node-ip 192.168.50.1 ' | ||
| 591 | '--bind-address 192.168.50.1 ' | ||
| 592 | '--advertise-address 192.168.50.1 ' | ||
| 593 | '--tls-san 192.168.50.1 ' | ||
| 594 | '--flannel-iface eth1 ' | ||
| 595 | '&>/var/log/k3s-server.log &') | ||
| 596 | |||
| 597 | # Wait for new kubeconfig to be written, then wait for Ready | ||
| 598 | try: | 579 | try: |
| 599 | server.wait_for_condition( | 580 | server.wait_for_condition( |
| 600 | 'test -f /etc/rancher/k3s/k3s.yaml && ' | ||
| 601 | f'{_KUBECTL} get nodes 2>/dev/null || echo WAITING', | 581 | f'{_KUBECTL} get nodes 2>/dev/null || echo WAITING', |
| 602 | r'\bReady\b', | 582 | r'\bReady\b', |
| 603 | timeout=k3s_timeout, | 583 | timeout=k3s_timeout, |
| @@ -605,32 +585,30 @@ class TestK3sMultiNode: | |||
| 605 | description="k3s server node Ready") | 585 | description="k3s server node Ready") |
| 606 | except TimeoutError: | 586 | except TimeoutError: |
| 607 | logs = server.run_command( | 587 | logs = server.run_command( |
| 608 | 'tail -50 /var/log/k3s-server.log 2>/dev/null || ' | 588 | 'journalctl -u k3s --no-pager -n 30 2>/dev/null || ' |
| 609 | 'echo "no logs"') | 589 | 'echo "no logs"') |
| 610 | pytest.fail(f"Server not Ready:\n{logs}") | 590 | pytest.fail(f"Server not Ready:\n{logs}") |
| 611 | 591 | ||
| 612 | # Extract node token | 592 | # Extract node token |
| 613 | token = server.run_command( | 593 | token = server.run_command('k3s-get-token 2>&1') |
| 614 | 'cat /var/lib/rancher/k3s/server/node-token 2>&1') | 594 | # Parse the actual token from the script output |
| 615 | assert token and 'No such file' not in token, \ | 595 | for line in token.splitlines(): |
| 596 | line = line.strip() | ||
| 597 | if line.startswith('K10'): | ||
| 598 | token = line | ||
| 599 | break | ||
| 600 | assert token.startswith('K10'), \ | ||
| 616 | f"Failed to get node token:\n{token}" | 601 | f"Failed to get node token:\n{token}" |
| 617 | token = token.strip().splitlines()[-1].strip() | 602 | |
| 618 | 603 | # The agent VM booted with k3s.role=agent but without a token | |
| 619 | # Stop default k3s on agent, clear server-only config, and | 604 | # (we didn't know it at launch time). Role-setup configured |
| 620 | # wipe agent state from the auto-started instance. | 605 | # networking and masked k3s.service. Start k3s-agent manually |
| 621 | # Set a unique node name — both VMs boot the same image and | 606 | # with the token from the server. |
| 622 | # have the same hostname, which causes k3s to treat the agent | ||
| 623 | # as the same node as the server. | ||
| 624 | agent.run_command('systemctl stop k3s 2>/dev/null') | ||
| 625 | agent.run_command( | ||
| 626 | 'rm -f /etc/rancher/k3s/config.yaml && ' | ||
| 627 | 'rm -rf /var/lib/rancher/k3s/agent ' | ||
| 628 | '/var/lib/rancher/k3s/server') | ||
| 629 | agent.run_command( | 607 | agent.run_command( |
| 608 | f'export K3S_URL=https://192.168.50.1:6443 && ' | ||
| 609 | f'export K3S_TOKEN={token} && ' | ||
| 630 | f'export PATH=$PATH:/opt/cni/bin:/usr/libexec/cni && ' | 610 | f'export PATH=$PATH:/opt/cni/bin:/usr/libexec/cni && ' |
| 631 | f'k3s agent ' | 611 | f'k3s agent ' |
| 632 | f'--server https://192.168.50.1:6443 ' | ||
| 633 | f'--token {token} ' | ||
| 634 | f'--node-name k3s-agent ' | 612 | f'--node-name k3s-agent ' |
| 635 | f'--node-ip 192.168.50.2 ' | 613 | f'--node-ip 192.168.50.2 ' |
| 636 | f'--flannel-iface eth1 ' | 614 | f'--flannel-iface eth1 ' |
