]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/CLI/qm.pm
destroy_vm: allow to pass new config and lock instead
[qemu-server.git] / PVE / CLI / qm.pm
index 46a7e2fd5beb9f54ba7849512e7fc3a95c4dde8b..1361581534866f5c4cb606945dda46b078169d83 100755 (executable)
@@ -8,28 +8,31 @@ use Getopt::Long qw(:config no_getopt_compat);
 
 use Fcntl ':flock';
 use File::Path;
-use IO::Socket::UNIX;
 use IO::Select;
+use IO::Socket::UNIX;
+use JSON;
+use POSIX qw(strftime);
+use Term::ReadLine;
 use URI::Escape;
 
-use PVE::Tools qw(extract_param);
 use PVE::Cluster;
-use PVE::SafeSyslog;
+use PVE::Exception qw(raise_param_exc);
+use PVE::GuestHelpers;
 use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Network;
 use PVE::RPCEnvironment;
-use PVE::Exception qw(raise_param_exc);
-use PVE::QemuServer;
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+
+use PVE::API2::Qemu::Agent;
+use PVE::API2::Qemu;
+use PVE::QemuServer::Agent qw(agent_available);
 use PVE::QemuServer::ImportDisk;
 use PVE::QemuServer::OVF;
-use PVE::QemuServer::Agent qw(agent_available);
-use PVE::API2::Qemu;
-use PVE::API2::Qemu::Agent;
-use JSON;
-use PVE::JSONSchema qw(get_standard_option);
-use Term::ReadLine;
+use PVE::QemuServer;
 
 use PVE::CLIHandler;
-
 use base qw(PVE::CLIHandler);
 
 my $upid_exit = sub {
@@ -126,7 +129,15 @@ __PACKAGE__->register_method ({
                type => 'boolean',
                optional => 1,
                default => 0,
-           }
+           },
+           snapshot => get_standard_option('pve-snapshot-name', {
+               description => "Fetch config values from given snapshot.",
+               optional => 1,
+               completion => sub {
+                   my ($cmd, $pname, $cur, $args) = @_;
+                   PVE::QemuConfig->snapshot_list($args->[0]);
+               }
+           }),
        },
     },
     returns => { type => 'null'},
@@ -134,7 +145,7 @@ __PACKAGE__->register_method ({
        my ($param) = @_;
 
        my $storecfg = PVE::Storage::config();
-       my $cmdline = PVE::QemuServer::vm_commandline($storecfg, $param->{vmid});
+       my $cmdline = PVE::QemuServer::vm_commandline($storecfg, $param->{vmid}, $param->{snapshot});
 
        $cmdline =~ s/ -/ \\\n  -/g if $param->{pretty};
 
@@ -205,7 +216,8 @@ __PACKAGE__->register_method ({
            PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'vnc', password => $ticket);
            PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'vnc', time => "+30");
        } else {
-           PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,x509,password");
+           # FIXME: remove or allow to add tls-creds object, as x509 vnc param is removed with qemu 4??
+           PVE::QemuServer::vm_mon_cmd($vmid, "change", device => 'vnc', target => "unix:$vnc_socket,password");
        }
 
        run_vnc_proxy($vnc_socket);
@@ -471,12 +483,13 @@ __PACKAGE__->register_method ({
        my $storecfg = PVE::Storage::config();
        PVE::Storage::storage_check_enabled($storecfg, $storeid);
 
-       my $target_storage_config =
-           PVE::Storage::storage_config($storecfg, $storeid);
+       my $target_storage_config =  PVE::Storage::storage_config($storecfg, $storeid);
        die "storage $storeid does not support vm images\n"
            if !$target_storage_config->{content}->{images};
 
-       PVE::QemuServer::ImportDisk::do_import($source, $vmid, $storeid, { format => $format });
+       print "importing disk '$source' to VM $vmid ...\n";
+       my ($drive_id, $volid) = PVE::QemuServer::ImportDisk::do_import($source, $vmid, $storeid, { format => $format });
+       print "Successfully imported disk as '$drive_id:$volid'\n";
 
        return undef;
     }});
