]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
prevent starting a 32-bit VM using a 64-bit OVMF BIOS
[qemu-server.git] / PVE / QemuServer.pm
index b52dfbf453bce06ea38b4989fba7f596ec6ecb24..93f66f36386ab67d657ec2f24b64cf6ae00701cb 100644 (file)
@@ -49,20 +49,22 @@ use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_for
 
 use PVE::QMPClient;
 use PVE::QemuConfig;
-use PVE::QemuServer::Helpers qw(min_version config_aware_timeout windows_version);
+use PVE::QemuServer::Helpers qw(config_aware_timeout min_version windows_version);
 use PVE::QemuServer::Cloudinit;
 use PVE::QemuServer::CGroup;
-use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options);
+use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options get_cpu_bitness);
 use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom drive_is_read_only parse_drive print_drive);
 use PVE::QemuServer::Machine;
-use PVE::QemuServer::Memory;
+use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
+use PVE::QemuServer::QMPHelpers qw(qemu_deviceadd qemu_devicedel qemu_objectadd qemu_objectdel);
 use PVE::QemuServer::USB;
 
 my $have_sdn;
 eval {
     require PVE::Network::SDN::Zones;
+    require PVE::Network::SDN::Vnets;
     $have_sdn = 1;
 };
 
@@ -85,6 +87,9 @@ my $OVMF = {
            "$EDK2_FW_BASE/OVMF_CODE_4M.secboot.fd",
            "$EDK2_FW_BASE/OVMF_VARS_4M.ms.fd",
        ],
