]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
remove unused method "touch_config"
[qemu-server.git] / PVE / QemuServer.pm
index ac9dfdead8bda95751714bbf587d55f049958ccf..dbac6957fb0dd47926a6acb37fe7f29361aa0ec7 100644 (file)
@@ -3,45 +3,47 @@ package PVE::QemuServer;
 use strict;
 use warnings;
 
-use POSIX;
-use IO::Handle;
-use IO::Select;
-use IO::File;
-use IO::Dir;
-use IO::Socket::UNIX;
+use Cwd 'abs_path';
+use Digest::SHA;
+use Fcntl ':flock';
+use Fcntl;
 use File::Basename;
+use File::Copy qw(copy);
 use File::Path;
 use File::stat;
 use Getopt::Long;
-use Digest::SHA;
-use Fcntl ':flock';
-use Cwd 'abs_path';
+use IO::Dir;
+use IO::File;
+use IO::Handle;
+use IO::Select;
+use IO::Socket::UNIX;
 use IPC::Open3;
 use JSON;
-use Fcntl;
-use PVE::SafeSyslog;
-use Storable qw(dclone);
 use MIME::Base64;
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::Storage;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
-use PVE::JSONSchema qw(get_standard_option);
+use POSIX;
+use Storable qw(dclone);
+use Time::HiRes qw(gettimeofday);
+use URI::Escape;
+
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::GuestHelpers;
 use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
 use PVE::ProcFSTools;
-use PVE::QemuConfig;
-use PVE::QMPClient;
 use PVE::RPCEnvironment;
-use PVE::GuestHelpers;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
-use PVE::QemuServer::Memory;
-use PVE::QemuServer::USB qw(parse_usb_device);
-use PVE::QemuServer::Cloudinit;
+use PVE::SafeSyslog;
+use PVE::Storage;
 use PVE::SysFSTools;
 use PVE::Systemd;