@@ -609,46 +622,49 @@ __PACKAGE__->register_method ({
            return;
        }
 
-       $param->{name} = $parsed->{qm}->{name} if defined($parsed->{qm}->{name});
-       $param->{memory} = $parsed->{qm}->{memory} if defined($parsed->{qm}->{memory});
-       $param->{cores} = $parsed->{qm}->{cores} if defined($parsed->{qm}->{cores});
-
-       my $importfn = sub {
-
-           PVE::Cluster::check_vmid_unused($vmid);
+       eval { PVE::QemuConfig->create_and_lock_config($vmid) };
+       die "Reserving empty config for OVF import to VM $vmid failed: $@" if $@;
 
-           my $conf = $param;
+       my $conf = PVE::QemuConfig->load_config($vmid);
+       die "Internal error: Expected 'create' lock in config of VM $vmid!"
+           if !PVE::QemuConfig->has_lock($conf, "create");
 
-           eval {
-               # order matters, as do_import() will load_config() internally
-               $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
-               $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
-               PVE::QemuConfig->write_config($vmid, $conf);
-
-               foreach my $disk (@{ $parsed->{disks} }) {
-                   my ($file, $drive) = ($disk->{backing_file}, $disk->{disk_address});
-                   PVE::QemuServer::ImportDisk::do_import($file, $vmid, $storeid,
-                       { drive_name => $drive, format => $format });
-               }
+       $conf->{name} = $parsed->{qm}->{name} if defined($parsed->{qm}->{name});
+       $conf->{memory} = $parsed->{qm}->{memory} if defined($parsed->{qm}->{memory});
+       $conf->{cores} = $parsed->{qm}->{cores} if defined($parsed->{qm}->{cores});
 
-               # reload after disks entries have been created
-               $conf = PVE::QemuConfig->load_config($vmid);
-               PVE::QemuConfig->check_lock($conf);
-               my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
-               $conf->{bootdisk} = $firstdisk if $firstdisk;
-               PVE::QemuConfig->write_config($vmid, $conf);
-           };
+       eval {
+           # order matters, as do_import() will load_config() internally
+           $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
+           $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
+           PVE::QemuConfig->write_config($vmid, $conf);
 
-           my $err = $@;
-           if ($err) {
-               my $skiplock = 1;
-               eval { PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock); };
-               die "import failed - $err";
+           foreach my $disk (@{ $parsed->{disks} }) {
+               my ($file, $drive) = ($disk->{backing_file}, $disk->{disk_address});
+               PVE::QemuServer::ImportDisk::do_import($file, $vmid, $storeid, {
+                   drive_name => $drive,
+                   format => $format,
+                   skiplock => 1,
+               });
            }
+
+           # reload after disks entries have been created
+           $conf = PVE::QemuConfig->load_config($vmid);
+           my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
+           $conf->{bootdisk} = $firstdisk if $firstdisk;
+           PVE::QemuConfig->write_config($vmid, $conf);
        };
 
-       my $wait_for_lock = 1;
-       PVE::QemuConfig->lock_config_full($vmid, $wait_for_lock, $importfn);
+       my $err = $@;
+       if ($err) {
+           my $skiplock = 1;
+           # eval for additional safety in error path
+           eval { PVE::QemuServer::destroy_vm($storecfg, $vmid, $skiplock) };
+           warn "Could not destroy VM $vmid: $@" if "$@";
+           die "import failed - $err";
+       }
+
+       PVE::QemuConfig->remove_lock($vmid, "create");
 
        return undef;
 
@@ -722,6 +738,81 @@ __PACKAGE__->register_method({
        return { result => $res };
     }});
 
+__PACKAGE__->register_method({
+    name => 'cleanup',
+    path => 'cleanup',
+    method => 'POST',
+    protected => 1,
+    description => "Cleans up resources like tap devices, vgpus, etc. Called after a vm shuts down, crashes, etc.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           vmid => get_standard_option('pve-vmid', {
+                   completion => \&PVE::QemuServer::complete_vmid_running }),
+           'clean-shutdown' => {
+               type => 'boolean',
+               description => "Indicates if qemu shutdown cleanly.",
+           },
+           'guest-requested' => {
+               type => 'boolean',
+               description => "Indicates if the shutdown was requested by the guest or via qmp.",
+           },
+       },
+    },
+    returns => { type => 'null', },
+    code => sub {
+       my ($param) = @_;
+
+       my $vmid = $param->{vmid};
+       my $clean = $param->{'clean-shutdown'};
+       my $guest = $param->{'guest-requested'};
+       my $restart = 0;
+
+       # return if we do not have the config anymore
+       return if !-f PVE::QemuConfig->config_file($vmid);
+
+       my $storecfg = PVE::Storage::config();
+       warn "Starting cleanup for $vmid\n";
+
+       PVE::QemuConfig->lock_config($vmid, sub {
+           my $conf = PVE::QemuConfig->load_config ($vmid);
+           my $pid = PVE::QemuServer::check_running ($vmid);
+           die "vm still running\n" if $pid;
+
+           if (!$clean) {
+               # we have to cleanup the tap devices after a crash
+
+               foreach my $opt (keys %$conf) {
+                   next if $opt !~  m/^net(\d)+$/;
+                   my $interface = $1;
+                   PVE::Network::tap_unplug("tap${vmid}i${interface}");
+               }
+           }
+
+           if (!$clean || $guest) {
+               # vm was shutdown from inside the guest or crashed, doing api cleanup
+               PVE::QemuServer::vm_stop_cleanup($storecfg, $vmid, $conf, 0, 0);
+           }
+           PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-stop');
+
+           $restart = eval { PVE::QemuServer::clear_reboot_request($vmid) };
+           warn $@ if $@;
+       });
+
+       warn "Finished cleanup for $vmid\n";
+
+       if ($restart) {
+           warn "Restarting VM $vmid\n";
+           PVE::API2::Qemu->vm_start({
+               vmid => $vmid,
+               node => $nodename,
+           });
+       }
+
+       return undef;
+    }});
+
 my $print_agent_result = sub {
     my ($data) = @_;
 
@@ -811,33 +902,7 @@ our $cmddef = {
                    }
                }],
 
