diff options
Diffstat (limited to 'scripts/testing/lib')
| -rwxr-xr-x | scripts/testing/lib/finalize-base-vm.sh | 21 | ||||
| -rwxr-xr-x | scripts/testing/lib/logging.sh | 151 | ||||
| -rw-r--r-- | scripts/testing/lib/network-diagnostics.sh | 60 | ||||
| -rwxr-xr-x | scripts/testing/lib/vm-utils.sh | 321 |
4 files changed, 553 insertions, 0 deletions
diff --git a/scripts/testing/lib/finalize-base-vm.sh b/scripts/testing/lib/finalize-base-vm.sh new file mode 100755 index 0000000..e3913ea --- /dev/null +++ b/scripts/testing/lib/finalize-base-vm.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Finalize base VM after installation +VM_NAME="archsetup-base" +echo "[i] Removing ISO from VM..." +virsh change-media $VM_NAME sda --eject 2>/dev/null || true +virsh change-media $VM_NAME hda --eject 2>/dev/null || true +echo "[✓] ISO removed" +echo "[i] Starting VM from installed system..." +virsh start $VM_NAME +echo "[i] Waiting for boot..." +sleep 30 +IP=$(virsh domifaddr $VM_NAME 2>/dev/null | grep -oP '(\d+\.){3}\d+' | head -1) +echo "[✓] Base VM is ready!" +echo "" +echo "Connect via:" +echo " Console: virsh console $VM_NAME" +echo " SSH: ssh root@$IP" +echo " Password: archsetup" +echo "" +echo "To create a test clone:" +echo " ./scripts/testing/run-test.sh" diff --git a/scripts/testing/lib/logging.sh b/scripts/testing/lib/logging.sh new file mode 100755 index 0000000..eda9eb1 --- /dev/null +++ b/scripts/testing/lib/logging.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# Logging utilities for archsetup testing +# Author: Craig Jennings <craigmartinjennings@gmail.com> +# License: GNU GPLv3 + +# Global log file (set by calling script) +LOGFILE="${LOGFILE:-/tmp/archsetup-test.log}" + +# Initialize logging +init_logging() { + local logfile="$1" + LOGFILE="$logfile" + + # Create log directory if it doesn't exist + mkdir -p "$(dirname "$LOGFILE")" + + # Initialize log file + echo "=== Test Log Started: $(date +'%Y-%m-%d %H:%M:%S') ===" > "$LOGFILE" + echo "" >> "$LOGFILE" +} + +# Log message (to file and optionally stdout) +log() { + local message="$1" + local timestamp + timestamp=$(date +'%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $message" >> "$LOGFILE" +} + +# Info message +info() { + local message="$1" + echo "[i] $message" + log "INFO: $message" +} + +# Success message +success() { + local message="$1" + echo "[✓] $message" + log "SUCCESS: $message" +} + +# Warning message +warn() { + local message="$1" + echo "[!] $message" + log "WARNING: $message" +} + +# Error message +error() { + local message="$1" + echo "[✗] $message" >&2 + log "ERROR: $message" +} + +# Fatal error (exits script) +fatal() { + local message="$1" + local exit_code="${2:-1}" + echo "[✗] FATAL: $message" >&2 + log "FATAL: $message (exit code: $exit_code)" + exit "$exit_code" +} + +# Section header +section() { + local title="$1" + echo "" + echo "=== $title ===" + log "=== $title ===" +} + +# Step message +step() { + local message="$1" + echo " -> $message" + log " STEP: $message" +} + +# Progress indicator (for long-running operations) +progress() { + local message="$1" + echo " ... $message" + log " PROGRESS: $message" +} + +# Clear progress line and show completion +complete() { + local message="$1" + echo " [✓] $message" + log " COMPLETE: $message" +} + +# Show command being executed (useful for debugging) +show_cmd() { + local cmd="$1" + echo "$ $cmd" + log "CMD: $cmd" +} + +# Separator line +separator() { + echo "----------------------------------------" +} + +# Summary statistics +summary() { + local passed="$1" + local failed="$2" + local total=$((passed + failed)) + + echo "" + separator + section "Test Summary" + echo " Total: $total" + echo " Passed: $passed" + echo " Failed: $failed" + separator + echo "" + + log "=== Test Summary ===" + log "Total: $total, Passed: $passed, Failed: $failed" +} + +# Timer utilities +declare -A TIMERS + +start_timer() { + local name="${1:-default}" + TIMERS[$name]=$(date +%s) + log "TIMER START: $name" +} + +stop_timer() { + local name="${1:-default}" + local start=${TIMERS[$name]} + local end=$(date +%s) + local duration=$((end - start)) + local mins=$((duration / 60)) + local secs=$((duration % 60)) + + if [ $mins -gt 0 ]; then + echo " Time: ${mins}m ${secs}s" + log "TIMER STOP: $name (${mins}m ${secs}s)" + else + echo " Time: ${secs}s" + log "TIMER STOP: $name (${secs}s)" + fi +} diff --git a/scripts/testing/lib/network-diagnostics.sh b/scripts/testing/lib/network-diagnostics.sh new file mode 100644 index 0000000..3f9735b --- /dev/null +++ b/scripts/testing/lib/network-diagnostics.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Network diagnostics for VM testing +# Author: Craig Jennings <craigmartinjennings@gmail.com> +# License: GNU GPLv3 + +# Note: logging.sh should already be sourced by the calling script + +# Run quick network diagnostics +# Args: $1 = VM IP address or hostname +run_network_diagnostics() { + local vm_host="$1" + + section "Pre-flight Network Diagnostics" + + # Test 1: Basic connectivity + step "Testing internet connectivity" + if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "ping -c 3 8.8.8.8 >/dev/null 2>&1"; then + success "Internet connectivity OK" + else + error "No internet connectivity" + return 1 + fi + + # Test 2: DNS resolution + step "Testing DNS resolution" + if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "nslookup archlinux.org >/dev/null 2>&1"; then + success "DNS resolution OK" + else + error "DNS resolution failed" + return 1 + fi + + # Test 3: Arch mirror accessibility + step "Testing Arch mirror access" + if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "curl -s -I https://mirrors.kernel.org/archlinux/ | head -1 | grep -qE '(200|301)'"; then + success "Arch mirrors accessible" + else + error "Cannot reach Arch mirrors" + return 1 + fi + + # Test 4: AUR accessibility + step "Testing AUR access" + if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "curl -s -I https://aur.archlinux.org/ | head -1 | grep -qE '(200|405)'"; then + success "AUR accessible" + else + error "Cannot reach AUR" + return 1 + fi + + # Show network info + info "Network configuration:" + sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host \ + "ip addr show | grep 'inet ' | grep -v '127.0.0.1'" 2>/dev/null | while read line; do + info " $line" + done + + success "Network diagnostics complete" + return 0 +} diff --git a/scripts/testing/lib/vm-utils.sh b/scripts/testing/lib/vm-utils.sh new file mode 100755 index 0000000..81aec33 --- /dev/null +++ b/scripts/testing/lib/vm-utils.sh @@ -0,0 +1,321 @@ +#!/bin/bash +# VM management utilities for archsetup testing +# Author: Craig Jennings <craigmartinjennings@gmail.com> +# License: GNU GPLv3 + +# Note: logging.sh should already be sourced by the calling script + +# VM configuration defaults +VM_CPUS="${VM_CPUS:-4}" +VM_RAM="${VM_RAM:-8192}" # MB +VM_DISK="${VM_DISK:-50}" # GB +VM_NETWORK="${VM_NETWORK:-default}" +LIBVIRT_URI="qemu:///system" # Use system session, not user session + +# Check if libvirt is running +check_libvirt() { + if ! systemctl is-active --quiet libvirtd; then + error "libvirtd service is not running" + info "Start it with: sudo systemctl start libvirtd" + return 1 + fi + return 0 +} + +# Check if user is in libvirt group +check_libvirt_group() { + if ! groups | grep -q libvirt; then + warn "Current user is not in libvirt group" + info "Add yourself with: sudo usermod -a -G libvirt $USER" + info "Then log out and back in for changes to take effect" + return 1 + fi + return 0 +} + +# Check if KVM is available +check_kvm() { + if [ ! -e /dev/kvm ]; then + error "KVM is not available" + info "Check if virtualization is enabled in BIOS" + info "Load kvm module: sudo modprobe kvm-intel (or kvm-amd)" + return 1 + fi + return 0 +} + +# Wait for VM to boot (check for SSH or serial console) +wait_for_vm() { + local vm_name="$1" + local timeout="${2:-300}" # 5 minutes default + local elapsed=0 + + progress "Waiting for VM $vm_name to boot..." + + while [ $elapsed -lt $timeout ]; do + if virsh --connect "$LIBVIRT_URI" domstate "$vm_name" 2>/dev/null | grep -q "running"; then + sleep 5 + complete "VM $vm_name is running" + return 0 + fi + sleep 2 + elapsed=$((elapsed + 2)) + done + + error "Timeout waiting for VM $vm_name to boot" + return 1 +} + +# Check if VM exists +vm_exists() { + local vm_name="$1" + virsh --connect "$LIBVIRT_URI" dominfo "$vm_name" &>/dev/null + return $? +} + +# Check if VM is running +vm_is_running() { + local vm_name="$1" + [ "$(virsh --connect "$LIBVIRT_URI" domstate "$vm_name" 2>/dev/null)" = "running" ] + return $? +} + +# Start VM +start_vm() { + local vm_name="$1" + + if vm_is_running "$vm_name"; then + warn "VM $vm_name is already running" + return 0 + fi + + step "Starting VM: $vm_name" + if virsh --connect "$LIBVIRT_URI" start "$vm_name" >> "$LOGFILE" 2>&1; then + success "VM $vm_name started" + return 0 + else + error "Failed to start VM $vm_name" + return 1 + fi +} + +# Stop VM gracefully +stop_vm() { + local vm_name="$1" + local timeout="${2:-60}" + + if ! vm_is_running "$vm_name"; then + info "VM $vm_name is not running" + return 0 + fi + + step "Shutting down VM: $vm_name" + if virsh --connect "$LIBVIRT_URI" shutdown "$vm_name" >> "$LOGFILE" 2>&1; then + # Wait for graceful shutdown + local elapsed=0 + while [ $elapsed -lt $timeout ]; do + if ! vm_is_running "$vm_name"; then + success "VM $vm_name stopped gracefully" + return 0 + fi + sleep 2 + elapsed=$((elapsed + 2)) + done + + warn "VM $vm_name did not stop gracefully, forcing..." + virsh --connect "$LIBVIRT_URI" destroy "$vm_name" >> "$LOGFILE" 2>&1 + fi + + success "VM $vm_name stopped" + return 0 +} + +# Destroy VM (force stop) +destroy_vm() { + local vm_name="$1" + + if ! vm_exists "$vm_name"; then + info "VM $vm_name does not exist" + return 0 + fi + + step "Destroying VM: $vm_name" + if vm_is_running "$vm_name"; then + virsh --connect "$LIBVIRT_URI" destroy "$vm_name" >> "$LOGFILE" 2>&1 + fi + + virsh --connect "$LIBVIRT_URI" undefine "$vm_name" --nvram >> "$LOGFILE" 2>&1 + success "VM $vm_name destroyed" + return 0 +} + +# Create snapshot +create_snapshot() { + local vm_name="$1" + local snapshot_name="$2" + + step "Creating snapshot: $snapshot_name" + if virsh --connect "$LIBVIRT_URI" snapshot-create-as "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then + success "Snapshot $snapshot_name created" + return 0 + else + error "Failed to create snapshot $snapshot_name" + return 1 + fi +} + +# Restore snapshot +restore_snapshot() { + local vm_name="$1" + local snapshot_name="$2" + + step "Restoring snapshot: $snapshot_name" + if virsh --connect "$LIBVIRT_URI" snapshot-revert "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then + success "Snapshot $snapshot_name restored" + return 0 + else + error "Failed to restore snapshot $snapshot_name" + return 1 + fi +} + +# Delete snapshot +delete_snapshot() { + local vm_name="$1" + local snapshot_name="$2" + + step "Deleting snapshot: $snapshot_name" + if virsh --connect "$LIBVIRT_URI" snapshot-delete "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then + success "Snapshot $snapshot_name deleted" + return 0 + else + error "Failed to delete snapshot $snapshot_name" + return 1 + fi +} + +# Clone disk image (copy-on-write) +clone_disk() { + local base_image="$1" + local new_image="$2" + + if [ ! -f "$base_image" ]; then + error "Base image not found: $base_image" + return 1 + fi + + step "Cloning disk image (full copy)" + if qemu-img convert -f qcow2 -O qcow2 "$base_image" "$new_image" >> "$LOGFILE" 2>&1; then + success "Disk cloned: $new_image" + else + error "Failed to clone disk" + return 1 + fi + + # Truncate machine-id so systemd generates a new one on boot (avoids DHCP conflicts) + step "Clearing machine-id for unique network identity" + if guestfish -a "$new_image" -i truncate /etc/machine-id >> "$LOGFILE" 2>&1; then + success "Machine-ID cleared (will regenerate on boot)" + return 0 + else + warn "Failed to clear machine-ID (guestfish failed)" + info "Network may conflict with base VM if both run simultaneously" + return 0 # Don't fail the whole operation + fi +} + +# Get VM IP address (requires guest agent or DHCP lease) +get_vm_ip() { + local vm_name="$1" + + # Try guest agent first + local ip + ip=$(virsh --connect "$LIBVIRT_URI" domifaddr "$vm_name" 2>/dev/null | grep -oP '(\d+\.){3}\d+' | head -1) + + if [ -n "$ip" ]; then + echo "$ip" + return 0 + fi + + # Fall back to DHCP leases + local mac + mac=$(virsh --connect "$LIBVIRT_URI" domiflist "$vm_name" | grep -oP '([0-9a-f]{2}:){5}[0-9a-f]{2}' | head -1) + + if [ -n "$mac" ]; then + ip=$(grep "$mac" /var/lib/libvirt/dnsmasq/default.leases 2>/dev/null | awk '{print $3}') + if [ -n "$ip" ]; then + echo "$ip" + return 0 + fi + fi + + return 1 +} + +# Execute command in VM via SSH +vm_exec() { + local vm_name="$1" + shift + local cmd="$*" + + local ip + ip=$(get_vm_ip "$vm_name") + + if [ -z "$ip" ]; then + error "Could not get IP address for VM $vm_name" + return 1 + fi + + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + "root@$ip" "$cmd" 2>> "$LOGFILE" +} + +# Copy file to VM +copy_to_vm() { + local vm_name="$1" + local local_file="$2" + local remote_path="$3" + + local ip + ip=$(get_vm_ip "$vm_name") + + if [ -z "$ip" ]; then + error "Could not get IP address for VM $vm_name" + return 1 + fi + + step "Copying $local_file to VM" + if scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + "$local_file" "root@$ip:$remote_path" >> "$LOGFILE" 2>&1; then + success "File copied to VM" + return 0 + else + error "Failed to copy file to VM" + return 1 + fi +} + +# Copy file from VM +copy_from_vm() { + local vm_name="$1" + local remote_file="$2" + local local_path="$3" + + local ip + ip=$(get_vm_ip "$vm_name") + + if [ -z "$ip" ]; then + error "Could not get IP address for VM $vm_name" + return 1 + fi + + step "Copying $remote_file from VM" + if scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + "root@$ip:$remote_file" "$local_path" >> "$LOGFILE" 2>&1; then + success "File copied from VM" + return 0 + else + error "Failed to copy file from VM" + return 1 + fi +} |
