|
|
@@ -144,22 +144,42 @@ async def stop_vm(vm_name: str):
|
|
|
async def create_vm(name: str, memory: int, disk_size: int, cpus: int = 1, iso: str = ""):
|
|
|
if not iso:
|
|
|
return {"error": "ISO path is required"}
|
|
|
+
|
|
|
images_path = os.environ.get('LIBVIRT_IMAGES_PATH', '/var/lib/libvirt/images')
|
|
|
disk_path = f"{images_path}/{name}.qcow2"
|
|
|
- iso_path = iso # iso ya es la ruta completa desde el select
|
|
|
+ iso_path = iso
|
|
|
|
|
|
- print(f"Using ISO path: {iso_path}")
|
|
|
+ print(f"[CREATE_VM] Starting VM creation: name={name}, memory={memory}, disk_size={disk_size}, cpus={cpus}")
|
|
|
+ print(f"[CREATE_VM] Using ISO path: {iso_path}")
|
|
|
|
|
|
- # Create disk
|
|
|
+ # Create disk using sudo script for proper permissions
|
|
|
try:
|
|
|
- result = subprocess.run(['qemu-img', 'create', '-f', 'qcow2', disk_path, f'{disk_size}G'], capture_output=True, text=True)
|
|
|
+ # Get absolute path to the script (up one level from app directory)
|
|
|
+ app_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
+ project_root = os.path.dirname(app_dir)
|
|
|
+ script_path = os.path.join(project_root, 'create_disk.sh')
|
|
|
+ print(f"[CREATE_VM] Script path: {script_path}")
|
|
|
+ result = subprocess.run(['sudo', script_path, disk_path, f'{disk_size}G'], capture_output=True, text=True, timeout=60)
|
|
|
+ print(f"[CREATE_VM] Disk creation return code: {result.returncode}")
|
|
|
if result.returncode != 0:
|
|
|
- return {"error": f"Failed to create disk: {result.stderr}"}
|
|
|
+ error_msg = result.stderr if result.stderr else result.stdout
|
|
|
+ print(f"[CREATE_VM] ERROR creating disk: {error_msg}")
|
|
|
+ return {"error": f"Failed to create disk: {error_msg}"}
|
|
|
+ print(f"[CREATE_VM] Disk created successfully: {disk_path}")
|
|
|
+ except subprocess.TimeoutExpired:
|
|
|
+ return {"error": "Disk creation timed out"}
|
|
|
except Exception as e:
|
|
|
+ print(f"[CREATE_VM] Exception creating disk: {str(e)}")
|
|
|
return {"error": f"Error creating disk: {str(e)}"}
|
|
|
|
|
|
+ # Verify disk exists
|
|
|
+ if not os.path.exists(disk_path):
|
|
|
+ print(f"[CREATE_VM] ERROR: Disk file not found after creation: {disk_path}")
|
|
|
+ return {"error": f"Disk file not created at {disk_path}"}
|
|
|
+
|
|
|
# Generate unique MAC
|
|
|
mac = f"52:54:00:{random.randint(0, 255):02x}:{random.randint(0, 255):02x}:{random.randint(0, 255):02x}"
|
|
|
+ print(f"[CREATE_VM] Generated MAC: {mac}")
|
|
|
|
|
|
# Basic XML template
|
|
|
xml_template = f"""<domain type='kvm'>
|
|
|
@@ -224,12 +244,33 @@ async def create_vm(name: str, memory: int, disk_size: int, cpus: int = 1, iso:
|
|
|
</devices>
|
|
|
</domain>"""
|
|
|
|
|
|
+ print(f"[CREATE_VM] XML template prepared for VM definition")
|
|
|
+
|
|
|
try:
|
|
|
+ print(f"[CREATE_VM] Defining VM with libvirt...")
|
|
|
domain = conn.defineXML(xml_template)
|
|
|
+ print(f"[CREATE_VM] VM defined successfully. Starting VM...")
|
|
|
domain.create()
|
|
|
+ print(f"[CREATE_VM] VM started successfully!")
|
|
|
return {"message": f"VM {name} created with {cpus} CPUs, {memory} MiB memory, {disk_size}G disk, booting from {iso}"}
|
|
|
+ except libvirt.libvirtError as e:
|
|
|
+ print(f"[CREATE_VM] Libvirt error: {str(e)}")
|
|
|
+ # Try to clean up the disk if VM definition failed
|
|
|
+ try:
|
|
|
+ os.remove(disk_path)
|
|
|
+ print(f"[CREATE_VM] Cleaned up disk file after error")
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ return {"error": f"Libvirt error: {str(e)}"}
|
|
|
except Exception as e:
|
|
|
- return {"error": str(e)}
|
|
|
+ print(f"[CREATE_VM] Unexpected error: {str(e)}")
|
|
|
+ # Try to clean up the disk if VM definition failed
|
|
|
+ try:
|
|
|
+ os.remove(disk_path)
|
|
|
+ print(f"[CREATE_VM] Cleaned up disk file after error")
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ return {"error": f"Error: {str(e)}"}
|
|
|
|
|
|
@app.get("/console/{vm_name}")
|
|
|
async def get_console(vm_name: str):
|
|
|
@@ -331,35 +372,57 @@ async def add_disk_to_vm(vm_name: str, disk_name: str, disk_size: int):
|
|
|
images_path = os.environ.get('LIBVIRT_IMAGES_PATH', '/var/lib/libvirt/images')
|
|
|
disk_path = f"{images_path}/{disk_name}.qcow2"
|
|
|
|
|
|
+ print(f"[ADD_DISK] Starting disk addition: vm={vm_name}, disk_name={disk_name}, disk_size={disk_size}")
|
|
|
+
|
|
|
try:
|
|
|
+ # Get the VM first to check its state
|
|
|
+ print(f"[ADD_DISK] Looking up VM: {vm_name}")
|
|
|
+ domain = conn.lookupByName(vm_name)
|
|
|
+
|
|
|
+ # Check if VM is running BEFORE creating disk
|
|
|
+ print(f"[ADD_DISK] Checking VM state...")
|
|
|
+ if domain.isActive():
|
|
|
+ print(f"[ADD_DISK] ERROR: VM is running, must be shut off to add disk")
|
|
|
+ return {"error": "Error: La máquina virtual debe estar PARADA (shut off) para agregar un disco"}
|
|
|
+ print(f"[ADD_DISK] VM state OK (shut off)")
|
|
|
+
|
|
|
# Check if disk already exists
|
|
|
+ print(f"[ADD_DISK] Checking if disk exists: {disk_path}")
|
|
|
if os.path.exists(disk_path):
|
|
|
- return {"error": f"Disk {disk_name}.qcow2 already exists"}
|
|
|
+ print(f"[ADD_DISK] ERROR: Disk already exists")
|
|
|
+ return {"error": f"Error: El disco {disk_name}.qcow2 ya existe. Usa un nombre de disco diferente"}
|
|
|
+ print(f"[ADD_DISK] Disk does not exist (OK)")
|
|
|
|
|
|
- # Create the disk
|
|
|
- result = subprocess.run(['qemu-img', 'create', '-f', 'qcow2', disk_path, f'{disk_size}G'],
|
|
|
- capture_output=True, text=True)
|
|
|
+ # Create the disk using sudo script for proper permissions
|
|
|
+ print(f"[ADD_DISK] Creating disk...")
|
|
|
+ app_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
+ project_root = os.path.dirname(app_dir)
|
|
|
+ script_path = os.path.join(project_root, 'create_disk.sh')
|
|
|
+ result = subprocess.run(['sudo', script_path, disk_path, f'{disk_size}G'],
|
|
|
+ capture_output=True, text=True, timeout=60)
|
|
|
if result.returncode != 0:
|
|
|
- return {"error": f"Failed to create disk: {result.stderr}"}
|
|
|
-
|
|
|
- # Get the VM
|
|
|
- domain = conn.lookupByName(vm_name)
|
|
|
- if domain.isActive():
|
|
|
- return {"error": "VM must be shut off to add a disk"}
|
|
|
+ error_msg = result.stderr if result.stderr else result.stdout
|
|
|
+ print(f"[ADD_DISK] ERROR creating disk: {error_msg}")
|
|
|
+ return {"error": f"Error al crear disco: {error_msg}"}
|
|
|
+ print(f"[ADD_DISK] Disk created successfully")
|
|
|
|
|
|
# Get the XML and add the disk
|
|
|
+ print(f"[ADD_DISK] Getting VM XML...")
|
|
|
xml_desc = domain.XMLDesc()
|
|
|
root = ET.fromstring(xml_desc)
|
|
|
|
|
|
# Find the devices section
|
|
|
devices = root.find("devices")
|
|
|
if devices is None:
|
|
|
- return {"error": "No devices section found in VM XML"}
|
|
|
+ print(f"[ADD_DISK] ERROR: No devices section in XML")
|
|
|
+ return {"error": "Error: No se encontró sección devices en la configuración de la VM"}
|
|
|
|
|
|
# Find the next available disk target (vda, vdb, vdc, etc.)
|
|
|
+ print(f"[ADD_DISK] Finding next available disk target...")
|
|
|
existing_targets = set()
|
|
|
for disk in root.findall(".//disk/target"):
|
|
|
existing_targets.add(disk.get('dev'))
|
|
|
+ print(f"[ADD_DISK] Existing targets: {existing_targets}")
|
|
|
|
|
|
# Generate next available target
|
|
|
target_letters = "abcdefghijklmnopqrstuvwxyz"
|
|
|
@@ -371,9 +434,12 @@ async def add_disk_to_vm(vm_name: str, disk_name: str, disk_size: int):
|
|
|
break
|
|
|
|
|
|
if next_target is None:
|
|
|
- return {"error": "No available disk targets"}
|
|
|
+ print(f"[ADD_DISK] ERROR: No available disk targets")
|
|
|
+ return {"error": "Error: No hay dispositivos de disco disponibles"}
|
|
|
+ print(f"[ADD_DISK] Next target: {next_target}")
|
|
|
|
|
|
# Create the new disk element
|
|
|
+ print(f"[ADD_DISK] Creating disk XML element...")
|
|
|
new_disk = ET.Element("disk", {"type": "file", "device": "disk"})
|
|
|
ET.SubElement(new_disk, "driver", {"name": "qemu", "type": "qcow2"})
|
|
|
ET.SubElement(new_disk, "source", {"file": disk_path})
|
|
|
@@ -383,13 +449,20 @@ async def add_disk_to_vm(vm_name: str, disk_name: str, disk_size: int):
|
|
|
devices.append(new_disk)
|
|
|
|
|
|
# Convert back to string and redefine the VM
|
|
|
+ print(f"[ADD_DISK] Redefining VM with new disk...")
|
|
|
new_xml = ET.tostring(root, encoding='unicode')
|
|
|
domain.undefine()
|
|
|
conn.defineXML(new_xml)
|
|
|
+ print(f"[ADD_DISK] VM redefined successfully")
|
|
|
|
|
|
- return {"message": f"Disk {disk_name}.qcow2 ({disk_size}G) added to {vm_name} as {next_target}"}
|
|
|
+ print(f"[ADD_DISK] SUCCESS: Disk added")
|
|
|
+ return {"message": f"Disco {disk_name}.qcow2 ({disk_size}G) agregado a {vm_name} como {next_target}"}
|
|
|
+ except libvirt.libvirtError as e:
|
|
|
+ print(f"[ADD_DISK] Libvirt error: {str(e)}")
|
|
|
+ return {"error": f"Error de libvirt: {str(e)}"}
|
|
|
except Exception as e:
|
|
|
- return {"error": str(e)}
|
|
|
+ print(f"[ADD_DISK] Unexpected error: {str(e)}")
|
|
|
+ return {"error": f"Error: {str(e)}"}
|
|
|
|
|
|
@app.post("/disks")
|
|
|
async def create_disk(name: str, size: int):
|