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.eb...@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 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel