Last active
August 11, 2023 22:17
-
-
Save lucanastasio/03618be000df7969726d932f395f63e3 to your computer and use it in GitHub Desktop.
Proxmox passthrough saver hook script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# Copyright (c) 2021 Luca Anastasio | |
# anastasio<dot>lu<at>gmail<dot>com | |
# Hookscript to avoid taking PCI passed through devices from running VMs | |
# also attaches them to their placeholders after guests are stopped | |
# You can set this via qm with | |
# qm set 100 --hookscript local:snippets/hookscript.pl | |
use strict; | |
use warnings; | |
my $dryRun = 0; | |
print "GUEST HOOK: " . join(' ', @ARGV). "\n"; | |
my $vmid = shift; | |
my $phase = shift; | |
my $placeholder = "gpu-placeholder-vm-"; | |
sub get_vm_pci { | |
my @retstr = qx(qm config $_[0] --current | grep hostpci | awk '{print substr(\$2,1,5)}'); | |
chomp(@retstr); | |
return @retstr; | |
} | |
sub get_vm_name { | |
my $retstr = qx(qm config $_[0] | grep name | awk '{print \$2}'); | |
chomp($retstr); | |
return $retstr; | |
} | |
sub get_vm_run { | |
my @retstr = qx(qm list | grep running | awk '{print \$1}'); | |
chomp(@retstr); | |
return @retstr; | |
} | |
sub get_vm_hold { | |
my @retstr = qx(qm list | grep $placeholder | grep stopped | awk '{print \$1}'); | |
chomp(@retstr); | |
return @retstr; | |
} | |
sub get_pci_id { | |
my $retstr = qx(lspci -s $_[0].0 -n | awk '{print \$3}'); | |
chomp($retstr); | |
return $retstr; | |
} | |
sub get_pci_from_id { | |
my @retstr = qx(lspci -n | grep $_[0] | awk '{print substr(\$1,1,5)}'); | |
chomp(@retstr); | |
return @retstr; | |
} | |
sub replace_vm_pci { | |
# old PCI, new PCI | |
my $orig = qx(qm config $vmid | grep \": $_[0],\"); | |
my $hostpci = substr($orig, 0, index($orig, ':')); | |
my $newconf = substr($orig, index($orig, ' ')+1); | |
$newconf =~ s/$_[0]/$_[1]/g; | |
my $cmd = "rm /var/lock/qemu-server/lock-$vmid.conf && qm set $vmid --$hostpci $newconf"; | |
print("Changing configuration with command: $cmd\n"); | |
my $ret = 0; | |
$ret = system($cmd) if ($dryRun == 0); | |
if ($ret != 0) { | |
print("Error editing configuration.\n"); | |
exit($ret); | |
} | |
} | |
if ($phase eq 'pre-start') { | |
my $start_vm = 1; | |
print("VM $vmid pre-start check.\n"); | |
# get PCI devices needed by the desired VM | |
my @vm_pci = get_vm_pci($vmid); | |
# only execute if that VM actually needs PCI devices | |
if (scalar(@vm_pci) > 0) { | |
print("VM $vmid requires PCI devices: ", join(" ",@vm_pci), "\n"); | |
# get running VMs list | |
my @vm_run = get_vm_run(); | |
if (scalar(@vm_run) > 0) { | |
my @vm_to_stop = (); | |
print("Running VMs: ", join(" ",@vm_run), "\n"); | |
# iterate over each required PCI device | |
for my $vm_pci_i (@vm_pci) { | |
print("Searching for VMs using PCI device $vm_pci_i.\n"); | |
for my $vm_run_i (@vm_run) { | |
my $pci_found = 0; | |
# get running VM PCI devices and name | |
my @vm_run_pci = get_vm_pci($vm_run_i); | |
if (scalar(@vm_run_pci) > 0) { | |
my $vm_run_name = get_vm_name($vm_run_i); | |
print("VM $vm_run_i \"$vm_run_name\" is using PCI devices: ", join(" ",@vm_run_pci), "\n"); | |
for my $vm_run_pci_i (@vm_run_pci) { | |
if ($vm_run_pci_i eq $vm_pci_i) { | |
if (index($vm_run_name, $placeholder) != -1) { | |
print("Placeholder VM $vm_run_i will be stopped.\n"); | |
push(@vm_to_stop, $vm_run_i); | |
$pci_found = 1; | |
last; | |
} | |
else { | |
print("PCI device $vm_run_pci_i required by VM $vm_run_i \"$vm_run_name\", searching for alternatives.\n"); | |
my $pci_id = get_pci_id($vm_pci_i); | |
my @pci_alt = get_pci_from_id($pci_id); | |
my $alt_found = 0; | |
if (scalar(@pci_alt) > 1) { | |
for my $pci_alt_i (@pci_alt) { | |
my $same_as_req = 0; | |
for my $vm_pci_alt_i (@vm_pci) { | |
$same_as_req = ($vm_pci_alt_i eq $pci_alt_i); | |
last if ($same_as_req); | |
} | |
if (not $same_as_req) { | |
print("Found alternative PCI device $pci_alt_i, searching for VMs using it.\n"); | |
my $alt_stop = 0; | |
for my $vm_alt_i (@vm_run) { | |
if ($vm_alt_i ne $vm_run_i) { | |
my @vm_alt_pci = get_vm_pci($vm_alt_i); | |
my $vm_alt_name = get_vm_name($vm_alt_i); | |
print("VM $vm_alt_i \"$vm_alt_name\" is using PCI devices: ", join(" ",@vm_alt_pci), "\n"); | |
for my $vm_alt_pci_i (@vm_alt_pci) { | |
if ($vm_alt_pci_i eq $pci_alt_i) { | |
if (index($vm_alt_name, $placeholder) != -1) { | |
print("Alternative available, placeholder VM $vm_alt_i will be stopped.\n"); | |
replace_vm_pci($vm_pci_i, $pci_alt_i); | |
push(@vm_to_stop, $vm_alt_i); | |
$alt_found = 1; | |
$pci_found = 1; | |
$start_vm = 0; | |
last; | |
} | |
else { | |
print("Alternative PCI device $pci_alt_i not available, in use by VM $vm_alt_i.\n"); | |
$alt_stop = 1; | |
last; | |
} | |
} | |
} | |
} | |
last if ($alt_stop == 1 || $alt_found == 1); | |
} | |
if ($alt_found == 0 && $alt_stop == 0) { | |
print("Alternative available, PCI device $pci_alt_i not in use.\n"); | |
replace_vm_pci($vm_pci_i, $pci_alt_i); | |
$alt_found = 1; | |
$pci_found = 1; | |
$start_vm = 0; | |
} | |
} | |
last if ($alt_found == 1); | |
} | |
} | |
if ($alt_found == 0) { | |
print("No alternatives found for PCI device $vm_run_pci_i, aborting.\n"); | |
exit(1); | |
} | |
} | |
} | |
else { | |
print("PCI device $vm_run_pci_i skipped.\n"); | |
} | |
} | |
} | |
else { | |
print("VM $vm_run_i is not using PCI devices.\n"); | |
} | |
last if ($pci_found == 1); | |
} | |
} | |
if (scalar(@vm_to_stop) > 0) { | |
for my $vm_to_stop_i (@vm_to_stop) { | |
my $cmd = "qm stop $vm_to_stop_i --skiplock"; | |
print("Stopping placeholder VM $vm_to_stop_i with command: $cmd\n"); | |
my $ret = 0; | |
$ret = system($cmd) if ($dryRun == 0); | |
if ($ret != 0) { | |
print("Error $ret stopping placeholder VM $vm_to_stop_i, aborting.\n"); | |
exit($ret); | |
} | |
} | |
} | |
else { | |
print("No VMs to be stopped.\n"); | |
} | |
} | |
else { | |
print("No running VMs, starting normally.\n"); | |
} | |
} | |
else { | |
print("VM $vmid does not require PCI devices.\n") | |
} | |
if ($start_vm == 1) { | |
print("Starting VM $vmid.\n"); | |
} | |
else { | |
print("Configuration changed, cannot start immediately, please start again.\n"); | |
exit(1); | |
} | |
} | |
elsif ($phase eq 'post-start') { | |
print "VM $vmid started successfully.\n"; | |
} | |
elsif ($phase eq 'pre-stop') { | |
print "VM $vmid will be stopped.\n"; | |
} | |
elsif ($phase eq 'post-stop') { | |
print("VM $vmid post-stop check.\n"); | |
my $vm_name = get_vm_name($vmid); | |
if (index($vm_name, $placeholder) != -1) { | |
print("Placeholder VM $vmid stopped successfully.\n"); | |
} | |
else { | |
my @vm_pci = get_vm_pci($vmid); | |
if (scalar(@vm_pci) > 0) { | |
print("VM $vmid was using PCI devices: ", join(" ",@vm_pci), "\n"); | |
my @holder = get_vm_hold(); | |
my @vm_to_start = (); | |
print("Available placeholders: ", join(" ",@holder), "\n"); | |
for my $vm_pci_i (@vm_pci) { | |
print("Searching for PCI device $vm_pci_i placeholder.\n"); | |
for my $holder_i (@holder) { | |
my @holder_pci = get_vm_pci($holder_i); | |
if ($holder_pci[0] eq $vm_pci_i) { | |
print("Placeholder VM $holder_i will be started.\n"); | |
push(@vm_to_start, $holder_i); | |
last; | |
} | |
} | |
} | |
if (scalar(@vm_to_start) > 0) { | |
for my $vm_to_start_i (@vm_to_start) { | |
my $cmd = "qm start $vm_to_start_i --skiplock"; | |
print("Starting placeholder VM $vm_to_start_i with command: $cmd\n"); | |
my $ret = 0; | |
$ret = system($cmd) if ($dryRun == 0); | |
if ($ret != 0) { | |
print("Error $ret starting placeholder VM $vm_to_start_i.\n"); | |
exit($ret); | |
} | |
else { | |
print("Placeholder VM $vm_to_start_i started successfully.\n"); | |
} | |
} | |
} | |
if (scalar(@vm_to_start) < scalar(@vm_pci)) { | |
print("Error: number of placeholders found does not match number of PCI devices.\n"); | |
exit(1); | |
} | |
} | |
else { | |
print("VM $vmid was not using PCI devices.\n"); | |
} | |
} | |
} else { | |
die "Got unknown phase '$phase'\n"; | |
} | |
exit(0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment