mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 05:11:09 -05:00
Refactor test utilities and enhance ASGI settings
- Cleaned up and standardized assertions in ApiTestMixin for API response validation. - Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE. - Removed unused imports and improved formatting in settings.py. - Refactored URL patterns in urls.py for better readability and organization. - Enhanced view functions in views.py for consistency and clarity. - Added .flake8 configuration for linting and style enforcement. - Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
@@ -5,7 +5,6 @@ Handles VM creation, configuration, and lifecycle management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import subprocess
|
||||
@@ -33,9 +32,9 @@ class UnraidVMManager:
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
|
||||
if result.returncode == 0 and "Connected" in result.stdout:
|
||||
logger.info("Successfully connected to Unraid via SSH")
|
||||
return True
|
||||
@@ -75,23 +74,28 @@ class UnraidVMManager:
|
||||
hash_bytes = hash_obj.digest()[:3]
|
||||
return ":".join([f"{b:02x}" for b in hash_bytes])
|
||||
|
||||
def create_vm_xml(self, vm_memory: int, vm_vcpus: int, vm_ip: str,
|
||||
existing_uuid: str = None) -> str:
|
||||
def create_vm_xml(
|
||||
self,
|
||||
vm_memory: int,
|
||||
vm_vcpus: int,
|
||||
vm_ip: str,
|
||||
existing_uuid: str = None,
|
||||
) -> str:
|
||||
"""Generate VM XML configuration from template file."""
|
||||
vm_uuid = existing_uuid if existing_uuid else str(uuid.uuid4())
|
||||
|
||||
|
||||
# Read XML template from file
|
||||
template_path = Path(__file__).parent / "thrillwiki-vm-template.xml"
|
||||
if not template_path.exists():
|
||||
raise FileNotFoundError(f"VM XML template not found at {template_path}")
|
||||
|
||||
with open(template_path, 'r', encoding='utf-8') as f:
|
||||
|
||||
with open(template_path, "r", encoding="utf-8") as f:
|
||||
xml_template = f.read()
|
||||
|
||||
|
||||
# Calculate CPU topology
|
||||
cpu_cores = vm_vcpus // 2 if vm_vcpus > 1 else 1
|
||||
cpu_threads = 2 if vm_vcpus > 1 else 1
|
||||
|
||||
|
||||
# Replace placeholders with actual values
|
||||
xml_content = xml_template.format(
|
||||
VM_NAME=self.vm_name,
|
||||
@@ -100,17 +104,18 @@ class UnraidVMManager:
|
||||
VM_VCPUS=vm_vcpus,
|
||||
CPU_CORES=cpu_cores,
|
||||
CPU_THREADS=cpu_threads,
|
||||
MAC_SUFFIX=self._generate_mac_suffix(vm_ip)
|
||||
MAC_SUFFIX=self._generate_mac_suffix(vm_ip),
|
||||
)
|
||||
|
||||
|
||||
return xml_content.strip()
|
||||
|
||||
def upload_iso_to_unraid(self, local_iso_path: Path) -> str:
|
||||
"""Upload ISO to Unraid server."""
|
||||
remote_iso_path = f"/mnt/user/isos/{self.vm_name}-ubuntu-autoinstall.iso"
|
||||
|
||||
remote_iso_path = f"/mnt/user/isos/{
|
||||
self.vm_name}-ubuntu-autoinstall.iso"
|
||||
|
||||
logger.info(f"Uploading ISO to Unraid: {remote_iso_path}")
|
||||
|
||||
|
||||
try:
|
||||
# Remove old ISO if exists
|
||||
subprocess.run(
|
||||
@@ -118,34 +123,42 @@ class UnraidVMManager:
|
||||
shell=True,
|
||||
check=False, # Don't fail if file doesn't exist
|
||||
)
|
||||
|
||||
|
||||
# Upload new ISO
|
||||
subprocess.run(
|
||||
f"scp {local_iso_path} {self.unraid_user}@{self.unraid_host}:{remote_iso_path}",
|
||||
shell=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"ISO uploaded successfully: {remote_iso_path}")
|
||||
return remote_iso_path
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload ISO: {e}")
|
||||
raise
|
||||
|
||||
def create_vm(self, vm_memory: int, vm_vcpus: int, vm_disk_size: int, vm_ip: str) -> bool:
|
||||
def create_vm(
|
||||
self, vm_memory: int, vm_vcpus: int, vm_disk_size: int, vm_ip: str
|
||||
) -> bool:
|
||||
"""Create or update the VM on Unraid."""
|
||||
try:
|
||||
vm_exists = self.check_vm_exists()
|
||||
|
||||
if vm_exists:
|
||||
logger.info(f"VM {self.vm_name} already exists, updating configuration...")
|
||||
logger.info(
|
||||
f"VM {
|
||||
self.vm_name} already exists, updating configuration..."
|
||||
)
|
||||
# Always try to stop VM before updating
|
||||
current_status = self.vm_status()
|
||||
logger.info(f"Current VM status: {current_status}")
|
||||
|
||||
|
||||
if current_status not in ["shut off", "unknown"]:
|
||||
logger.info(f"Stopping VM {self.vm_name} for configuration update...")
|
||||
logger.info(
|
||||
f"Stopping VM {
|
||||
self.vm_name} for configuration update..."
|
||||
)
|
||||
self.stop_vm()
|
||||
time.sleep(3)
|
||||
else:
|
||||
@@ -174,13 +187,19 @@ class UnraidVMManager:
|
||||
"""
|
||||
subprocess.run(disk_cmd, shell=True, check=True)
|
||||
else:
|
||||
logger.info(f"Virtual disk already exists for VM {self.vm_name}")
|
||||
logger.info(
|
||||
f"Virtual disk already exists for VM {
|
||||
self.vm_name}"
|
||||
)
|
||||
|
||||
existing_uuid = None
|
||||
|
||||
if vm_exists:
|
||||
# Get existing VM UUID
|
||||
cmd = f"ssh {self.unraid_user}@{self.unraid_host} 'virsh dumpxml {self.vm_name} | grep \"<uuid>\" | sed \"s/<uuid>//g\" | sed \"s/<\\/uuid>//g\" | tr -d \" \"'"
|
||||
cmd = f'ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} \'virsh dumpxml {
|
||||
self.vm_name} | grep "<uuid>" | sed "s/<uuid>//g" | sed "s/<\\/uuid>//g" | tr -d " "\''
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
@@ -199,34 +218,49 @@ class UnraidVMManager:
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
is_persistent = self.vm_name in persistent_check.stdout
|
||||
|
||||
|
||||
if is_persistent:
|
||||
# Undefine persistent VM with NVRAM flag
|
||||
logger.info(f"VM {self.vm_name} is persistent, undefining with NVRAM for reconfiguration...")
|
||||
logger.info(
|
||||
f"VM {
|
||||
self.vm_name} is persistent, undefining with NVRAM for reconfiguration..."
|
||||
)
|
||||
subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'virsh undefine {self.vm_name} --nvram'",
|
||||
f"ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} 'virsh undefine {
|
||||
self.vm_name} --nvram'",
|
||||
shell=True,
|
||||
check=True,
|
||||
)
|
||||
logger.info(f"Persistent VM {self.vm_name} undefined for reconfiguration")
|
||||
logger.info(
|
||||
f"Persistent VM {
|
||||
self.vm_name} undefined for reconfiguration"
|
||||
)
|
||||
else:
|
||||
# Handle transient VM - just destroy it
|
||||
logger.info(f"VM {self.vm_name} is transient, destroying for reconfiguration...")
|
||||
logger.info(
|
||||
f"VM {
|
||||
self.vm_name} is transient, destroying for reconfiguration..."
|
||||
)
|
||||
if self.vm_status() == "running":
|
||||
subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'virsh destroy {self.vm_name}'",
|
||||
shell=True,
|
||||
check=True,
|
||||
)
|
||||
logger.info(f"Transient VM {self.vm_name} destroyed for reconfiguration")
|
||||
logger.info(
|
||||
f"Transient VM {
|
||||
self.vm_name} destroyed for reconfiguration"
|
||||
)
|
||||
|
||||
# Generate VM XML with appropriate UUID
|
||||
vm_xml = self.create_vm_xml(vm_memory, vm_vcpus, vm_ip, existing_uuid)
|
||||
xml_file = f"/tmp/{self.vm_name}.xml"
|
||||
|
||||
with open(xml_file, "w", encoding='utf-8') as f:
|
||||
with open(xml_file, "w", encoding="utf-8") as f:
|
||||
f.write(vm_xml)
|
||||
|
||||
# Copy XML to Unraid and define/redefine VM
|
||||
@@ -245,7 +279,10 @@ class UnraidVMManager:
|
||||
|
||||
# Ensure VM is set to autostart for persistent configuration
|
||||
subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'virsh autostart {self.vm_name}'",
|
||||
f"ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} 'virsh autostart {
|
||||
self.vm_name}'",
|
||||
shell=True,
|
||||
check=False, # Don't fail if autostart is already enabled
|
||||
)
|
||||
@@ -281,7 +318,9 @@ class UnraidVMManager:
|
||||
# Copy template to create NVRAM file
|
||||
logger.info(f"Creating NVRAM file: {nvram_path}")
|
||||
result = subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'cp /usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd {nvram_path}'",
|
||||
f"ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} 'cp /usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd {nvram_path}'",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -316,7 +355,10 @@ class UnraidVMManager:
|
||||
return False
|
||||
|
||||
# Get VM UUID from XML
|
||||
cmd = f"ssh {self.unraid_user}@{self.unraid_host} 'virsh dumpxml {self.vm_name} | grep \"<uuid>\" | sed \"s/<uuid>//g\" | sed \"s/<\\/uuid>//g\" | tr -d \" \"'"
|
||||
cmd = f'ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} \'virsh dumpxml {
|
||||
self.vm_name} | grep "<uuid>" | sed "s/<uuid>//g" | sed "s/<\\/uuid>//g" | tr -d " "\''
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
@@ -361,37 +403,49 @@ class UnraidVMManager:
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# Wait up to 30 seconds for graceful shutdown
|
||||
logger.info(f"Waiting for VM {self.vm_name} to shutdown gracefully...")
|
||||
logger.info(
|
||||
f"Waiting for VM {
|
||||
self.vm_name} to shutdown gracefully..."
|
||||
)
|
||||
for i in range(30):
|
||||
status = self.vm_status()
|
||||
if status in ["shut off", "unknown"]:
|
||||
logger.info(f"VM {self.vm_name} stopped gracefully")
|
||||
return True
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
# If still running after 30 seconds, force destroy
|
||||
logger.warning(f"VM {self.vm_name} didn't shutdown gracefully, forcing destroy...")
|
||||
logger.warning(
|
||||
f"VM {
|
||||
self.vm_name} didn't shutdown gracefully, forcing destroy..."
|
||||
)
|
||||
destroy_result = subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'virsh destroy {self.vm_name}'",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
|
||||
if destroy_result.returncode == 0:
|
||||
logger.info(f"VM {self.vm_name} forcefully destroyed")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to destroy VM: {destroy_result.stderr}")
|
||||
logger.error(
|
||||
f"Failed to destroy VM: {
|
||||
destroy_result.stderr}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
logger.error(f"Failed to initiate VM shutdown: {result.stderr}")
|
||||
logger.error(
|
||||
f"Failed to initiate VM shutdown: {
|
||||
result.stderr}"
|
||||
)
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
@@ -418,7 +472,9 @@ class UnraidVMManager:
|
||||
lines = result.stdout.strip().split("\\n")
|
||||
for line in lines:
|
||||
if "ipv4" in line:
|
||||
# Extract IP from line like: vnet0 52:54:00:xx:xx:xx ipv4 192.168.1.100/24
|
||||
# Extract IP from line like: vnet0
|
||||
# 52:54:00:xx:xx:xx ipv4
|
||||
# 192.168.1.100/24
|
||||
parts = line.split()
|
||||
if len(parts) >= 4:
|
||||
ip_with_mask = parts[3]
|
||||
@@ -427,7 +483,8 @@ class UnraidVMManager:
|
||||
return ip
|
||||
|
||||
logger.info(
|
||||
f"Waiting for VM IP... (attempt {attempt + 1}/{max_attempts}) - Ubuntu autoinstall in progress"
|
||||
f"Waiting for VM IP... (attempt {
|
||||
attempt + 1}/{max_attempts}) - Ubuntu autoinstall in progress"
|
||||
)
|
||||
time.sleep(10)
|
||||
|
||||
@@ -460,7 +517,10 @@ class UnraidVMManager:
|
||||
def delete_vm(self) -> bool:
|
||||
"""Completely remove VM and all associated files."""
|
||||
try:
|
||||
logger.info(f"Deleting VM {self.vm_name} and all associated files...")
|
||||
logger.info(
|
||||
f"Deleting VM {
|
||||
self.vm_name} and all associated files..."
|
||||
)
|
||||
|
||||
# Check if VM exists
|
||||
if not self.check_vm_exists():
|
||||
@@ -476,7 +536,10 @@ class UnraidVMManager:
|
||||
# Undefine VM with NVRAM
|
||||
logger.info(f"Undefining VM {self.vm_name}...")
|
||||
subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'virsh undefine {self.vm_name} --nvram'",
|
||||
f"ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} 'virsh undefine {
|
||||
self.vm_name} --nvram'",
|
||||
shell=True,
|
||||
check=True,
|
||||
)
|
||||
@@ -491,7 +554,10 @@ class UnraidVMManager:
|
||||
|
||||
# Remove autoinstall ISO
|
||||
subprocess.run(
|
||||
f"ssh {self.unraid_user}@{self.unraid_host} 'rm -f /mnt/user/isos/{self.vm_name}-ubuntu-autoinstall.iso'",
|
||||
f"ssh {
|
||||
self.unraid_user}@{
|
||||
self.unraid_host} 'rm -f /mnt/user/isos/{
|
||||
self.vm_name}-ubuntu-autoinstall.iso'",
|
||||
shell=True,
|
||||
check=False, # Don't fail if file doesn't exist
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user