#!/bin/bash # ══════════════════════════════════════════════════════════════════════════════ # Volt Hybrid Integration Tests — Mode Toggle (Container ↔ Hybrid-Native) # # Tests toggling a workload between container and hybrid-native mode: # 1. Create container workload # 2. Start and create a test file inside # 3. Toggle to hybrid-native mode # 4. Verify test file persists (filesystem state preserved) # 5. Verify now running with own kernel/init # 6. Toggle back to container mode # 7. Verify test file still exists # 8. Verify back to shared kernel behavior # # The toggle operation uses the workload abstraction layer. Currently a # placeholder (metadata-only), so we test the state transition and # filesystem preservation. # # Requires: root, systemd-nspawn, base image # ══════════════════════════════════════════════════════════════════════════════ set -uo pipefail source "$(dirname "$0")/test_helpers.sh" # ── Prerequisites ───────────────────────────────────────────────────────────── require_root require_volt require_nspawn BASE_IMAGE="/var/lib/volt/images/ubuntu_24.04" if ! require_image "$BASE_IMAGE"; then echo "SKIP: No base image." exit 0 fi trap cleanup_all EXIT echo "⚡ Volt Hybrid Integration Tests — Mode Toggle" echo "════════════════════════════════════════════════════════════════" TOGGLE_WL=$(test_name "toggle") # ── 1. Create container workload ──────────────────────────────────────────── section "📦 1. Create Container Workload" output=$(create_container "$TOGGLE_WL" "$BASE_IMAGE" 2>&1) assert_ok "Create container workload '$TOGGLE_WL'" test $? -eq 0 assert_dir_exists "Rootfs exists" "/var/lib/volt/containers/$TOGGLE_WL" # Register in workload state store as a container # The workload abstraction layer tracks type (container vs vm) sudo "$VOLT" workload list &>/dev/null || true # trigger discovery # ── 2. Start and create a test file ───────────────────────────────────────── section "🚀 2. Start and Create Test File" output=$(start_workload "$TOGGLE_WL" 2>&1) assert_ok "Start workload" test $? -eq 0 if wait_running "$TOGGLE_WL" 30; then pass "Workload running" else fail "Workload running" "timed out" fi LEADER_PID=$(get_leader_pid "$TOGGLE_WL") assert_nonempty "Leader PID available" "$LEADER_PID" # Create a test file with unique content TEST_MARKER="volt-toggle-test-$(date +%s)-$$" exec_in "$TOGGLE_WL" sh -c "echo '$TEST_MARKER' > /tmp/toggle-test-file" 2>/dev/null || \ sudo nsenter -t "$LEADER_PID" -p -m sh -c "echo '$TEST_MARKER' > /tmp/toggle-test-file" 2>/dev/null # Verify the file was created if exec_in "$TOGGLE_WL" cat /tmp/toggle-test-file 2>/dev/null | grep -q "$TEST_MARKER"; then pass "Test file created inside workload" elif sudo nsenter -t "$LEADER_PID" -m cat /tmp/toggle-test-file 2>/dev/null | grep -q "$TEST_MARKER"; then pass "Test file created inside workload (via nsenter)" else fail "Test file created inside workload" "marker not found" fi # Also create a file directly on the rootfs (this will definitely persist) sudo sh -c "echo '$TEST_MARKER' > /var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" assert_file_exists "Rootfs test file created" "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" # Record the kernel version seen from inside (shared host kernel for containers) KERNEL_BEFORE=$(exec_in "$TOGGLE_WL" uname -r 2>/dev/null || \ sudo nsenter -t "$LEADER_PID" -m -u uname -r 2>/dev/null || echo "unknown") HOST_KERNEL=$(uname -r) pass "Kernel before toggle: $KERNEL_BEFORE (host: $HOST_KERNEL)" # ── 3. Toggle to hybrid-native mode ──────────────────────────────────────── section "🔄 3. Toggle to Hybrid-Native Mode" # Stop the workload first (toggle currently requires stop → reconfigure → start) stop_workload "$TOGGLE_WL" &>/dev/null # Use the workload toggle command toggle_output=$(sudo "$VOLT" workload toggle "$TOGGLE_WL" 2>&1) || true if echo "$toggle_output" | grep -qi "toggle\|vm\|hybrid"; then pass "Toggle command executed (output mentions toggle/vm/hybrid)" else # If workload toggle doesn't exist yet, simulate by checking what we can skip "Toggle command" "workload toggle may not be fully implemented" fi # Check the workload state after toggle wl_status=$(sudo "$VOLT" workload status "$TOGGLE_WL" 2>&1) || true if echo "$wl_status" | grep -qi "vm\|hybrid"; then pass "Workload type changed after toggle" else skip "Workload type changed" "toggle may only update metadata" fi # ── 4. Verify filesystem state preserved ──────────────────────────────────── section "📂 4. Verify Filesystem State Preserved" # The rootfs file we created directly should still be there if [[ -f "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" ]]; then content=$(cat "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" 2>/dev/null) if [[ "$content" == "$TEST_MARKER" ]]; then pass "Rootfs test file preserved with correct content" else fail "Rootfs test file preserved" "content mismatch: expected '$TEST_MARKER', got '$content'" fi else fail "Rootfs test file preserved" "file not found after toggle" fi # Check the in-container test file (was written to container's /tmp) if [[ -f "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-test-file" ]]; then content=$(cat "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-test-file" 2>/dev/null) if [[ "$content" == "$TEST_MARKER" ]]; then pass "In-container test file preserved with correct content" else fail "In-container test file preserved" "content mismatch" fi else skip "In-container test file preserved" "may have been on tmpfs (ephemeral)" fi # ── 5. Verify hybrid-native mode properties ──────────────────────────────── section "🔒 5. Verify Hybrid-Native Mode (post-toggle)" # Start the workload in its new mode start_output=$(start_workload "$TOGGLE_WL" 2>&1) || true if wait_running "$TOGGLE_WL" 30; then pass "Workload starts after toggle" NEW_LEADER_PID=$(get_leader_pid "$TOGGLE_WL") if [[ -n "$NEW_LEADER_PID" && "$NEW_LEADER_PID" != "0" ]]; then pass "New leader PID: $NEW_LEADER_PID" # If we're truly in hybrid/boot mode, PID 1 inside should be init/systemd pid1_comm=$(sudo nsenter -t "$NEW_LEADER_PID" -p -m cat /proc/1/comm 2>/dev/null || echo "") if echo "$pid1_comm" | grep -qE "systemd|init"; then pass "PID 1 inside is systemd/init (hybrid mode confirmed)" else skip "PID 1 check after toggle" "PID 1 is: $pid1_comm (may not be in true hybrid mode)" fi # Check kernel version — in hybrid mode with custom kernel it could differ KERNEL_AFTER=$(sudo nsenter -t "$NEW_LEADER_PID" -m -u uname -r 2>/dev/null || echo "unknown") pass "Kernel after toggle: $KERNEL_AFTER" else skip "Post-toggle leader PID" "PID not available" fi # Stop for the next toggle stop_workload "$TOGGLE_WL" &>/dev/null else skip "Post-toggle start" "workload failed to start after toggle" fi # ── 6. Toggle back to container mode ──────────────────────────────────────── section "🔄 6. Toggle Back to Container Mode" toggle_back_output=$(sudo "$VOLT" workload toggle "$TOGGLE_WL" 2>&1) || true if echo "$toggle_back_output" | grep -qi "toggle\|container"; then pass "Toggle-back command executed" else skip "Toggle-back command" "may not be implemented" fi # Check workload type reverted wl_status2=$(sudo "$VOLT" workload status "$TOGGLE_WL" 2>&1) || true if echo "$wl_status2" | grep -qi "container"; then pass "Workload type reverted to container" else skip "Workload type reverted" "status check inconclusive" fi # ── 7. Verify test file still exists ──────────────────────────────────────── section "📂 7. Verify Test File After Round-Trip Toggle" if [[ -f "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" ]]; then content=$(cat "/var/lib/volt/containers/$TOGGLE_WL/tmp/toggle-rootfs-file" 2>/dev/null) assert_eq "Test file survives round-trip toggle" "$TEST_MARKER" "$content" else fail "Test file survives round-trip toggle" "file not found" fi # ── 8. Verify back to shared kernel ──────────────────────────────────────── section "🔧 8. Verify Container Mode (shared kernel)" start_workload "$TOGGLE_WL" &>/dev/null || true if wait_running "$TOGGLE_WL" 30; then FINAL_LEADER=$(get_leader_pid "$TOGGLE_WL") if [[ -n "$FINAL_LEADER" && "$FINAL_LEADER" != "0" ]]; then KERNEL_FINAL=$(sudo nsenter -t "$FINAL_LEADER" -m -u uname -r 2>/dev/null || echo "unknown") if [[ "$KERNEL_FINAL" == "$HOST_KERNEL" ]]; then pass "Kernel matches host after toggle back ($KERNEL_FINAL)" else # In boot mode the kernel is always shared (nspawn doesn't boot a real kernel) # so this should always match unless a custom kernel-exec is used skip "Kernel match check" "kernel=$KERNEL_FINAL, host=$HOST_KERNEL" fi else skip "Post-toggle-back kernel check" "no leader PID" fi stop_workload "$TOGGLE_WL" &>/dev/null else skip "Post-toggle-back start" "workload failed to start" fi # ── Cleanup ────────────────────────────────────────────────────────────────── destroy_workload "$TOGGLE_WL" CLEANUP_WORKLOADS=("${CLEANUP_WORKLOADS[@]/$TOGGLE_WL/}") # ── Results ────────────────────────────────────────────────────────────────── print_results "Mode Toggle (Container ↔ Hybrid-Native)" exit $?