]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
followup: code cleanup
[qemu-server.git] / PVE / QemuServer.pm
index 8339e298dfd67a28dbe555051406605084517508..34ac7ce7fdae2bda35badf577bf3d7f305a9557d 100644 (file)
@@ -167,7 +167,21 @@ my $cpu_vendor_list = {
     max => 'default',
 };
 
-my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb|md-clear)/;
+my @supported_cpu_flags = (
+    'pcid',
+    'spec-ctrl',
+    'ibpb',
+    'ssbd',
+    'virt-ssbd',
+    'amd-ssbd',
+    'amd-no-ssb',
+    'pdpe1gb',
+    'md-clear',
+    'hv-tlbflush',
+    'hv-evmcs',
+    'aes'
+);
+my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/;
 
 my $cpu_fmt = {
     cputype => {
@@ -193,7 +207,7 @@ my $cpu_fmt = {
     flags => {
        description => "List of additional CPU flags separated by ';'."
                     . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
-                    . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb', 'md-clear'.",
+                    . " Currently supported flags: @{[join(', ', @supported_cpu_flags)]}.",
        format_description => '+FLAG[;-FLAG...]',
        type => 'string',
        pattern => qr/$cpu_flag(;$cpu_flag)*/,
@@ -267,6 +281,35 @@ my $ivshmem_fmt = {
     },
 };
 
+my $audio_fmt = {
+    device => {
+       type => 'string',
+       enum => [qw(ich9-intel-hda intel-hda AC97)],
+       description =>  "Configure an audio device."
+    },
+    driver =>  {
+       type => 'string',
+       enum => ['spice'],
+       default => 'spice',
+       optional => 1,
+       description => "Driver backend for the audio device."
+    },
+};
+
+my $spice_enhancements_fmt = {
+    foldersharing => {
+       type => 'boolean',
+       optional => 1,
+       description =>  "Enable folder sharing via SPICE. Needs Spice-WebDAV daemon installed in the VM."
+    },
+    videostreaming =>  {
+       type => 'string',
+       enum => ['off', 'all', 'filter'],
+       optional => 1,
+       description => "Enable video streaming. Uses compression for detected video streams."
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -637,7 +680,19 @@ EODESCR
        format => $ivshmem_fmt,
        description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
        optional => 1,
-    }
+    },
+    audio0 => {
+       type => 'string',
+       format => $audio_fmt,
+       description => "Configure a audio device, useful in combination with QXL/Spice.",
+       optional => 1
+    },
+    spice_enhancements => {
+       type => 'string',
+       format => $spice_enhancements_fmt,
+       description => "Configure additional enhancements for SPICE.",
+       optional => 1
+    },
 };
 
 my $cicustom_fmt = {
@@ -1437,25 +1492,33 @@ sub kvm_version {
     return $kvm_api_version;
 }
 
-my $kvm_user_version;
+my $kvm_user_version = {};
+my $kvm_mtime = {};
 
 sub kvm_user_version {
+    my ($binary) = @_;
+
+    $binary //= get_command_for_arch(get_host_arch()); # get the native arch by default
+    my $st = stat($binary);
 
-    return $kvm_user_version if $kvm_user_version;
+    my $cachedmtime = $kvm_mtime->{$binary} // -1;
+    return $kvm_user_version->{$binary} if $kvm_user_version->{$binary} &&
+       $cachedmtime == $st->mtime;
 
-    $kvm_user_version = 'unknown';
+    $kvm_user_version->{$binary} = 'unknown';
+    $kvm_mtime->{$binary} = $st->mtime;
 
     my $code = sub {
        my $line = shift;
        if ($line =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)(\.\d+)?[,\s]/) {
-           $kvm_user_version = $2;
+           $kvm_user_version->{$binary} = $2;
        }
     };
 
-    eval { run_command("kvm -version", outfunc => $code); };
+    eval { run_command([$binary, '--version'], outfunc => $code); };
     warn $@ if $@;
 
-    return $kvm_user_version;
+    return $kvm_user_version->{$binary};
 
 }
 
@@ -2924,6 +2987,45 @@ sub shared_nodes {
     return $nodehash
 }
 
+sub check_local_storage_availability {
+    my ($conf, $storecfg) = @_;
+
+    my $nodelist = PVE::Cluster::get_nodelist();
+    my $nodehash = { map { $_ => {} } @$nodelist };
+
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       my $volid = $drive->{file};
+       return if !$volid;
+
+       my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+       if ($storeid) {
+           my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+
+           if ($scfg->{disable}) {
+               foreach my $node (keys %$nodehash) {
+                   $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+               }
+           } elsif (my $avail = $scfg->{nodes}) {
+               foreach my $node (keys %$nodehash) {
+                   if (!$avail->{$node}) {
+                       $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+                   }
+               }
+           }
+       }
+    });
+
+    foreach my $node (values %$nodehash) {
+       if (my $unavail = $node->{unavailable_storages}) {
+           $node->{unavailable_storages} = [ sort keys %$unavail ];
+       }
+    }
+
+    return $nodehash
+}
+
 sub check_cmdline {
     my ($pidfile, $pid) = @_;
 
@@ -3301,7 +3403,7 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
+       my ($volid, $is_cdrom, $replicate, $shared, $snapname, $size) = @_;
 
        return if !$volid;
 
@@ -3319,11 +3421,12 @@ sub foreach_volid {
 
        $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
            if defined($snapname);
+       $volhash->{$volid}->{size} = $size if $size;
     };
 
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
-       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
+       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef, $drive->{size});
     });
 
     foreach my $snapname (keys %{$conf->{snapshots}}) {
@@ -3352,6 +3455,24 @@ sub conf_has_serial {
     return 0;
 }
 
+sub conf_has_audio {
+    my ($conf, $id) = @_;
+
+    $id //= 0;
+    my $audio = $conf->{"audio$id"};
+    return undef if !defined($audio);
+
+    my $audioproperties = PVE::JSONSchema::parse_property_string($audio_fmt, $audio);
+    my $audiodriver = $audioproperties->{driver} // 'spice';
+
+    return {
+       dev => $audioproperties->{device},
+       dev_id => "audiodev$id",
+       backend => $audiodriver,
+       backend_id => "$audiodriver-backend${id}",
+    };
+}
+
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
@@ -3474,13 +3595,14 @@ sub config_to_command {
     my $devices = [];
     my $pciaddr = '';
     my $bridges = {};
-    my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
     my $winversion = windows_version($ostype);
     my $kvm = $conf->{kvm};
 
     my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+    my $kvm_binary = get_command_for_arch($arch);
+    my $kvmver = kvm_user_version($kvm_binary);
     $kvm //= 1 if is_native($arch);
 
     if ($kvm) {
@@ -3506,7 +3628,7 @@ sub config_to_command {
     my $cpuunits = defined($conf->{cpuunits}) ?
             $conf->{cpuunits} : $defaults->{cpuunits};
 
-    push @$cmd, get_command_for_arch($arch);
+    push @$cmd, $kvm_binary;
 
     push @$cmd, '-id', $vmid;
 
@@ -3588,6 +3710,15 @@ sub config_to_command {
        push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path";
     }
 
+    # load q35 config
+    if ($q35) {
+       # we use different pcie-port hardware for qemu >= 4.0 for passthrough
+       if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) {
+           push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';
+       } else {
+           push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
+       }
+    }
 
     # add usb controllers
     my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES);
@@ -3731,6 +3862,23 @@ sub config_to_command {
        }
     }
 
+    if (my $audio = conf_has_audio($conf)) {
+
+       my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
+
+       my $id = $audio->{dev_id};
+       if ($audio->{dev} eq 'AC97') {
+           push @$devices, '-device', "AC97,id=${id}${audiopciaddr}";
+       } elsif ($audio->{dev} =~ /intel\-hda$/) {
+           push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
+           push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0";
+           push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1";
+       } else {
+           die "unkown audio device '$audio->{dev}', implement me!";
+       }
+
+       push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+    }
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3814,7 +3962,7 @@ sub config_to_command {
        push @$machineFlags, "type=${machine_type}";
     }
 
-    if ($conf->{startdate}) {
+    if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) {
        push @$rtcFlags, "base=$conf->{startdate}";
     } elsif ($useLocaltime) {
        push @$rtcFlags, 'base=localtime';
@@ -3828,11 +3976,6 @@ sub config_to_command {
 
     push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
 
-    # enable sound
-    #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
-    #push @$cmd, '-soundhw', 'es1370';
-    #push @$cmd, '-soundhw', $soundhw if $soundhw;
-
     if (parse_guest_agent($conf)->{enabled}) {
        my $qgasocket = qmp_socket($vmid, 1);
        my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
@@ -3870,11 +4013,20 @@ sub config_to_command {
        my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
        $spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
 
-       push @$devices, '-spice', "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
+       my $spice_enhancement = PVE::JSONSchema::parse_property_string($spice_enhancements_fmt, $conf->{spice_enhancements} // '');
+       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 $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";
 
        push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
        push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
        push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
+
     }
 
     # enable balloon by default, unless explicitly disabled
@@ -5485,9 +5637,8 @@ sub vm_qmp_command {
     my $res;
 
     my $timeout;
-    if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) {
-       $timeout = $cmd->{arguments}->{timeout};
-       delete $cmd->{arguments}->{timeout};
+    if ($cmd->{arguments}) {
+       $timeout = delete $cmd->{arguments}->{timeout};
     }
 
     eval {
@@ -5512,8 +5663,6 @@ sub vm_qmp_command {
 sub vm_human_monitor_command {
     my ($vmid, $cmdline) = @_;
 
-    my $res;
-
     my $cmd = {
        execute => 'human-monitor-command',
        arguments => { 'command-line' => $cmdline},
@@ -5823,7 +5972,8 @@ sub vm_sendkey {
        my $conf = PVE::QemuConfig->load_config($vmid);
 
        # there is no qmp command, so we use the human monitor command
-       vm_human_monitor_command($vmid, "sendkey $key");
+       my $res = vm_human_monitor_command($vmid, "sendkey $key");
+       die $res if $res ne '';
     });
 }
 
@@ -7058,9 +7208,9 @@ sub qemu_machine_feature_enabled {
 }
 
 sub qemu_machine_pxe {
-    my ($vmid, $conf, $machine) = @_;
+    my ($vmid, $conf) = @_;
 
-    $machine =  PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine;
+    my $machine =  PVE::QemuServer::get_current_qemu_machine($vmid);
 
     if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
        $machine .= '.pxe';
@@ -7176,9 +7326,7 @@ sub add_hyperv_enlightenments {
        }
 
        if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
-           push @$cpuFlags , 'hv_tlbflush';
            push @$cpuFlags , 'hv_ipi';
-           push @$cpuFlags , 'hv_evmcs';
        }
     }
 }