-    pending => [ "PVE::API2::Qemu", 'vm_pending', ['vmid'],
-               { node => $nodename }, sub {
-                   my $data = shift;
-                   foreach my $item (sort { $a->{key} cmp $b->{key}} @$data) {
-                       my $k = $item->{key};
-                       next if $k eq 'digest';
-                       my $v = $item->{value};
-                       my $p = $item->{pending};
-                       if ($k eq 'description') {
-                           $v = PVE::Tools::encode_text($v) if defined($v);
-                           $p = PVE::Tools::encode_text($p) if defined($p);
-                       }
-                       if (defined($v)) {
-                           if ($item->{delete}) {
-                               print "del $k: $v\n";
-                           } elsif (defined($p)) {
-                               print "cur $k: $v\n";
-                               print "new $k: $p\n";
-                           } else {
-                               print "cur $k: $v\n";
-                           }
-                       } elsif (defined($p)) {
-                           print "new $k: $p\n";
-                       }
-                   }
-               }],
-
+    pending => [ "PVE::API2::Qemu", 'vm_pending', ['vmid'], { node => $nodename }, \&PVE::GuestHelpers::format_pending ],
     showcmd => [ __PACKAGE__, 'showcmd', ['vmid']],
 
     status => [ __PACKAGE__, 'status', ['vmid']],
@@ -846,16 +911,7 @@ our $cmddef = {
 
     delsnapshot => [ "PVE::API2::Qemu", 'delsnapshot', ['vmid', 'snapname'], { node => $nodename } , $upid_exit ],
 
-    listsnapshot => [ "PVE::API2::Qemu", 'snapshot_list', ['vmid'], { node => $nodename },
-                   sub {
-                       my $res = shift;
-                       foreach my $e (@$res) {
-                           my $headline = $e->{description} || 'no-description';
-                           $headline =~ s/\n.*//sg;
-                           my $parent = $e->{parent} // 'no-parent';
-                           printf("%-20s %-20s %s\n", $e->{name}, $parent, $headline);
-                       }
-                   }],
+    listsnapshot => [ "PVE::API2::Qemu", 'snapshot_list', ['vmid'], { node => $nodename }, \&PVE::GuestHelpers::print_snapshot_tree],
 
     rollback => [ "PVE::API2::Qemu", 'rollback', ['vmid', 'snapname'], { node => $nodename } , $upid_exit ],
 
@@ -869,6 +925,8 @@ our $cmddef = {
 
     shutdown => [ "PVE::API2::Qemu", 'vm_shutdown', ['vmid'], { node => $nodename }, $upid_exit ],
 
+    reboot => [ "PVE::API2::Qemu", 'vm_reboot', ['vmid'], { node => $nodename }, $upid_exit ],
+
     suspend => [ "PVE::API2::Qemu", 'vm_suspend', ['vmid'], { node => $nodename }, $upid_exit ],
 
     resume => [ "PVE::API2::Qemu", 'vm_resume', ['vmid'], { node => $nodename }, $upid_exit ],
@@ -904,6 +962,15 @@ our $cmddef = {
 
     importovf => [ __PACKAGE__, 'importovf', ['vmid', 'manifest', 'storage']],
 
+    cleanup => [ __PACKAGE__, 'cleanup', ['vmid', 'clean-shutdown', 'guest-requested'], { node => $nodename }],
+
+    cloudinit => {
+       dump => [ "PVE::API2::Qemu", 'cloudinit_generated_config_dump', ['vmid', 'type'], { node => $nodename }, sub {
+               my $data = shift;
+               print "$data\n";
+           }],
+    },
+
 };
 
 1;