]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
api: clone vm: comment and style clean-up deactivation error-handling
[qemu-server.git] / PVE / API2 / Qemu.pm
index 593071331b82880fbb1bace2c2c37dae6c395ad9..40b6c3020982302b27d86f8d1ca462cad6315404 100644 (file)
@@ -32,6 +32,7 @@ use PVE::QemuServer::Drive;
 use PVE::QemuServer::ImportDisk;
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::Machine;
+use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::PCI;
 use PVE::QemuServer::USB;
 use PVE::QemuMigrate;
@@ -47,6 +48,7 @@ use PVE::DataCenterConfig;
 use PVE::SSHInfo;
 use PVE::Replication;
 use PVE::StorageTunnel;
+use PVE::RESTEnvironment qw(log_warn);
 
 BEGIN {
     if (!$ENV{PVE_GENERATING_DOCS}) {
@@ -85,8 +87,6 @@ my $foreach_volume_with_alloc = sub {
     }
 };
 
-my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
-
 my $check_drive_param = sub {
     my ($param, $storecfg, $extra_checks) = @_;
 
@@ -97,7 +97,7 @@ my $check_drive_param = sub {
        raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
 
        if ($drive->{'import-from'}) {
-           if ($drive->{file} !~ $NEW_DISK_RE || $3 != 0) {
+           if ($drive->{file} !~ $PVE::QemuServer::Drive::NEW_DISK_RE || $3 != 0) {
                raise_param_exc({
                    $opt => "'import-from' requires special syntax - ".
                        "use <storage ID>:0,import-from=<source>",
@@ -141,7 +141,7 @@ my $check_storage_access = sub {
            # nothing to check
        } elsif ($isCDROM && ($volid eq 'cdrom')) {
            $rpcenv->check($authuser, "/", ['Sys.Console']);
-       } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
+       } elsif (!$isCDROM && ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE)) {
            my ($storeid, $size) = ($2 || $default_storage, $3);
            die "no storage ID specified (and no default storage)\n" if !$storeid;
            $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
@@ -364,7 +364,7 @@ my $create_disks = sub {
            delete $disk->{format}; # no longer needed
            $res->{$ds} = PVE::QemuServer::print_drive($disk);
            print "$ds: successfully created disk '$res->{$ds}'\n";
-       } elsif ($volid =~ $NEW_DISK_RE) {
+       } elsif ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE) {
            my ($storeid, $size) = ($2 || $default_storage, $3);
            die "no storage ID specified (and no default storage)\n" if !$storeid;
 
@@ -562,6 +562,7 @@ my $cloudinitoptions = {
     cipassword => 1,
     citype => 1,
     ciuser => 1,
+    ciupgrade => 1,
     nameserver => 1,
     searchdomain => 1,
     sshkeys => 1,
@@ -696,6 +697,33 @@ my $check_vm_modify_config_perm = sub {
     return 1;
 };
 
+sub assert_scsi_feature_compatibility {
+    my ($opt, $conf, $storecfg, $drive_attributes) = @_;
+
+    my $drive = PVE::QemuServer::Drive::parse_drive($opt, $drive_attributes, 1);
+
+    my $machine_type = PVE::QemuServer::get_vm_machine($conf, undef, $conf->{arch});
+    my $machine_version = PVE::QemuServer::Machine::extract_version(
+       $machine_type, PVE::QemuServer::kvm_user_version());
+    my $drivetype = PVE::QemuServer::Drive::get_scsi_devicetype(
+       $drive, $storecfg, $machine_version);
+
+    if ($drivetype ne 'hd' && $drivetype ne 'cd') {
+       if ($drive->{product}) {
+           raise_param_exc({
+               $opt => "Passing of product information is only supported for 'scsi-hd' and "
+                   ."'scsi-cd' devices (e.g. not pass-through).",
+           });
+       }
+       if ($drive->{vendor}) {
+           raise_param_exc({
+               $opt => "Passing of vendor information is only supported for 'scsi-hd' and "
+                   ."'scsi-cd' devices (e.g. not pass-through).",
+           });
+       }
+    }
+}
+
 __PACKAGE__->register_method({
     name => 'vmlist',
     path => '',
@@ -989,6 +1017,8 @@ __PACKAGE__->register_method({
                    eval { PVE::QemuServer::template_create($vmid, $restored_conf) };
                    warn $@ if $@;
                }
+
+               PVE::QemuServer::create_ifaces_ipams_ips($restored_conf, $vmid) if $unique;
            };
 
            # ensure no old replication state are exists
@@ -1011,6 +1041,12 @@ __PACKAGE__->register_method({
                my $conf = $param;
                my $arch = PVE::QemuServer::get_vm_arch($conf);
 
+
+               for my $opt (sort keys $param->%*) {
+                   next if $opt !~ m/^scsi\d+$/;
+                   assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt});
+               }
+
                $conf->{meta} = PVE::QemuServer::new_meta_info_string();
 
                my $vollist = [];
@@ -1033,6 +1069,9 @@ __PACKAGE__->register_method({
                        $conf->{boot} = PVE::QemuServer::print_bootorder($devs);
                    }
 
+                   my $vga = PVE::QemuServer::parse_vga($conf->{vga});
+                   PVE::QemuServer::assert_clipboard_config($vga);
+
                    # auto generate uuid if user did not specify smbios1 option
                    if (!$conf->{smbios1}) {
                        $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
@@ -1064,6 +1103,8 @@ __PACKAGE__->register_method({
                }
 
                PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
+
+               PVE::QemuServer::create_ifaces_ipams_ips($conf, $vmid);
            };
 
            PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
@@ -1481,7 +1522,7 @@ __PACKAGE__->register_method({
     proxyto => 'node',
     description => "Regenerate and change cloudinit config drive.",
     permissions => {
-       check => ['perm', '/vms/{vmid}', 'VM.Config.Cloudinit'],
+       check => ['perm', '/vms/{vmid}', ['VM.Config.Cloudinit']],
     },
     parameters => {
        additionalProperties => 0,
@@ -1572,8 +1613,6 @@ my $update_vm_api  = sub {
 
     my $storecfg = PVE::Storage::config();
 
-    my $defaults = PVE::QemuServer::load_defaults();
-
     &$resolve_cdrom_alias($param);
 
     # now try to verify all parameters
@@ -1626,7 +1665,7 @@ my $update_vm_api  = sub {
        return if defined($volname) && $volname eq 'cloudinit';
 
        my $format;
-       if ($volid =~ $NEW_DISK_RE) {
+       if ($volid =~ $PVE::QemuServer::Drive::NEW_DISK_RE) {
            $storeid = $2;
            $format = $drive->{format} || PVE::Storage::storage_default_format($storecfg, $storeid);
        } else {
@@ -1693,7 +1732,9 @@ my $update_vm_api  = sub {
        }
 
        if ($param->{memory} || defined($param->{balloon})) {
-           my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
+
+           my $memory = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory};
+           my $maxmem = get_current_memory($memory);
            my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
 
            die "balloon value too large (must be smaller than assigned memory)\n"
@@ -1792,6 +1833,16 @@ my $update_vm_api  = sub {
                    assert_tag_permissions($vmid, $val, '', $rpcenv, $authuser);
                    delete $conf->{$opt};
                    PVE::QemuConfig->write_config($vmid, $conf);
+               } elsif ($opt =~ m/^net\d+$/) {
+                   if ($conf->{$opt}) {
+                       PVE::QemuServer::check_bridge_access(
+                           $rpcenv,
+                           $authuser,
+                           { $opt => $conf->{$opt} },
+                       );
+                   }
+                   PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
+                   PVE::QemuConfig->write_config($vmid, $conf);
                } else {
                    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
                    PVE::QemuConfig->write_config($vmid, $conf);
@@ -1816,6 +1867,9 @@ my $update_vm_api  = sub {
                    PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
                        if defined($conf->{pending}->{$opt});
 
+                   assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt})
+                       if $opt =~ m/^scsi\d+$/;
+
                    my (undef, $created_opts) = $create_disks->(
                        $rpcenv,
                        $authuser,
@@ -1845,6 +1899,10 @@ my $update_vm_api  = sub {
                        die "only root can modify '$opt' config for real devices\n";
                    }
                    $conf->{pending}->{$opt} = $param->{$opt};
+               } elsif ($opt eq 'vga') {
+                   my $vga = PVE::QemuServer::parse_vga($param->{$opt});
+                   PVE::QemuServer::assert_clipboard_config($vga);
+                   $conf->{pending}->{$opt} = $param->{$opt};
                } elsif ($opt =~ m/^usb\d+/) {
                    if (my $olddevice = $conf->{$opt}) {
                        check_usb_perm($rpcenv, $authuser, $vmid, undef, $opt, $conf->{$opt});
@@ -1860,6 +1918,15 @@ my $update_vm_api  = sub {
                } elsif ($opt eq 'tags') {
                    assert_tag_permissions($vmid, $conf->{$opt}, $param->{$opt}, $rpcenv, $authuser);
                    $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt});
+               } elsif ($opt =~ m/^net\d+$/) {
+                   if ($conf->{$opt}) {
+                       PVE::QemuServer::check_bridge_access(
+                           $rpcenv,
+                           $authuser,
+                           { $opt => $conf->{$opt} },
+                       );
+                   }
+                   $conf->{pending}->{$opt} = $param->{$opt};
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
 
@@ -2246,7 +2313,8 @@ __PACKAGE__->register_method({
            websocket => {
                optional => 1,
                type => 'boolean',
-               description => "starts websockify instead of vncproxy",
+               description => "Prepare for websocket upgrade (only required when using "
+                   ."serial terminal, otherwise upgrade is always possible).",
            },
            'generate-password' => {
                optional => 1,
@@ -2344,7 +2412,7 @@ __PACKAGE__->register_method({
 
            } else {
 
-               $ENV{LC_PVE_TICKET} = $password if $websocket; # set ticket with "qm vncproxy"
+               $ENV{LC_PVE_TICKET} = $password; # set ticket with "qm vncproxy"
 
                $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
 
@@ -2670,6 +2738,13 @@ __PACKAGE__->register_method({
                type => 'boolean',
                optional => 1,
            },
+           clipboard => {
+               description => 'Enable a specific clipboard. If not set, depending on'
+                   .' the display type the SPICE one will be added.',
+               type => 'string',
+               enum => ['vnc'],
+               optional => 1,
+           },
        },
     },
     code => sub {
@@ -2688,6 +2763,7 @@ __PACKAGE__->register_method({
            my $spice = defined($vga->{type}) && $vga->{type} =~ /^virtio/;
            $spice ||= PVE::QemuServer::vga_conf_has_spice($conf->{vga});
            $status->{spice} = 1 if $spice;
+           $status->{clipboard} = $vga->{clipboard};
        }
        $status->{agent} = 1 if PVE::QemuServer::get_qga_key($conf, 'enabled');
 
@@ -3060,13 +3136,9 @@ __PACKAGE__->register_method({
 
        my $shutdown = 1;
 
-       # if vm is paused, do not shutdown (but stop if forceStop = 1)
-       # otherwise, we will infer a shutdown command, but run into the timeout,
-       # then when the vm is resumed, it will instantly shutdown
-       #
-       # checking the qmp status here to get feedback to the gui/cli/api
-       # and the status query should not take too long
-       if (PVE::QemuServer::vm_is_paused($vmid)) {
+       # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when
+       # the VM gets resumed later, it still gets the request delivered and powers off
+       if (PVE::QemuServer::vm_is_paused($vmid, 1)) {
            if ($param->{forceStop}) {
                warn "VM is paused - stop instead of shutdown\n";
                $shutdown = 0;
@@ -3142,7 +3214,7 @@ __PACKAGE__->register_method({
        my $node = extract_param($param, 'node');
        my $vmid = extract_param($param, 'vmid');
 
-       die "VM is paused - cannot shutdown\n" if PVE::QemuServer::vm_is_paused($vmid);
+       die "VM is paused - cannot shutdown\n" if PVE::QemuServer::vm_is_paused($vmid, 1);
 
        die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
 
@@ -3745,9 +3817,16 @@ __PACKAGE__->register_method({
 
                PVE::QemuConfig->write_config($newid, $newconf);
 
+               PVE::QemuServer::create_ifaces_ipams_ips($newconf, $newid);
+
                if ($target) {
-                   # always deactivate volumes - avoid lvm LVs to be active on several nodes
-                   PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
+                   if (!$running) {
+                       # always deactivate volumes – avoids that LVM LVs are active on several nodes
+                       eval { PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) };
+                       # but only warn when that fails (e.g., parallel clones keeping them active)
+                       log_warn($@) if $@;
+                   }
+
                    PVE::Storage::deactivate_volumes($storecfg, $newvollist);
 
                    my $newconffile = PVE::QemuConfig->config_file($newid, $target);
@@ -4337,6 +4416,11 @@ __PACKAGE__->register_method({
            PVE::QemuServer::check_local_resources($vmconf, 1);
        delete $missing_mappings_by_node->{$localnode};
 
+       my $vga = PVE::QemuServer::parse_vga($vmconf->{vga});
+       if ($res->{running} && $vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {
+           push $local_resources->@*, "clipboard=vnc";
+       }
+
        # if vm is not running, return target nodes where local storage/mapped devices are available
        # for offline migration
        if (!$res->{running}) {
@@ -5634,7 +5718,6 @@ __PACKAGE__->register_method({
                        'disk' => [
                            undef,
                            $storeid,
-                           undef,
                            $drive,
                            0,
                            $format,