[pve-devel] [PATCH qemu-server 16/31] introduce RunState module
Fiona Ebner
f.ebner at proxmox.com
Wed Jun 25 17:56:39 CEST 2025
For now, move only the vm_resume() and vm_suspend() functions. Others
like vm_stop() and friends, vm_reboot() and vm_start() would require
more preparation.
Apart from slightly improving modularity, this is in preparation to
add a BlockJob module, where vm_resume() and vm_suspend() need to be
called after drive-mirror, to avoid a cyclic dependency with the main
QemuServer module.
Signed-off-by: Fiona Ebner <f.ebner at proxmox.com>
---
src/PVE/API2/Qemu.pm | 7 +-
src/PVE/CLI/qm.pm | 3 +-
src/PVE/QemuServer.pm | 175 +------------------------------
src/PVE/QemuServer/Makefile | 1 +
src/PVE/QemuServer/RunState.pm | 185 +++++++++++++++++++++++++++++++++
5 files changed, 196 insertions(+), 175 deletions(-)
create mode 100644 src/PVE/QemuServer/RunState.pm
diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 27426eab..de762cca 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -42,6 +42,7 @@ use PVE::QemuServer::OVMF;
use PVE::QemuServer::PCI;
use PVE::QemuServer::QMPHelpers;
use PVE::QemuServer::RNG;
+use PVE::QemuServer::RunState;
use PVE::QemuServer::USB;
use PVE::QemuServer::Virtiofs qw(max_virtiofs);
use PVE::QemuMigrate;
@@ -3934,7 +3935,7 @@ __PACKAGE__->register_method({
syslog('info', "suspend VM $vmid: $upid\n");
- PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
+ PVE::QemuServer::RunState::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
return;
};
@@ -4011,7 +4012,7 @@ __PACKAGE__->register_method({
syslog('info', "resume VM $vmid: $upid\n");
if (!$to_disk_suspended) {
- PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
+ PVE::QemuServer::RunState::vm_resume($vmid, $skiplock, $nocheck);
} else {
my $storecfg = PVE::Storage::config();
PVE::QemuServer::vm_start($storecfg, $vmid, { skiplock => $skiplock });
@@ -6642,7 +6643,7 @@ __PACKAGE__->register_method({
},
'resume' => sub {
if (PVE::QemuServer::Helpers::vm_running_locally($state->{vmid})) {
- PVE::QemuServer::vm_resume($state->{vmid}, 1, 1);
+ PVE::QemuServer::RunState::vm_resume($state->{vmid}, 1, 1);
} else {
die "VM $state->{vmid} not running\n";
}
diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index 23f71ab0..f3e9a702 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -36,6 +36,7 @@ use PVE::QemuServer::Agent;
use PVE::QemuServer::ImportDisk;
use PVE::QemuServer::Monitor qw(mon_cmd);
use PVE::QemuServer::QMPHelpers;
+use PVE::QemuServer::RunState;
use PVE::QemuServer;
use PVE::CLIHandler;
@@ -465,7 +466,7 @@ __PACKAGE__->register_method({
# check_running and vm_resume with nocheck, since local node
# might not have processed config move/rename yet
if (PVE::QemuServer::check_running($vmid, 1)) {
- eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
+ eval { PVE::QemuServer::RunState::vm_resume($vmid, 1, 1); };
if ($@) {
$tunnel_write->("ERR: resume failed - $@");
} else {
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 3c612b44..942a1363 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -81,6 +81,7 @@ use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port
use PVE::QemuServer::QemuImage;
use PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);
use PVE::QemuServer::RNG qw(parse_rng print_rng_device_commandline print_rng_object_commandline);
+use PVE::QemuServer::RunState;
use PVE::QemuServer::StateFile;
use PVE::QemuServer::USB;
use PVE::QemuServer::Virtiofs qw(max_virtiofs start_all_virtiofsd);
@@ -5359,7 +5360,7 @@ sub vm_start {
if ($has_backup_lock && $running) {
# a backup is currently running, attempt to start the guest in the
# existing QEMU instance
- return vm_resume($vmid);
+ return PVE::QemuServer::RunState::vm_resume($vmid);
}
PVE::QemuConfig->check_lock($conf)
@@ -6165,174 +6166,6 @@ sub vm_reboot {
);
}
-# note: if using the statestorage parameter, the caller has to check privileges
-sub vm_suspend {
- my ($vmid, $skiplock, $includestate, $statestorage) = @_;
-
- my $conf;
- my $path;
- my $storecfg;
- my $vmstate;
-
- PVE::QemuConfig->lock_config(
- $vmid,
- sub {
-
- $conf = PVE::QemuConfig->load_config($vmid);
-
- my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');
- PVE::QemuConfig->check_lock($conf)
- if !($skiplock || $is_backing_up);
-
- die "cannot suspend to disk during backup\n"
- if $is_backing_up && $includestate;
-
- PVE::QemuMigrate::Helpers::check_non_migratable_resources($conf, $includestate, 0);
-
- if ($includestate) {
- $conf->{lock} = 'suspending';
- my $date = strftime("%Y-%m-%d", localtime(time()));
- $storecfg = PVE::Storage::config();
- if (!$statestorage) {
- $statestorage = PVE::QemuConfig::find_vmstate_storage($conf, $storecfg);
- # check permissions for the storage
- my $rpcenv = PVE::RPCEnvironment::get();
- if ($rpcenv->{type} ne 'cli') {
- my $authuser = $rpcenv->get_user();
- $rpcenv->check(
- $authuser,
- "/storage/$statestorage",
- ['Datastore.AllocateSpace'],
- );
- }
- }
-
- $vmstate = PVE::QemuConfig->__snapshot_save_vmstate(
- $vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1,
- );
- $path = PVE::Storage::path($storecfg, $vmstate);
- PVE::QemuConfig->write_config($vmid, $conf);
- } else {
- mon_cmd($vmid, "stop");
- }
- },
- );
-
- if ($includestate) {
- # save vm state
- PVE::Storage::activate_volumes($storecfg, [$vmstate]);
-
- eval {
- PVE::QemuMigrate::Helpers::set_migration_caps($vmid, 1);
- mon_cmd($vmid, "savevm-start", statefile => $path);
- for (;;) {
- my $state = mon_cmd($vmid, "query-savevm");
- if (!$state->{status}) {
- die "savevm not active\n";
- } elsif ($state->{status} eq 'active') {
- sleep(1);
- next;
- } elsif ($state->{status} eq 'completed') {
- print "State saved, quitting\n";
- last;
- } elsif ($state->{status} eq 'failed' && $state->{error}) {
- die "query-savevm failed with error '$state->{error}'\n";
- } else {
- die "query-savevm returned status '$state->{status}'\n";
- }
- }
- };
- my $err = $@;
-
- PVE::QemuConfig->lock_config(
- $vmid,
- sub {
- $conf = PVE::QemuConfig->load_config($vmid);
- if ($err) {
- # cleanup, but leave suspending lock, to indicate something went wrong
- eval {
- eval { mon_cmd($vmid, "savevm-end"); };
- warn $@ if $@;
- PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
- PVE::Storage::vdisk_free($storecfg, $vmstate);
- delete $conf->@{qw(vmstate runningmachine runningcpu)};
- PVE::QemuConfig->write_config($vmid, $conf);
- };
- warn $@ if $@;
- die $err;
- }
-
- die "lock changed unexpectedly\n"
- if !PVE::QemuConfig->has_lock($conf, 'suspending');
-
- mon_cmd($vmid, "quit");
- $conf->{lock} = 'suspended';
- PVE::QemuConfig->write_config($vmid, $conf);
- },
- );
- }
-}
-
-# $nocheck is set when called as part of a migration - in this context the
-# location of the config file (source or target node) is not deterministic,
-# since migration cannot wait for pmxcfs to process the rename
-sub vm_resume {
- my ($vmid, $skiplock, $nocheck) = @_;
-
- PVE::QemuConfig->lock_config(
- $vmid,
- sub {
- # After migration, the VM might not immediately be able to respond to QMP commands, because
- # activating the block devices might take a bit of time.
- my $res = mon_cmd($vmid, 'query-status', timeout => 60);
- my $resume_cmd = 'cont';
- my $reset = 0;
- my $conf;
- if ($nocheck) {
- $conf = eval { PVE::QemuConfig->load_config($vmid) }; # try on target node
- if ($@) {
- my $vmlist = PVE::Cluster::get_vmlist();
- if (exists($vmlist->{ids}->{$vmid})) {
- my $node = $vmlist->{ids}->{$vmid}->{node};
- $conf = eval { PVE::QemuConfig->load_config($vmid, $node) }; # try on source node
- }
- if (!$conf) {
- PVE::Cluster::cfs_update(); # vmlist was wrong, invalidate cache
- $conf = PVE::QemuConfig->load_config($vmid); # last try on target node again
- }
- }
- } else {
- $conf = PVE::QemuConfig->load_config($vmid);
- }
-
- die "VM $vmid is a template and cannot be resumed!\n"
- if PVE::QemuConfig->is_template($conf);
-
- if ($res->{status}) {
- return if $res->{status} eq 'running'; # job done, go home
- $resume_cmd = 'system_wakeup' if $res->{status} eq 'suspended';
- $reset = 1 if $res->{status} eq 'shutdown';
- }
-
- if (!$nocheck) {
- PVE::QemuConfig->check_lock($conf)
- if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
- }
-
- if ($reset) {
- # required if a VM shuts down during a backup and we get a resume
- # request before the backup finishes for example
- mon_cmd($vmid, "system_reset");
- }
-
- PVE::QemuServer::Network::add_nets_bridge_fdb($conf, $vmid)
- if $resume_cmd eq 'cont';
-
- mon_cmd($vmid, $resume_cmd);
- },
- );
-}
-
sub vm_sendkey {
my ($vmid, $skiplock, $key) = @_;
@@ -7967,7 +7800,7 @@ sub qemu_drive_mirror_monitor {
warn $@ if $@;
} else {
print "suspend vm\n";
- eval { PVE::QemuServer::vm_suspend($vmid, 1); };
+ eval { PVE::QemuServer::RunState::vm_suspend($vmid, 1); };
warn $@ if $@;
}
@@ -7980,7 +7813,7 @@ sub qemu_drive_mirror_monitor {
warn $@ if $@;
} else {
print "resume vm\n";
- eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
+ eval { PVE::QemuServer::RunState::vm_resume($vmid, 1, 1); };
warn $@ if $@;
}
diff --git a/src/PVE/QemuServer/Makefile b/src/PVE/QemuServer/Makefile
index e30c571c..5f475c73 100644
--- a/src/PVE/QemuServer/Makefile
+++ b/src/PVE/QemuServer/Makefile
@@ -20,6 +20,7 @@ SOURCES=Agent.pm \
QemuImage.pm \
QMPHelpers.pm \
RNG.pm \
+ RunState.pm \
StateFile.pm \
USB.pm \
Virtiofs.pm
diff --git a/src/PVE/QemuServer/RunState.pm b/src/PVE/QemuServer/RunState.pm
new file mode 100644
index 00000000..05e7fb47
--- /dev/null
+++ b/src/PVE/QemuServer/RunState.pm
@@ -0,0 +1,185 @@
+package PVE::QemuServer::RunState;
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+
+use PVE::Cluster;
+use PVE::RPCEnvironment;
+use PVE::Storage;
+
+use PVE::QemuConfig;
+use PVE::QemuMigrate::Helpers;
+use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Network;
+
+# note: if using the statestorage parameter, the caller has to check privileges
+sub vm_suspend {
+ my ($vmid, $skiplock, $includestate, $statestorage) = @_;
+
+ my $conf;
+ my $path;
+ my $storecfg;
+ my $vmstate;
+
+ PVE::QemuConfig->lock_config(
+ $vmid,
+ sub {
+
+ $conf = PVE::QemuConfig->load_config($vmid);
+
+ my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');
+ PVE::QemuConfig->check_lock($conf)
+ if !($skiplock || $is_backing_up);
+
+ die "cannot suspend to disk during backup\n"
+ if $is_backing_up && $includestate;
+
+ PVE::QemuMigrate::Helpers::check_non_migratable_resources($conf, $includestate, 0);
+
+ if ($includestate) {
+ $conf->{lock} = 'suspending';
+ my $date = strftime("%Y-%m-%d", localtime(time()));
+ $storecfg = PVE::Storage::config();
+ if (!$statestorage) {
+ $statestorage = PVE::QemuConfig::find_vmstate_storage($conf, $storecfg);
+ # check permissions for the storage
+ my $rpcenv = PVE::RPCEnvironment::get();
+ if ($rpcenv->{type} ne 'cli') {
+ my $authuser = $rpcenv->get_user();
+ $rpcenv->check(
+ $authuser,
+ "/storage/$statestorage",
+ ['Datastore.AllocateSpace'],
+ );
+ }
+ }
+
+ $vmstate = PVE::QemuConfig->__snapshot_save_vmstate(
+ $vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1,
+ );
+ $path = PVE::Storage::path($storecfg, $vmstate);
+ PVE::QemuConfig->write_config($vmid, $conf);
+ } else {
+ mon_cmd($vmid, "stop");
+ }
+ },
+ );
+
+ if ($includestate) {
+ # save vm state
+ PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+
+ eval {
+ PVE::QemuMigrate::Helpers::set_migration_caps($vmid, 1);
+ mon_cmd($vmid, "savevm-start", statefile => $path);
+ for (;;) {
+ my $state = mon_cmd($vmid, "query-savevm");
+ if (!$state->{status}) {
+ die "savevm not active\n";
+ } elsif ($state->{status} eq 'active') {
+ sleep(1);
+ next;
+ } elsif ($state->{status} eq 'completed') {
+ print "State saved, quitting\n";
+ last;
+ } elsif ($state->{status} eq 'failed' && $state->{error}) {
+ die "query-savevm failed with error '$state->{error}'\n";
+ } else {
+ die "query-savevm returned status '$state->{status}'\n";
+ }
+ }
+ };
+ my $err = $@;
+
+ PVE::QemuConfig->lock_config(
+ $vmid,
+ sub {
+ $conf = PVE::QemuConfig->load_config($vmid);
+ if ($err) {
+ # cleanup, but leave suspending lock, to indicate something went wrong
+ eval {
+ eval { mon_cmd($vmid, "savevm-end"); };
+ warn $@ if $@;
+ PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+ PVE::Storage::vdisk_free($storecfg, $vmstate);
+ delete $conf->@{qw(vmstate runningmachine runningcpu)};
+ PVE::QemuConfig->write_config($vmid, $conf);
+ };
+ warn $@ if $@;
+ die $err;
+ }
+
+ die "lock changed unexpectedly\n"
+ if !PVE::QemuConfig->has_lock($conf, 'suspending');
+
+ mon_cmd($vmid, "quit");
+ $conf->{lock} = 'suspended';
+ PVE::QemuConfig->write_config($vmid, $conf);
+ },
+ );
+ }
+}
+
+# $nocheck is set when called as part of a migration - in this context the
+# location of the config file (source or target node) is not deterministic,
+# since migration cannot wait for pmxcfs to process the rename
+sub vm_resume {
+ my ($vmid, $skiplock, $nocheck) = @_;
+
+ PVE::QemuConfig->lock_config(
+ $vmid,
+ sub {
+ # After migration, the VM might not immediately be able to respond to QMP commands, because
+ # activating the block devices might take a bit of time.
+ my $res = mon_cmd($vmid, 'query-status', timeout => 60);
+ my $resume_cmd = 'cont';
+ my $reset = 0;
+ my $conf;
+ if ($nocheck) {
+ $conf = eval { PVE::QemuConfig->load_config($vmid) }; # try on target node
+ if ($@) {
+ my $vmlist = PVE::Cluster::get_vmlist();
+ if (exists($vmlist->{ids}->{$vmid})) {
+ my $node = $vmlist->{ids}->{$vmid}->{node};
+ $conf = eval { PVE::QemuConfig->load_config($vmid, $node) }; # try on source node
+ }
+ if (!$conf) {
+ PVE::Cluster::cfs_update(); # vmlist was wrong, invalidate cache
+ $conf = PVE::QemuConfig->load_config($vmid); # last try on target node again
+ }
+ }
+ } else {
+ $conf = PVE::QemuConfig->load_config($vmid);
+ }
+
+ die "VM $vmid is a template and cannot be resumed!\n"
+ if PVE::QemuConfig->is_template($conf);
+
+ if ($res->{status}) {
+ return if $res->{status} eq 'running'; # job done, go home
+ $resume_cmd = 'system_wakeup' if $res->{status} eq 'suspended';
+ $reset = 1 if $res->{status} eq 'shutdown';
+ }
+
+ if (!$nocheck) {
+ PVE::QemuConfig->check_lock($conf)
+ if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
+ }
+
+ if ($reset) {
+ # required if a VM shuts down during a backup and we get a resume
+ # request before the backup finishes for example
+ mon_cmd($vmid, "system_reset");
+ }
+
+ PVE::QemuServer::Network::add_nets_bridge_fdb($conf, $vmid)
+ if $resume_cmd eq 'cont';
+
+ mon_cmd($vmid, $resume_cmd);
+ },
+ );
+}
+
+1;
--
2.47.2
More information about the pve-devel
mailing list