Private
Public Access
1
0

fix: OS template type matching for LXC/KVM nodes

- Add is_lxc column to ext_proxmox_oses for tracking template type
- OS model now has vmType() helper returning 'lxc' or 'qemu'
- Install uses OS template's type for clone API path (not node type)
- createServer validates OS type matches node type before provisioning
- calculateNode filters nodes by LXC/KVM type based on selected OS
- Reinstall OS list filtered to only show matching template types
- Admin location OS repeater now has LXC Template checkbox
This commit is contained in:
2026-03-02 21:23:10 -05:00
parent 153bcfcea5
commit ef1499cde4
5 changed files with 83 additions and 13 deletions

View File

@@ -470,10 +470,22 @@ class Proxmox extends ClassServer
$server->save();
$vmType = $server->node->vmType();
// Use the OS template's type for cloning (source type determines API path)
// The template type should match the node type (LXC templates for LXC nodes, KVM for KVM)
$templateVmType = $server->os->vmType();
$nodeVmType = $server->node->vmType();
// Create the VM/CT
$cloneResult = $this->requestAndWait('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->os->vm_id . '/clone', 'post', [
// Warn if there's a type mismatch (shouldn't happen if OS selection is filtered correctly)
if ($templateVmType !== $nodeVmType) {
\Log::warning('OS template type does not match node type', [
'server_id' => $server->id,
'os_type' => $templateVmType,
'node_type' => $nodeVmType,
]);
}
// Create the VM/CT - use template's vmType for source path
$cloneResult = $this->requestAndWait('/nodes/' . $server->node->name . '/' . $templateVmType . '/' . $server->os->vm_id . '/clone', 'post', [
'newid' => $server->vm_id,
'hostname' => $server->hostname,
'target' => $server->node->name,
@@ -698,7 +710,7 @@ class Proxmox extends ClassServer
ReinstallJob::dispatch($server);
}
private function calculateNode(Location $location, Service $service, $settings)
private function calculateNode(Location $location, Service $service, $settings, ?bool $isLxc = null)
{
// What type of selection do we want to use?
/* 'random' => 'Random Node',
@@ -708,12 +720,18 @@ class Proxmox extends ClassServer
'least_loaded_disk' => 'Least Loaded Node (based on disk)',
'least_loaded_cpu' => 'Least Loaded Node (based on CPU)',
*/
$cpuNeeded = $settings['cores'] * $settings['sockets'];
$cpuNeeded = $settings['cores'] * ($settings['sockets'] ?? 1);
$memoryNeeded = (int) $settings['memory'];
$diskNeeded = (int) $settings['disk'];
// Build base query for nodes, optionally filtering by type (LXC or KVM)
$nodeQuery = $location->nodes();
if ($isLxc !== null) {
$nodeQuery->where('is_lxc', $isLxc);
}
if ($location->server_placement == 'random') {
$nodes = $location->nodes()->inRandomOrder()->get();
$nodes = (clone $nodeQuery)->inRandomOrder()->get();
$node = null;
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -723,7 +741,7 @@ class Proxmox extends ClassServer
}
} elseif ($location->server_placement == 'fill') {
// Get the nodes that have enough resources
$nodes = $location->nodes()->get();
$nodes = (clone $nodeQuery)->get();
$node = null;
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -733,7 +751,7 @@ class Proxmox extends ClassServer
}
} elseif ($location->server_placement == 'least_loaded_server') {
// Get the node with the least
$nodes = $location->nodes()->withCount('servers')->orderBy('servers_count', 'asc')->get();
$nodes = (clone $nodeQuery)->withCount('servers')->orderBy('servers_count', 'asc')->get();
$node = null;
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -742,7 +760,7 @@ class Proxmox extends ClassServer
}
}
} elseif ($location->server_placement == 'least_loaded_ram') {
$nodes = $location->nodes()->withCount('servers')->orderBy('ram', 'asc')->get();
$nodes = (clone $nodeQuery)->withCount('servers')->orderBy('ram', 'asc')->get();
$nodeAndRam = [];
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -756,7 +774,7 @@ class Proxmox extends ClassServer
break;
}
} elseif ($location->server_placement == 'least_loaded_disk') {
$nodes = $location->nodes()->withCount('servers')->orderBy('disk', 'asc')->get();
$nodes = (clone $nodeQuery)->withCount('servers')->orderBy('disk', 'asc')->get();
$nodeAndDisk = [];
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -770,7 +788,7 @@ class Proxmox extends ClassServer
break;
}
} elseif ($location->server_placement == 'least_loaded_cpu') {
$nodes = $location->nodes()->withCount('servers')->orderBy('cpu', 'asc')->get();
$nodes = (clone $nodeQuery)->withCount('servers')->orderBy('cpu', 'asc')->get();
$nodeAndCpu = [];
foreach ($nodes as $n) {
if ($n->canStoreServer($memoryNeeded, $diskNeeded, $cpuNeeded)) {
@@ -967,8 +985,18 @@ class Proxmox extends ClassServer
$settings = array_merge($settings, $properties);
$location = Location::findOrFail($settings['location_id']);
$node = $this->calculateNode($location, $service, $settings);
// Get the OS first to determine if it's LXC or KVM
$os = $location->os()->findOrFail($settings['os']);
// Calculate node - pass the OS type so it selects a matching node (LXC or KVM)
$node = $this->calculateNode($location, $service, $settings, $os->is_lxc);
// Verify OS type matches node type
if ($os->is_lxc !== ($node->is_lxc ?? false)) {
throw new Exception('OS template type (' . ($os->is_lxc ? 'LXC' : 'KVM') . ') does not match available node type (' . ($node->is_lxc ? 'LXC' : 'KVM') . '). Please select a different OS or contact support.');
}
// Get ippools, then check if a pool has enough IPs
$ipv4 = (object) [
'ips' => collect(),