+       # FIXME: These are legacy 2MB-sized images that modern OVMF doesn't supports to build
+       # anymore. how can we deperacate this sanely without breaking existing instances, or using
+       # older backups and snapshot?
        default => [
            "$EDK2_FW_BASE/OVMF_CODE.fd",
            "$EDK2_FW_BASE/OVMF_VARS.fd",
@@ -195,6 +200,13 @@ my $vga_fmt = {
        minimum => 4,
        maximum => 512,
     },
+    clipboard => {
+       description => 'Enable a specific clipboard. If not set, depending on the display type the'
+           .' SPICE one will be added. Migration with VNC clipboard is not yet supported!',
+       type => 'string',
+       enum => ['vnc'],
+       optional => 1,
+    },
 };
 
 my $ivshmem_fmt = {
@@ -349,11 +361,9 @@ my $confdesc = {
     },
     memory => {
        optional => 1,
-       type => 'integer',
-       description => "Amount of RAM for the VM in MiB. This is the maximum available memory when"
-           ." you use the balloon device.",
-       minimum => 16,
-       default => 512,
+       type => 'string',
+       description => "Memory properties.",
+       format => $PVE::QemuServer::Memory::memory_fmt
     },
     balloon => {
        optional => 1,
@@ -842,44 +852,9 @@ while (my ($k, $v) = each %$confdesc) {
 my $MAX_NETS = 32;
 my $MAX_SERIAL_PORTS = 4;
 my $MAX_PARALLEL_PORTS = 3;
-my $MAX_NUMA = 8;
-
-my $numa_fmt = {
-    cpus => {
-       type => "string",
-       pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/,
-       description => "CPUs accessing this NUMA node.",
-       format_description => "id[-id];...",
-    },
-    memory => {
-       type => "number",
-       description => "Amount of memory this NUMA node provides.",
-       optional => 1,
-    },
-    hostnodes => {
-       type => "string",
-       pattern => qr/\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*/,
-       description => "Host NUMA nodes to use.",
-       format_description => "id[-id];...",
-       optional => 1,
-    },
-    policy => {
-       type => 'string',
-       enum => [qw(preferred bind interleave)],
-       description => "NUMA allocation policy.",
-       optional => 1,
-    },
-};
-PVE::JSONSchema::register_format('pve-qm-numanode', $numa_fmt);
-my $numadesc = {
-    optional => 1,
-    type => 'string', format => $numa_fmt,
-    description => "NUMA topology.",
-};
-PVE::JSONSchema::register_standard_option("pve-qm-numanode", $numadesc);
 
-for (my $i = 0; $i < $MAX_NUMA; $i++)  {
-    $confdesc->{"numa$i"} = $numadesc;
+for (my $i = 0; $i < $PVE::QemuServer::Memory::MAX_NUMA; $i++)  {
+    $confdesc->{"numa$i"} = $PVE::QemuServer::Memory::numadesc;
 }
 
 my $nic_model_list = [
@@ -1375,64 +1350,19 @@ sub pve_verify_hotplug_features {
     die "unable to parse hotplug option\n";
 }
 
-sub scsi_inquiry {
-    my($fh, $noerr) = @_;
-
-    my $SG_IO = 0x2285;
-    my $SG_GET_VERSION_NUM = 0x2282;
-
-    my $versionbuf = "\x00" x 8;
-    my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
-    if (!$ret) {
-       die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
-       return;
-    }
-    my $version = unpack("I", $versionbuf);
-    if ($version < 30000) {
-       die "scsi generic interface too old\n"  if !$noerr;
-       return;
-    }
-
-    my $buf = "\x00" x 36;
-    my $sensebuf = "\x00" x 8;
-    my $cmd = pack("C x3 C x1", 0x12, 36);
-
-    # see /usr/include/scsi/sg.h
-    my $sg_io_hdr_t = "i i C C s I P P P I I i P C C C C S S i I I";
-
-    my $packet = pack(
-       $sg_io_hdr_t, ord('S'), -3, length($cmd), length($sensebuf), 0, length($buf), $buf, $cmd, $sensebuf, 6000
-    );
+sub assert_clipboard_config {
+    my ($vga) = @_;
 
-    $ret = ioctl($fh, $SG_IO, $packet);
-    if (!$ret) {
-       die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
-       return;
-    }
+    my $clipboard_regex = qr/^(std|cirrus|vmware|virtio|qxl)/;
 
-    my @res = unpack($sg_io_hdr_t, $packet);
-    if ($res[17] || $res[18]) {
-       die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
-       return;
+    if (
+       $vga->{'clipboard'}
+       && $vga->{'clipboard'} eq 'vnc'
+       && $vga->{type}
+       && $vga->{type} !~ $clipboard_regex
+    ) {
+       die "vga type $vga->{type} is not compatible with VNC clipboard\n";
     }
-
-    my $res = {};
-    $res->@{qw(type removable vendor product revision)} = unpack("C C x6 A8 A16 A4", $buf);
-
-    $res->{removable} = $res->{removable} & 128 ? 1 : 0;
-    $res->{type} &= 0x1F;
-
-    return $res;
-}
-
-sub path_is_scsi {
-    my ($path) = @_;
-
-    my $fh = IO::File->new("+<$path") || return;
-    my $res = scsi_inquiry($fh, 1);
-    close($fh);
-
-    return $res;
 }
 
 sub print_tabletdevice_full {
@@ -1479,31 +1409,10 @@ sub print_drivedevice_full {
 
        my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive);
        my $unit = $drive->{index} % $maxdev;
-       my $devicetype = 'hd';
-       my $path = '';
-       if (drive_is_cdrom($drive)) {
-           $devicetype = 'cd';
-       } else {
-           if ($drive->{file} =~ m|^/|) {
-               $path = $drive->{file};
-               if (my $info = path_is_scsi($path)) {
-                   if ($info->{type} == 0 && $drive->{scsiblock}) {
-                       $devicetype = 'block';
-                   } elsif ($info->{type} == 1) { # tape
-                       $devicetype = 'generic';
-                   }
-               }
-           } else {
-                $path = PVE::Storage::path($storecfg, $drive->{file});
-           }
 
-           # for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
-           my $version = extract_version($machine_type, kvm_user_version());
-           if ($path =~ m/^iscsi\:\/\// &&
-              !min_version($version, 4, 1)) {
-               $devicetype = 'generic';
-           }
-       }
+       my $machine_version = extract_version($machine_type, kvm_user_version());
+       my $devicetype  = PVE::QemuServer::Drive::get_scsi_devicetype(
+           $drive, $storecfg, $machine_version);
 
        if (!$conf->{scsihw} || $conf->{scsihw} =~ m/^lsi/ || $conf->{scsihw} eq 'pvscsi') {
            $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,scsi-id=$unit";
@@ -1518,6 +1427,16 @@ sub print_drivedevice_full {
        }
        $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
 
+       # only scsi-hd and scsi-cd support passing vendor and product information
+       if ($devicetype eq 'hd' || $devicetype eq 'cd') {
+           if (my $vendor = $drive->{vendor}) {
+               $device .= ",vendor=$vendor";
+           }
+           if (my $product = $drive->{product}) {
+               $device .= ",product=$product";
+           }
+       }
+
     } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
        my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2;
        my $controller = int($drive->{index} / $maxdev);
@@ -1930,29 +1849,6 @@ sub print_vga_device {
     return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}${edidoff}";
 }
 
-sub parse_number_sets {
-    my ($set) = @_;
-    my $res = [];
-    foreach my $part (split(/;/, $set)) {
-       if ($part =~ /^\s*(\d+)(?:-(\d+))?\s*$/) {
-           die "invalid range: $part ($2 < $1)\n" if defined($2) && $2 < $1;
-           push @$res, [ $1, $2 ];
-       } else {
-           die "invalid range: $part\n";
-       }
-    }
-    return $res;
-}
-
-sub parse_numa {
-    my ($data) = @_;
-
-    my $res = parse_property_string($numa_fmt, $data);
-    $res->{cpus} = parse_number_sets($res->{cpus}) if defined($res->{cpus});
-    $res->{hostnodes} = parse_number_sets($res->{hostnodes}) if defined($res->{hostnodes});
-    return $res;
-}
-
 # netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>
 sub parse_net {
     my ($data, $disable_mac_autogen) = @_;
@@ -2396,6 +2292,9 @@ sub destroy_vm {
        });
     }
 
+    eval { delete_ifaces_ipams_ips($conf, $vmid)};
+    warn $@ if $@;
+
     if (defined $replacement_conf) {
        PVE::QemuConfig->write_config($vmid, $replacement_conf);
     } else {
@@ -2960,8 +2859,7 @@ sub vmstatus {
        $d->{cpus} = $conf->{vcpus} if $conf->{vcpus};
 
        $d->{name} = $conf->{name} || "VM $vmid";
-       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024)
-           : $defaults->{memory}*(1024*1024);
+       $d->{maxmem} = get_current_memory($conf->{memory})*(1024*1024);
 
        if ($conf->{balloon}) {
            $d->{balloon_min} = $conf->{balloon}*(1024*1024);
@@ -3403,9 +3301,13 @@ sub get_ovmf_files($$$) {
        or die "no OVMF images known for architecture '$arch'\n";
 
     my $type = 'default';
-    if ($arch ne "aarch64" && defined($efidisk->{efitype}) && $efidisk->{efitype} eq '4m') {
-       $type = $smm ? "4m" : "4m-no-smm";
-       $type .= '-ms' if $efidisk->{'pre-enrolled-keys'};
+    if ($arch eq 'x86_64') {
+       if (defined($efidisk->{efitype}) && $efidisk->{efitype} eq '4m') {
+           $type = $smm ? "4m" : "4m-no-smm";
+           $type .= '-ms' if $efidisk->{'pre-enrolled-keys'};
+       } else {
+           # TODO: log_warn about use of legacy images for x86_64 with Promxox VE 9
+       }
     }
 
     my ($ovmf_code, $ovmf_vars) = $types->{$type}->@*;
@@ -3716,6 +3618,9 @@ sub config_to_command {
     }
 
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
+       die "OVMF (UEFI) BIOS is not supported on 32-bit CPU types\n"
+           if !$forcecpu && get_cpu_bitness($conf->{cpu}, $arch) == 32;
+
        my ($code_drive_str, $var_drive_str) =
            print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $arch, $q35, $version_guard);
        push $cmd->@*, '-drive', $code_drive_str;
@@ -3906,7 +3811,7 @@ sub config_to_command {
     }
 
     PVE::QemuServer::Memory::config(
-       $conf, $vmid, $sockets, $cores, $defaults, $hotplug_features->{memory}, $cmd);
+       $conf, $vmid, $sockets, $cores, $hotplug_features->{memory}, $cmd);
 
     push @$cmd, '-S' if $conf->{freeze};
 
@@ -3945,7 +3850,10 @@ sub config_to_command {
 
     my $spice_port;
 
-    if ($qxlnum || $vga->{type} =~ /^virtio/) {
+    assert_clipboard_config($vga);
+    my $is_spice = $qxlnum || $vga->{type} =~ /^virtio/;
+
+    if ($is_spice || ($vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc')) {
        if ($qxlnum > 1) {
            if ($winversion){
                for (my $i = 1; $i < $qxlnum; $i++){
@@ -3966,29 +3874,34 @@ sub config_to_command {
 
        my $pciaddr = print_pci_addr("spice", $bridges, $arch, $machine_type);
 
-       my $pfamily = PVE::Tools::get_host_address_family($nodename);
-       my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
-       die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
-
        push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
-       push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
+       if ($vga->{'clipboard'} && $vga->{'clipboard'} eq 'vnc') {
+           push @$devices, '-chardev', 'qemu-vdagent,id=vdagent,name=vdagent,clipboard=on';
+       } else {
+           push @$devices, '-chardev', 'spicevmc,id=vdagent,name=vdagent';
+       }
        push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
 
-       my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
-       $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
+       if ($is_spice) {
+           my $pfamily = PVE::Tools::get_host_address_family($nodename);
+           my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
+           die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
 
-       my $spice_enhancement_str = $conf->{spice_enhancements} // '';
-       my $spice_enhancement = parse_property_string($spice_enhancements_fmt, $spice_enhancement_str);
-       if ($spice_enhancement->{foldersharing}) {
-           push @$devices, '-chardev', "spiceport,id=foldershare,name=org.spice-space.webdav.0";
-           push @$devices, '-device', "virtserialport,chardev=foldershare,name=org.spice-space.webdav.0";
-       }
+           my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
+           $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
 
-       my $spice_opts = "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
-       $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}"
-           if $spice_enhancement->{videostreaming};
+           my $spice_enhancement_str = $conf->{spice_enhancements} // '';
+           my $spice_enhancement = parse_property_string($spice_enhancements_fmt, $spice_enhancement_str);
+           if ($spice_enhancement->{foldersharing}) {
+               push @$devices, '-chardev', "spiceport,id=foldershare,name=org.spice-space.webdav.0";
+               push @$devices, '-device', "virtserialport,chardev=foldershare,name=org.spice-space.webdav.0";
+           }
 
-       push @$devices, '-spice', "$spice_opts";
+           my $spice_opts = "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
+           $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}"
+               if $spice_enhancement->{videostreaming};
+           push @$devices, '-spice', "$spice_opts";
+       }
     }
 
     # enable balloon by default, unless explicitly disabled
@@ -4435,21 +4348,6 @@ sub qemu_spice_usbredir_chardev_add {
     ));
 }
 
-sub qemu_deviceadd {
-    my ($vmid, $devicefull) = @_;
-
-    $devicefull = "driver=".$devicefull;
-    my %options =  split(/[=,]/, $devicefull);
-
-    mon_cmd($vmid, "device_add" , %options);
-}
-
-sub qemu_devicedel {
-    my ($vmid, $deviceid) = @_;
-
-    my $ret = mon_cmd($vmid, "device_del", id => $deviceid);
-}
-
 sub qemu_iothread_add {
     my ($vmid, $deviceid, $device) = @_;
 
@@ -4468,22 +4366,6 @@ sub qemu_iothread_del {
     }
 }
 
-sub qemu_objectadd {
-    my ($vmid, $objectid, $qomtype) = @_;
-
-    mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
-
-    return 1;
-}
-
-sub qemu_objectdel {
-    my ($vmid, $objectid) = @_;
-
-    mon_cmd($vmid, "object-del", id => $objectid);
-
-    return 1;
-}
-
 sub qemu_driveadd {
     my ($storecfg, $vmid, $device) = @_;
 
@@ -4801,24 +4683,29 @@ sub qemu_volume_snapshot {
 }
 
 sub qemu_volume_snapshot_delete {
-    my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
+    my ($vmid, $storecfg, $volid, $snap) = @_;
 
     my $running = check_running($vmid);
+    my $attached_deviceid;
 
-    if($running) {
-
-       $running = undef;
+    if ($running) {
        my $conf = PVE::QemuConfig->load_config($vmid);
        PVE::QemuConfig->foreach_volume($conf, sub {
            my ($ds, $drive) = @_;
-           $running = 1 if $drive->{file} eq $volid;
+           $attached_deviceid = "drive-$ds" if $drive->{file} eq $volid;
        });
     }
 
-    if ($running && do_snapshots_with_qemu($storecfg, $volid, $deviceid)) {
-       mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
+    if ($attached_deviceid && do_snapshots_with_qemu($storecfg, $volid, $attached_deviceid)) {
+       mon_cmd(
+           $vmid,
+           'blockdev-snapshot-delete-internal-sync',
+           device => $attached_deviceid,
+           name => $snap,
+       );
     } else {
-       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
+       PVE::Storage::volume_snapshot_delete(
+           $storecfg, $volid, $snap, $attached_deviceid ? 1 : undef);
     }
 }
 
@@ -5052,7 +4939,7 @@ sub vmconfig_hotplug_pending {
        my $force = $pending_delete_hash->{$opt}->{force};
        eval {
            if ($opt eq 'hotplug') {
-               die "skip\n" if ($conf->{hotplug} =~ /memory/);
+               die "skip\n" if ($conf->{hotplug} =~ /(cpu|memory)/);
            } elsif ($opt eq 'tablet') {
                die "skip\n" if !$hotplug_features->{usb};
                if ($defaults->{tablet}) {
@@ -5075,20 +4962,24 @@ sub vmconfig_hotplug_pending {
                # enable balloon device is not hotpluggable
                die "skip\n" if defined($conf->{balloon}) && $conf->{balloon} == 0;
                # here we reset the ballooning value to memory
-               my $balloon = $conf->{memory} || $defaults->{memory};
+               my $balloon = get_current_memory($conf->{memory});
                mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
            } elsif ($fast_plug_option->{$opt}) {
                # do nothing
            } elsif ($opt =~ m/^net(\d+)$/) {
                die "skip\n" if !$hotplug_features->{network};
                vm_deviceunplug($vmid, $conf, $opt);
+               if($have_sdn) {
+                   my $net = PVE::QemuServer::parse_net($conf->{$opt});
+                   PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name});
+               }
            } elsif (is_valid_drivename($opt)) {
                die "skip\n" if !$hotplug_features->{disk} || $opt =~ m/(ide|sata)(\d+)/;
                vm_deviceunplug($vmid, $conf, $opt);
                vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
            } elsif ($opt =~ m/^memory$/) {
                die "skip\n" if !$hotplug_features->{memory};
-               PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults);
+               PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf);
            } elsif ($opt eq 'cpuunits') {
                $cgroup->change_cpu_shares(undef);
            } elsif ($opt eq 'cpulimit') {
@@ -5113,6 +5004,7 @@ sub vmconfig_hotplug_pending {
        eval {
            if ($opt eq 'hotplug') {
                die "skip\n" if ($value =~ /memory/) || ($value !~ /memory/ && $conf->{hotplug} =~ /memory/);
+               die "skip\n" if ($value =~ /cpu/) || ($value !~ /cpu/ && $conf->{hotplug} =~ /cpu/);
            } elsif ($opt eq 'tablet') {
                die "skip\n" if !$hotplug_features->{usb};
                if ($value == 1) {
@@ -5143,7 +5035,8 @@ sub vmconfig_hotplug_pending {
 
                # allow manual ballooning if shares is set to zero
                if ((defined($conf->{shares}) && ($conf->{shares} == 0))) {
-                   my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
+                   my $memory = get_current_memory($conf->{memory});
+                   my $balloon = $conf->{pending}->{balloon} || $memory;
                    mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
                }
            } elsif ($opt =~ m/^net(\d+)$/) {
@@ -5163,7 +5056,7 @@ sub vmconfig_hotplug_pending {
                                     $vmid, $opt, $value, $arch, $machine_type);
            } elsif ($opt =~ m/^memory$/) { #dimms
                die "skip\n" if !$hotplug_features->{memory};
-               $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $value);
+               $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $value);
            } elsif ($opt eq 'cpuunits') {
                my $new_cpuunits = PVE::CGroup::clamp_cpu_shares($conf->{pending}->{$opt}); #clamp
                $cgroup->change_cpu_shares($new_cpuunits);
@@ -5285,6 +5178,12 @@ sub vmconfig_apply_pending {
                die "internal error";
            } elsif (defined($conf->{$opt}) && is_valid_drivename($opt)) {
                vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
+           } elsif (defined($conf->{$opt}) && $opt =~ m/^net\d+$/) {
+               if($have_sdn) {
+                   my $net = PVE::QemuServer::parse_net($conf->{$opt});
+                   eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name}) };
+                   warn if $@;
+               }
            }
        };
        if (my $err = $@) {
@@ -5304,6 +5203,20 @@ sub vmconfig_apply_pending {
        eval {
            if (defined($conf->{$opt}) && is_valid_drivename($opt)) {
                vmconfig_register_unused_drive($storecfg, $vmid, $conf, parse_drive($opt, $conf->{$opt}))
+           } elsif (defined($conf->{pending}->{$opt}) && $opt =~ m/^net\d+$/) {
+               if($have_sdn) {
+                    my $new_net = PVE::QemuServer::parse_net($conf->{pending}->{$opt});
+                   if ($conf->{$opt}){
+                       my $old_net = PVE::QemuServer::parse_net($conf->{$opt});
+
+                       if ($old_net->{bridge} ne $new_net->{bridge} ||
+                           $old_net->{macaddr} ne $new_net->{macaddr}) {
+                           PVE::Network::SDN::Vnets::del_ips_from_mac($old_net->{bridge}, $old_net->{macaddr}, $conf->{name});
+                       }
+                  }
+                  #fixme: reuse ip if mac change && same bridge
+                  PVE::Network::SDN::Vnets::add_next_free_cidr($new_net->{bridge}, $conf->{name}, $new_net->{macaddr}, $vmid, undef, 1);
+               }
            }
        };
        if (my $err = $@) {
@@ -5347,6 +5260,11 @@ sub vmconfig_update_net {
             # for non online change, we try to hot-unplug
            die "skip\n" if !$hotplug;
            vm_deviceunplug($vmid, $conf, $opt);
+
+           if($have_sdn) {
+               PVE::Network::SDN::Vnets::del_ips_from_mac($oldnet->{bridge}, $oldnet->{macaddr}, $conf->{name});
+           }
+
        } else {
 
            die "internal error" if $opt !~ m/net(\d+)/;
@@ -5358,11 +5276,31 @@ sub vmconfig_update_net {
                safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
                PVE::Network::tap_unplug($iface);
 
+               #set link_down in guest if bridge or vlan change to notify guest (dhcp renew for example)
+               if (safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+                   safe_num_ne($oldnet->{tag}, $newnet->{tag})) {
+                   qemu_set_link_status($vmid, $opt, 0);
+               }
+
+               if (safe_string_ne($oldnet->{bridge}, $newnet->{bridge})) {
+                   if ($have_sdn) {
+                       PVE::Network::SDN::Vnets::del_ips_from_mac($oldnet->{bridge}, $oldnet->{macaddr}, $conf->{name});
+                       PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, $vmid, undef, 1);
+                   }
+               }
+
                if ($have_sdn) {
                    PVE::Network::SDN::Zones::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
                } else {
                    PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
                }
+
+               #set link_up in guest if bridge or vlan change to notify guest (dhcp renew for example)
+               if (safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+                   safe_num_ne($oldnet->{tag}, $newnet->{tag})) {
+                   qemu_set_link_status($vmid, $opt, 1);
+               }
+
            } elsif (safe_num_ne($oldnet->{rate}, $newnet->{rate})) {
                # Rate can be applied on its own but any change above needs to
                # include the rate in tap_plug since OVS resets everything.
@@ -5378,6 +5316,10 @@ sub vmconfig_update_net {
     }
 
     if ($hotplug) {
+       if ($have_sdn) {
+           PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, $vmid, undef, 1);
+           PVE::Network::SDN::Vnets::add_dhcp_mapping($newnet->{bridge}, $newnet->{macaddr}, $vmid, $conf->{name});
+       }
        vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet, $arch, $machine_type);
     } else {
        die "skip\n";
@@ -5435,8 +5377,10 @@ sub vmconfig_update_disk {
                    safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
                    safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
                    safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
+                   safe_string_ne($drive->{product}, $old_drive->{product}) ||
                    safe_string_ne($drive->{cache}, $old_drive->{cache}) ||
                    safe_string_ne($drive->{ssd}, $old_drive->{ssd}) ||
+                   safe_string_ne($drive->{vendor}, $old_drive->{vendor}) ||
                    safe_string_ne($drive->{ro}, $old_drive->{ro})) {
                    die "skip\n";
                }
@@ -5799,10 +5743,9 @@ sub vm_start_nolock {
                $migrate->{addr} = "[$migrate->{addr}]" if Net::IP::ip_is_ipv6($migrate->{addr});
            }
 
-           my $pfamily = PVE::Tools::get_host_address_family($nodename);
-           $migrate->{port} = PVE::Tools::next_migrate_port($pfamily);
-           $migrate->{uri} = "tcp:$migrate->{addr}:$migrate->{port}";
-           push @$cmd, '-incoming', $migrate->{uri};
+           # see #4501: port reservation should be done close to usage - tell QEMU where to listen
+           # via QMP later
+           push @$cmd, '-incoming', 'defer';
            push @$cmd, '-S';
 
        } elsif ($statefile eq 'unix') {
@@ -5828,7 +5771,8 @@ sub vm_start_nolock {
        push @$cmd, '-S';
     }
 
-    my $start_timeout = $params->{timeout} // config_aware_timeout($conf, $resume);
+    my $memory = get_current_memory($conf->{memory});
+    my $start_timeout = $params->{timeout} // config_aware_timeout($conf, $memory, $resume);
 
     my $pci_reserve_list = [];
     for my $device (values $pci_devices->%*) {
@@ -5985,8 +5929,15 @@ sub vm_start_nolock {
     eval { PVE::QemuServer::PCI::reserve_pci_usage($pci_reserve_list, $vmid, undef, $pid) };
     warn $@ if $@;
 
-    if (defined($res->{migrate})) {
-       print "migration listens on $res->{migrate}->{uri}\n";
+    if (defined(my $migrate = $res->{migrate})) {
+       if ($migrate->{proto} eq 'tcp') {
+           my $nodename = nodename();
+           my $pfamily = PVE::Tools::get_host_address_family($nodename);
+           $migrate->{port} = PVE::Tools::next_migrate_port($pfamily);
+           $migrate->{uri} = "tcp:$migrate->{addr}:$migrate->{port}";
+           mon_cmd($vmid, "migrate-incoming", uri => $migrate->{uri});
+       }
+       print "migration listens on $migrate->{uri}\n";
     } elsif ($statefile) {
        eval { mon_cmd($vmid, "cont"); };
        warn $@ if $@;
@@ -6102,6 +6053,15 @@ sub vm_start_nolock {
 
     PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
 
+    my ($current_machine, $is_deprecated) =
+       PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
+    if ($is_deprecated) {
+       log_warn(
+           "current machine version '$current_machine' is deprecated - see the documentation and ".
+           "change to a newer one",
+       );
+    }
+
     return $res;
 }
 
@@ -6176,12 +6136,20 @@ sub cleanup_pci_devices {
            my $dev_sysfs_dir = "/sys/bus/mdev/devices/$uuid";
 
            # some nvidia vgpu driver versions want to clean the mdevs up themselves, and error
-           # out when we do it first. so wait for 10 seconds and then try it
-           if ($d->{ids}->[0]->[0]->{vendor} =~ m/^(0x)?10de$/) {
-               sleep 10;
+           # out when we do it first. so wait for up to 10 seconds and then try it manually
+           if ($d->{ids}->[0]->[0]->{vendor} =~ m/^(0x)?10de$/ && -e $dev_sysfs_dir) {
+               my $count = 0;
+               while (-e $dev_sysfs_dir && $count < 10) {
+                   sleep 1;
+                   $count++;
+               }
+               print "waited $count seconds for mediated device driver finishing clean up\n";
            }
 
-           PVE::SysFSTools::file_write("$dev_sysfs_dir/remove", "1") if -e $dev_sysfs_dir;
+           if (-e $dev_sysfs_dir) {
+               print "actively clean up mediated device with UUID $uuid\n";
+               PVE::SysFSTools::file_write("$dev_sysfs_dir/remove", "1");
+           }
        }
     }
     PVE::QemuServer::PCI::remove_pci_reservation($vmid);
@@ -8356,7 +8324,7 @@ sub generate_smbios1_uuid {
 sub nbd_stop {
     my ($vmid) = @_;
 
-    mon_cmd($vmid, 'nbd-server-stop');
+    mon_cmd($vmid, 'nbd-server-stop', timeout => 25);
 }
 
 sub create_reboot_request {
@@ -8586,13 +8554,17 @@ sub complete_migration_storage {
 }
 
 sub vm_is_paused {
-    my ($vmid) = @_;
+    my ($vmid, $include_suspended) = @_;
     my $qmpstatus = eval {
        PVE::QemuConfig::assert_config_exists_on_node($vmid);
        mon_cmd($vmid, "query-status");
     };
     warn "$@\n" if $@;
-    return $qmpstatus && $qmpstatus->{status} eq "paused";
+    return $qmpstatus && (
+       $qmpstatus->{status} eq "paused" ||
+       $qmpstatus->{status} eq "prelaunch" ||
+       ($include_suspended && $qmpstatus->{status} eq "suspended")
+    );
 }
 
 sub check_volume_storage_type {
@@ -8630,9 +8602,9 @@ sub add_nets_bridge_fdb {
            next;
        }
        if ($have_sdn) {
-           PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
+           PVE::Network::SDN::Zones::add_bridge_fdb($iface, $mac, $bridge);
        } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now
-           PVE::Network::add_bridge_fdb($iface, $mac, $net->{firewall});
+           PVE::Network::add_bridge_fdb($iface, $mac);
        }
     }
 }
@@ -8649,9 +8621,38 @@ sub del_nets_bridge_fdb {
 
        my $bridge = $net->{bridge};
        if ($have_sdn) {
-           PVE::Network::SDN::Zones::del_bridge_fdb($iface, $mac, $bridge, $net->{firewall});
+           PVE::Network::SDN::Zones::del_bridge_fdb($iface, $mac, $bridge);
        } elsif (-d "/sys/class/net/$bridge/bridge") { # avoid fdb management with OVS for now
-           PVE::Network::del_bridge_fdb($iface, $mac, $net->{firewall});
+           PVE::Network::del_bridge_fdb($iface, $mac);
+       }
+    }
+}
+
+sub create_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    foreach my $opt (keys %$conf) {
+        if ($opt =~ m/^net(\d+)$/) {
+            my $value = $conf->{$opt};
+            my $net = PVE::QemuServer::parse_net($value);
+            eval { PVE::Network::SDN::Vnets::add_next_free_cidr($net->{bridge}, $conf->{name}, $net->{macaddr}, $vmid, undef, 1) };
+            warn $@ if $@;
+        }
+    }
+}
+
+sub delete_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    foreach my $opt (keys %$conf) {
+       if ($opt =~ m/^net(\d+)$/) {
+           my $net = PVE::QemuServer::parse_net($conf->{$opt});
+           eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name}) };
+           warn $@ if $@;
        }
     }
 }