-use Time::HiRes qw(gettimeofday);
-use File::Copy qw(copy);
-use URI::Escape;
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
+
+use PVE::QMPClient;
+use PVE::QemuConfig;
+use PVE::QemuServer::Cloudinit;
+use PVE::QemuServer::Memory;
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
+use PVE::QemuServer::USB qw(parse_usb_device);
 
 my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
 my $OVMF = {
@@ -1896,7 +1898,9 @@ sub print_drivedevice_full {
                 $path = PVE::Storage::path($storecfg, $drive->{file});
            }
 
-           if($path =~ m/^iscsi\:\/\//){
+           # for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
+           if ($path =~ m/^iscsi\:\/\// &&
+              !qemu_machine_feature_enabled($machine_type, undef, 4, 1)) {
                $devicetype = 'generic';
            }
        }
@@ -2348,40 +2352,6 @@ sub vm_is_volid_owner {
     return undef;
 }
 
-sub split_flagged_list {
-    my $text = shift || '';
-    $text =~ s/[,;]/ /g;
-    $text =~ s/^\s+//;
-    return { map { /^(!?)(.*)$/ && ($2, $1) } ($text =~ /\S+/g) };
-}
-
-sub join_flagged_list {
-    my ($how, $lst) = @_;
-    join $how, map { $lst->{$_} . $_ } keys %$lst;
-}
-
-sub vmconfig_delete_pending_option {
-    my ($conf, $key, $force) = @_;
-
-    delete $conf->{pending}->{$key};
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    $pending_delete_hash->{$key} = $force ? '!' : '';
-    $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-}
-
-sub vmconfig_undelete_pending_option {
-    my ($conf, $key) = @_;
-
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    delete $pending_delete_hash->{$key};
-
-    if (%$pending_delete_hash) {
-       $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-    } else {
-       delete $conf->{pending}->{delete};
-    }
-}
-
 sub vmconfig_register_unused_drive {
     my ($storecfg, $vmid, $conf, $drive) = @_;
 
@@ -2396,37 +2366,6 @@ sub vmconfig_register_unused_drive {
     }
 }
 
-sub vmconfig_cleanup_pending {
-    my ($conf) = @_;
-
-    # remove pending changes when nothing changed
-    my $changes;
-    foreach my $opt (keys %{$conf->{pending}}) {
-       if (defined($conf->{$opt}) && ($conf->{pending}->{$opt} eq  $conf->{$opt})) {
-           $changes = 1;
-           delete $conf->{pending}->{$opt};
-       }
-    }
-
-    my $current_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    my $pending_delete_hash = {};
-    while (my ($opt, $force) = each %$current_delete_hash) {
-       if (defined($conf->{$opt})) {
-           $pending_delete_hash->{$opt} = $force;
-       } else {
-           $changes = 1;
-       }
-    }
-
-    if (%$pending_delete_hash) {
-       $conf->{pending}->{delete} = join_flagged_list(',', $pending_delete_hash);
-    } else {
-       delete $conf->{pending}->{delete};
-    }
-
-    return $changes;
-}
-
 # smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool]
 my $smbios1_fmt = {
     uuid => {
@@ -2609,18 +2548,9 @@ sub check_type {
     }
 }
 
-sub touch_config {
-    my ($vmid) = @_;
-
-    my $conf = PVE::QemuConfig->config_file($vmid);
-    utime undef, undef, $conf;
-}
-
 sub destroy_vm {
     my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_;
 
-    my $conffile = PVE::QemuConfig->config_file($vmid);
-
     my $conf = PVE::QemuConfig->load_config($vmid);
 
     PVE::QemuConfig->check_lock($conf) if !$skiplock;
@@ -2663,9 +2593,9 @@ sub destroy_vm {
     });
 
     if ($keep_empty_config) {
-       PVE::Tools::file_set_contents($conffile, "memory: 128\n");
+       PVE::QemuConfig->write_config($vmid, { memory => 128 });
     } else {
-       unlink $conffile;
+       PVE::QemuConfig->destroy_config($vmid);
     }
 
     # also remove unused disk
@@ -3623,8 +3553,6 @@ sub config_to_command {
 
     die "detected old qemu-kvm binary ($kvmver)\n" if $vernum < 15000;
 
-    my $have_ovz = -f '/proc/vz/vestat';
-
     my $q35 = machine_type_is_q35($conf);
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
     my $use_old_bios_files = undef;
@@ -4185,7 +4113,7 @@ sub config_to_command {
 
     if (my $vmstate = $conf->{vmstate}) {
        my $statepath = PVE::Storage::path($storecfg, $vmstate);
-       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+       push @$vollist, $vmstate;
        push @$cmd, '-loadstate', $statepath;
     }
 
@@ -4922,9 +4850,10 @@ sub vmconfig_hotplug_pending {
 
     my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
 
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    while (my ($opt, $force) = each %$pending_delete_hash) {
+    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+    foreach my $opt (sort keys %$pending_delete_hash) {
        next if $selection && !$selection->{$opt};
+       my $force = $pending_delete_hash->{$opt}->{force};
        eval {
            if ($opt eq 'hotplug') {
                die "skip\n" if ($conf->{hotplug} =~ /memory/);
@@ -4978,7 +4907,7 @@ sub vmconfig_hotplug_pending {
        } else {
            # save new config if hotplug was successful
            delete $conf->{$opt};
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            PVE::QemuConfig->write_config($vmid, $conf);
            $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
@@ -5113,25 +5042,28 @@ sub vmconfig_delete_or_detach_drive {
     }
 }
 
+
+
 sub vmconfig_apply_pending {
     my ($vmid, $conf, $storecfg) = @_;
 
     # cold plug
 
-    my $pending_delete_hash = split_flagged_list($conf->{pending}->{delete});
-    while (my ($opt, $force) = each %$pending_delete_hash) {
+    my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+    foreach my $opt (sort keys %$pending_delete_hash) {
        die "internal error" if $opt =~ m/^unused/;
+       my $force = $pending_delete_hash->{$opt}->{force};
        $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        if (!defined($conf->{$opt})) {
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            PVE::QemuConfig->write_config($vmid, $conf);
        } elsif (is_valid_drivename($opt)) {
            vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            delete $conf->{$opt};
            PVE::QemuConfig->write_config($vmid, $conf);
        } else {
-           vmconfig_undelete_pending_option($conf, $opt);
+           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            delete $conf->{$opt};
            PVE::QemuConfig->write_config($vmid, $conf);
        }
@@ -5420,7 +5352,6 @@ sub vm_start {
 
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
-       my $migrate_port = 0;
        my $migrate_uri;
        if ($statefile) {
            if ($statefile eq 'tcp') {
@@ -5448,7 +5379,7 @@ sub vm_start {
                }
 
                my $pfamily = PVE::Tools::get_host_address_family($nodename);
-               $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+               my $migrate_port = PVE::Tools::next_migrate_port($pfamily);
                $migrate_uri = "tcp:${localip}:${migrate_port}";
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
@@ -5465,8 +5396,12 @@ sub vm_start {
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
 
-           } else {
+           } elsif (-e $statefile) {
                push @$cmd, '-loadstate', $statefile;
+           } else {
+               my $statepath = PVE::Storage::path($storecfg, $statefile);
+               push @$vollist, $statefile;
+               push @$cmd, '-loadstate', $statepath;
            }
        } elsif ($paused) {
            push @$cmd, '-S';
@@ -5572,16 +5507,16 @@ sub vm_start {
            my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
            my $localip = $migrate_network_addr ? $migrate_network_addr : PVE::Cluster::remote_node_ip($nodename, 1);
            my $pfamily = PVE::Tools::get_host_address_family($nodename);
-           $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+           my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily);
 
-           vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${migrate_port}" } } );
+           vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${storage_migrate_port}" } } );
 
            $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
 
            foreach my $opt (sort keys %$local_volumes) {
                my $volid = $local_volumes->{$opt};
                vm_mon_cmd_nocheck($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
-               my $migrate_storage_uri = "nbd:${localip}:${migrate_port}:exportname=drive-$opt";
+               my $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}:exportname=drive-$opt";
                print "storage migration listens on $migrate_storage_uri volume:$volid\n";
            }
        }
@@ -6887,66 +6822,77 @@ sub qemu_img_convert {
     my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1);
     my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
 
-    if ($src_storeid && $dst_storeid) {
+    die "destination '$dst_volid' is not a valid volid form qemu-img convert\n" if !$dst_storeid;
 
-       PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
+    my $cachemode;
+    my $src_path;
+    my $src_is_iscsi = 0;
+    my $src_format = 'raw';
 
+    if ($src_storeid) {
+       PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
        my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
-       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+       $src_format = qemu_img_format($src_scfg, $src_volname);
+       $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
+       $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+       $cachemode = 'none' if $src_scfg->{type} eq 'zfspool';
+    } elsif (-f $src_volid) {
+       $src_path = $src_volid;
+       if ($src_path =~ m/\.($QEMU_FORMAT_RE)$/) {
+           $src_format = $1;
+       }
+    }
 
-       my $src_format = qemu_img_format($src_scfg, $src_volname);
-       my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+    die "source '$src_volid' is not a valid volid nor path for qemu-img convert\n" if !$src_path;
 
-       my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
-       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+    my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+    my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
 
-       my $src_is_iscsi = ($src_path =~ m|^iscsi://|);
-       my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+    my $cmd = [];
+    push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
+    push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
+    push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
+    push @$cmd, '-T', $cachemode if defined($cachemode);
+
+    if ($src_is_iscsi) {
+       push @$cmd, '--image-opts';
+       $src_path = convert_iscsi_path($src_path);
+    } else {
+       push @$cmd, '-f', $src_format;
+    }
 
-       my $cmd = [];
-       push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
-       push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
-       push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
-       push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
+    if ($dst_is_iscsi) {
+       push @$cmd, '--target-image-opts';
+       $dst_path = convert_iscsi_path($dst_path);
+    } else {
+       push @$cmd, '-O', $dst_format;
+    }
 
-       if ($src_is_iscsi) {
-           push @$cmd, '--image-opts';
-           $src_path = convert_iscsi_path($src_path);
-       } else {
-           push @$cmd, '-f', $src_format;
-       }
+    push @$cmd, $src_path;
 
-       if ($dst_is_iscsi) {
-           push @$cmd, '--target-image-opts';
-           $dst_path = convert_iscsi_path($dst_path);
-       } else {
-           push @$cmd, '-O', $dst_format;
-       }
+    if (!$dst_is_iscsi && $is_zero_initialized) {
+       push @$cmd, "zeroinit:$dst_path";
+    } else {
+       push @$cmd, $dst_path;
+    }
 
-       push @$cmd, $src_path;
+    my $parser = sub {
+       my $line = shift;
+       if($line =~ m/\((\S+)\/100\%\)/){
+           my $percent = $1;
+           my $transferred = int($size * $percent / 100);
+           my $remaining = $size - $transferred;
 
-       if (!$dst_is_iscsi && $is_zero_initialized) {
-           push @$cmd, "zeroinit:$dst_path";
-       } else {
-           push @$cmd, $dst_path;
+           print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
        }
 
-       my $parser = sub {
-           my $line = shift;
-           if($line =~ m/\((\S+)\/100\%\)/){
-               my $percent = $1;
-               my $transferred = int($size * $percent / 100);
-               my $remaining = $size - $transferred;
-
-               print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
-           }
-
-       };
+    };
 
-       eval  { run_command($cmd, timeout => undef, outfunc => $parser); };
-       my $err = $@;
-       die "copy failed: $err" if $err;
-    }
+    eval  { run_command($cmd, timeout => undef, outfunc => $parser); };
+    my $err = $@;
+    die "copy failed: $err" if $err;
 }
 
 sub qemu_img_format {
@@ -7227,9 +7173,38 @@ sub qemu_machine_feature_enabled {
        $current_minor = $2;
     }
 
-    return 1 if $current_major > $version_major ||
-                ($current_major == $version_major &&
-                 $current_minor >= $version_minor);
+    return 1 if version_cmp($current_major, $version_major, $current_minor, $version_minor) >= 0;
+}
+
+# gets in pairs the versions you want to compares, i.e.:
+# ($a-major, $b-major, $a-minor, $b-minor, $a-extra, $b-extra, ...)
+# returns 0 if same, -1 if $a is older than $b, +1 if $a is newer than $b
+sub version_cmp {
+    my @versions = @_;
+
+    my $size = scalar(@versions);
+
+    return 0 if $size == 0;
+    die "cannot compare odd count of versions" if $size & 1;
+
+    for (my $i = 0; $i < $size; $i += 2) {
+       my ($a, $b) = splice(@versions, 0, 2);
+       $a //= 0;
+       $b //= 0;
+
+       return 1 if $a > $b;
+       return -1 if $a < $b;
+    }
+    return 0;
+}
+
+sub runs_at_least_qemu_version {
+    my ($vmid, $major, $minor, $extra) = @_;
+
+    my $v = eval { PVE::QemuServer::vm_qmp_command($vmid, { execute => 'query-version' }) } // {};
+    $v = $v->{qemu};
+
+    return version_cmp($v->{major}, $major, $v->{minor}, $minor, $v->{micro}, $extra) >= 0;
 }
 
 sub qemu_machine_pxe {
@@ -7272,15 +7247,12 @@ sub create_efidisk($$$$$) {
     my (undef, $ovmf_vars) = get_ovmf_files($arch);
     die "EFI vars default image not found\n" if ! -f $ovmf_vars;
 
-    my $vars_size = PVE::Tools::convert_size(-s $ovmf_vars, 'b' => 'kb');
+    my $vars_size_b = -s $ovmf_vars;
+    my $vars_size = PVE::Tools::convert_size($vars_size_b, 'b' => 'kb');
     my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
     PVE::Storage::activate_volumes($storecfg, [$volid]);
 
-    my $path = PVE::Storage::path($storecfg, $volid);
-    eval {
-       run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $ovmf_vars, $path]);
-    };
-    die "Copying EFI vars image failed: $@" if $@;
+    qemu_img_convert($ovmf_vars, $volid, $vars_size_b, undef, 0);
 
     return ($volid, $vars_size);
 }