X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=abed0c1d4ab76b1d08e4141828e0c4813a022e6a;hb=2796e7d542b4ca50b37d40fc4e76c8b23eee50bb;hp=f4f8a2cc0692115fc303365c8dc9b0fe7d7aea83;hpb=dd25eecf62703a5881cdecfa910a79da81e276d0;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index f4f8a2c..abed0c1 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -22,7 +22,7 @@ use PVE::SafeSyslog; use Storable qw(dclone); 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); +use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach); use PVE::JSONSchema qw(get_standard_option); use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file); use PVE::INotify; @@ -300,6 +300,13 @@ EODESC minimum => 1, default => 1, }, + maxcpus => { + optional => 1, + type => 'integer', + description => "Maximum cpus for hotplug.", + minimum => 1, + default => 1, + }, acpi => { optional => 1, type => 'boolean', @@ -430,6 +437,13 @@ EODESCR maxLength => 40, optional => 1, }, + smbios1 => { + description => "Specify SMBIOS type 1 fields.", + type => 'string', format => 'pve-qm-smbios1', + typetext => "[manufacturer=str][,product=str][,version=str][,serial=str] [,uuid=uuid][,sku=str][,family=str]", + maxLength => 256, + optional => 1, + }, }; # what about other qemu settings ? @@ -471,7 +485,7 @@ my $nic_model_list_txt = join(' ', sort @$nic_model_list); my $netdesc = { optional => 1, type => 'string', format => 'pve-qm-net', - typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=][,rate=][,tag=]", + typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=][,queues=][,rate=][,tag=][,firewall=0|1]", description => < 1, type => 'string', format => 'pve-qm-hostpci', - typetext => "HOSTPCIDEVICE", + typetext => "[host=]HOSTPCIDEVICE [,driver=kvm|vfio] [,rombar=on|off] [,pcie=0|1] [,x-vga=on|off]", description => <{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0; +} + +sub print_tabletdevice_full { + my ($conf) = @_; + + my $q35 = machine_type_is_q35($conf); + + # we use uhci for old VMs because tablet driver was buggy in older qemu + my $usbbus = $q35 ? "ehci" : "uhci"; + + return "usb-tablet,id=tablet,bus=$usbbus.0,port=1"; +} + sub print_drivedevice_full { my ($storecfg, $conf, $vmid, $drive, $bridges) = @_; @@ -1114,6 +1147,20 @@ sub print_drivedevice_full { return $device; } +sub get_initiator_name { + my $initiator; + + my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return undef; + while (defined(my $line = <$fh>)) { + next if $line !~ m/^\s*InitiatorName\s*=\s*([\.\-:\w]+)/; + $initiator = $1; + last; + } + $fh->close(); + + return $initiator; +} + sub print_drive_full { my ($storecfg, $vmid, $drive) = @_; @@ -1165,6 +1212,11 @@ sub print_netdevice_full { my $extra = ($bootorder !~ m/n/) ? "romfile=," : ''; my $pciaddr = print_pci_addr("$netid", $bridges); my $tmpstr = "$device,${extra}mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid"; + if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio'){ + #Consider we have N queues, the number of vectors needed is 2*N + 2 (plus one config interrupt and control vq) + my $vectors = $net->{queues} * 2 + 2; + $tmpstr .= ",vectors=$vectors,mq=on"; + } $tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ; return $tmpstr; } @@ -1190,11 +1242,17 @@ sub print_netdev_full { my $vmname = $conf->{name} || "vm$vmid"; + my $netdev = ""; + if ($net->{bridge}) { - return "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge$vhostparam"; + $netdev = "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown$vhostparam"; } else { - return "type=user,id=$netid,hostname=$vmname"; + $netdev = "type=user,id=$netid,hostname=$vmname"; } + + $netdev .= ",queues=$net->{queues}" if ($net->{queues} && $net->{model} eq 'virtio'); + + return $netdev; } sub drive_is_cdrom { @@ -1209,14 +1267,37 @@ sub parse_hostpci { return undef if !$value; + + my @list = split(/,/, $value); + my $found; + my $res = {}; + foreach my $kv (@list) { - if ($value =~ m/^[a-f0-9]{2}:[a-f0-9]{2}\.[a-f0-9]$/) { - $res->{pciid} = $value; - } else { - return undef; + if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2})(\.([a-f0-9]))?$/) { + $found = 1; + if(defined($4)){ + push @{$res->{pciid}}, { id => $2 , function => $4}; + + }else{ + my $pcidevices = lspci($2); + $res->{pciid} = $pcidevices->{$2}; + } + } elsif ($kv =~ m/^driver=(kvm|vfio)$/) { + $res->{driver} = $1; + } elsif ($kv =~ m/^rombar=(on|off)$/) { + $res->{rombar} = $1; + } elsif ($kv =~ m/^x-vga=(on|off)$/) { + $res->{'x-vga'} = $1; + } elsif ($kv =~ m/^pcie=(\d+)$/) { + $res->{pcie} = 1 if $1 == 1; + } else { + warn "unknown hostpci setting '$kv'\n"; + } } + return undef if !$found; + return $res; } @@ -1235,10 +1316,14 @@ sub parse_net { $res->{macaddr} = $mac; } elsif ($kvp =~ m/^bridge=(\S+)$/) { $res->{bridge} = $1; + } elsif ($kvp =~ m/^queues=(\d+)$/) { + $res->{queues} = $1; } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) { $res->{rate} = $1; } elsif ($kvp =~ m/^tag=(\d+)$/) { $res->{tag} = $1; + } elsif ($kvp =~ m/^firewall=(\d+)$/) { + $res->{firewall} = $1; } else { return undef; } @@ -1258,6 +1343,7 @@ sub print_net { $res .= ",bridge=$net->{bridge}" if $net->{bridge}; $res .= ",rate=$net->{rate}" if $net->{rate}; $res .= ",tag=$net->{tag}" if $net->{tag}; + $res .= ",firewall=$net->{firewall}" if $net->{firewall}; return $res; } @@ -1293,6 +1379,45 @@ sub add_unused_volume { return $key; } +my $valid_smbios1_options = { + manufacturer => '\S+', + product => '\S+', + version => '\S+', + serial => '\S+', + uuid => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}', + sku => '\S+', + family => '\S+', +}; + +# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str] +sub parse_smbios1 { + my ($data) = @_; + + my $res = {}; + + foreach my $kvp (split(/,/, $data)) { + return undef if $kvp !~ m/^(\S+)=(.+)$/; + my ($k, $v) = split(/=/, $kvp); + return undef if !defined($k) || !defined($v); + return undef if !$valid_smbios1_options->{$k}; + return undef if $v !~ m/^$valid_smbios1_options->{$k}$/; + $res->{$k} = $v; + } + + return $res; +} + +PVE::JSONSchema::register_format('pve-qm-smbios1', \&verify_smbios1); +sub verify_smbios1 { + my ($value, $noerr) = @_; + + return $value if parse_smbios1($value); + + return undef if $noerr; + + die "unable to parse smbios (type 1) options\n"; +} + PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk); sub verify_bootdisk { my ($value, $noerr) = @_; @@ -1724,6 +1849,10 @@ sub write_vm_config { delete $conf->{smp}; } + if ($conf->{maxcpus} && $conf->{sockets}) { + delete $conf->{sockets}; + } + my $used_volids = {}; my $cleanup_config = sub { @@ -2273,6 +2402,8 @@ sub config_to_command { my $have_ovz = -f '/proc/vz/vestat'; + my $q35 = machine_type_is_q35($conf); + push @$cmd, '/usr/bin/kvm'; push @$cmd, '-id', $vmid; @@ -2290,16 +2421,26 @@ sub config_to_command { push @$cmd, '-daemonize'; - $pciaddr = print_pci_addr("piix3", $bridges); - push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2"; + if ($conf->{smbios1}) { + push @$cmd, '-smbios', "type=1,$conf->{smbios1}"; + } - my $use_usb2 = 0; - for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) { - next if !$conf->{"usb$i"}; - $use_usb2 = 1; + if ($q35) { + # the q35 chipset support native usb2, so we enable usb controller + # by default for this machine type + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg'; + } else { + $pciaddr = print_pci_addr("piix3", $bridges); + push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2"; + + my $use_usb2 = 0; + for (my $i = 0; $i < $MAX_USB_DEVICES; $i++) { + next if !$conf->{"usb$i"}; + $use_usb2 = 1; + } + # include usb device config + push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2; } - # include usb device config - push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2; my $vga = $conf->{vga}; @@ -2307,8 +2448,8 @@ sub config_to_command { $vga = 'qxl' if $qxlnum; if (!$vga) { - if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || - $conf->{ostype} eq 'win7' || + if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || + $conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) { $vga = 'std'; } else { @@ -2326,14 +2467,45 @@ sub config_to_command { $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card) } - push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet; + push @$devices, '-device', print_tabletdevice_full($conf) if $tablet; # host pci devices for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { - my $d = parse_hostpci($conf->{"hostpci$i"}); - next if !$d; - $pciaddr = print_pci_addr("hostpci$i", $bridges); - push @$devices, '-device', "pci-assign,host=$d->{pciid},id=hostpci$i$pciaddr"; + my $d = parse_hostpci($conf->{"hostpci$i"}); + next if !$d; + + my $pcie = $d->{pcie}; + if($pcie){ + die "q35 machine model is not enabled" if !$q35; + $pciaddr = print_pcie_addr("hostpci$i"); + }else{ + $pciaddr = print_pci_addr("hostpci$i", $bridges); + } + + my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : ""; + my $driver = $d->{driver} && $d->{driver} eq 'vfio' ? "vfio-pci" : "pci-assign"; + my $xvga = $d->{'x-vga'} && $d->{'x-vga'} eq 'on' ? ",x-vga=on" : ""; + $driver = "vfio-pci" if $xvga ne ''; + my $pcidevices = $d->{pciid}; + my $multifunction = 1 if @$pcidevices > 1; + + my $j=0; + foreach my $pcidevice (@$pcidevices) { + + my $id = "hostpci$i"; + $id .= ".$j" if $multifunction; + my $addr = $pciaddr; + $addr .= ".$j" if $multifunction; + my $devicestr = "$driver,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr"; + + if($j == 0){ + $devicestr .= "$rombar$xvga"; + $devicestr .= ",multifunction=on" if $multifunction; + } + + push @$devices, '-device', $devicestr; + $j++; + } } # usb devices @@ -2385,7 +2557,13 @@ sub config_to_command { $sockets = $conf->{sockets} if $conf->{sockets}; my $cores = $conf->{cores} || 1; - push @$cmd, '-smp', "sockets=$sockets,cores=$cores"; + my $maxcpus = $conf->{maxcpus} if $conf->{maxcpus}; + + if ($maxcpus) { + push @$cmd, '-smp', "cpus=$cores,maxcpus=$maxcpus"; + } else { + push @$cmd, '-smp', "sockets=$sockets,cores=$cores"; + } push @$cmd, '-nodefaults'; @@ -2469,6 +2647,8 @@ sub config_to_command { $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags); + # Note: enforce needs kernel 3.10, so we do not use it for now + # push @$cmd, '-cpu', "$cpu,enforce"; push @$cmd, '-cpu', $cpu; push @$cmd, '-S' if $conf->{freeze}; @@ -2536,6 +2716,11 @@ sub config_to_command { my $ahcicontroller = {}; my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : $defaults->{scsihw}; + # Add iscsi initiator name if available + if (my $initiator = get_initiator_name()) { + push @$devices, '-iscsi', "initiator-name=$initiator"; + } + foreach_drive($conf, sub { my ($ds, $drive) = @_; @@ -2573,8 +2758,9 @@ sub config_to_command { $ahcicontroller->{$controller}=1; } - push @$devices, '-drive',print_drive_full($storecfg, $vmid, $drive); - push @$devices, '-device',print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges); + my $drive_cmd = print_drive_full($storecfg, $vmid, $drive); + push @$devices, '-drive',$drive_cmd; + push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges); }); push @$cmd, '-m', $conf->{memory} || $defaults->{memory}; @@ -2598,13 +2784,14 @@ sub config_to_command { push @$devices, '-device', $netdevicefull; } - #bridges - while (my ($k, $v) = each %$bridges) { - $pciaddr = print_pci_addr("pci.$k"); - unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0; + if (!$q35) { + # add pci bridges + while (my ($k, $v) = each %$bridges) { + $pciaddr = print_pci_addr("pci.$k"); + unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0; + } } - # hack: virtio with fairsched is unreliable, so we do not use fairsched # when the VM uses virtio devices. if (!$use_virtio && $have_ovz) { @@ -2684,9 +2871,10 @@ sub vm_deviceplug { return 1 if !check_running($vmid); + my $q35 = machine_type_is_q35($conf); + if ($deviceid eq 'tablet') { - my $devicefull = "usb-tablet,id=tablet,bus=uhci.0,port=1"; - qemu_deviceadd($vmid, $devicefull); + qemu_deviceadd($vmid, print_tabletdevice_full($conf)); return 1; } @@ -2736,7 +2924,8 @@ sub vm_deviceplug { } } - if ($deviceid =~ m/^(pci\.)(\d+)$/) { + + if (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) { my $bridgeid = $2; my $pciaddr = print_pci_addr($deviceid); my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr"; @@ -2909,6 +3098,29 @@ sub qemu_netdevdel { return 1; } +sub qemu_cpu_hotplug { + my ($vmid, $conf, $cores) = @_; + + die "new cores config is not defined" if !$cores; + die "you can't add more cores than maxcpus" + if $conf->{maxcpus} && ($cores > $conf->{maxcpus}); + return if !check_running($vmid); + + my $currentcores = $conf->{cores} if $conf->{cores}; + die "current cores is not defined" if !$currentcores; + die "maxcpus is not defined" if !$conf->{maxcpus}; + raise_param_exc({ 'cores' => "online cpu unplug is not yet possible" }) + if($cores < $currentcores); + + my $currentrunningcores = vm_mon_cmd($vmid, "query-cpus"); + raise_param_exc({ 'cores' => "cores number if running vm is different than configuration" }) + if scalar (@{$currentrunningcores}) != $currentcores; + + for(my $i = $currentcores; $i < $cores; $i++) { + vm_mon_cmd($vmid, "cpu-add", id => int($i)); + } +} + sub qemu_block_set_io_throttle { my ($vmid, $deviceid, $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr) = @_; @@ -3076,6 +3288,30 @@ sub qga_unfreezefs { #need to impplement call to qemu-ga } +sub set_migration_caps { + my ($vmid) = @_; + + my $cap_ref = []; + + my $enabled_cap = { + "auto-converge" => 1, + "xbzrle" => 0, + "x-rdma-pin-all" => 0, + "zero-blocks" => 0, + }; + + my $supported_capabilities = vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities"); + + for my $supported_capability (@$supported_capabilities) { + push @$cap_ref, { + capability => $supported_capability->{capability}, + state => $enabled_cap->{$supported_capability->{capability}} ? JSON::true : JSON::false, + }; + } + + vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref); +} + sub vm_start { my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_; @@ -3120,11 +3356,22 @@ sub vm_start { for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) { my $d = parse_hostpci($conf->{"hostpci$i"}); next if !$d; - my $info = pci_device_info("0000:$d->{pciid}"); - die "IOMMU not present\n" if !check_iommu_support(); - die "no pci device info for device '$d->{pciid}'\n" if !$info; - die "can't unbind pci device '$d->{pciid}'\n" if !pci_dev_bind_to_stub($info); - die "can't reset pci device '$d->{pciid}'\n" if !pci_dev_reset($info); + my $pcidevices = $d->{pciid}; + foreach my $pcidevice (@$pcidevices) { + my $pciid = $pcidevice->{id}.".".$pcidevice->{function}; + + my $info = pci_device_info("0000:$pciid"); + die "IOMMU not present\n" if !check_iommu_support(); + die "no pci device info for device '$pciid'\n" if !$info; + + if ($d->{driver} && $d->{driver} eq "vfio") { + die "can't unbind/bind pci group to vfio '$pciid'\n" if !pci_dev_group_bind_to_vfio($pciid); + } else { + die "can't unbind/bind to stub pci device '$pciid'\n" if !pci_dev_bind_to_stub($info); + } + + die "can't reset pci device '$pciid'\n" if !pci_dev_reset($info); + } } PVE::Storage::activate_volumes($storecfg, $vollist); @@ -3142,12 +3389,12 @@ sub vm_start { } if ($migratedfrom) { - my $capabilities = {}; - $capabilities->{capability} = "xbzrle"; - $capabilities->{state} = JSON::true; - eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); }; + + eval { + PVE::QemuServer::set_migration_caps($vmid); + }; warn $@ if $@; - + if ($spice_port) { print "spice listens on port $spice_port\n"; if ($spice_ticket) { @@ -3518,6 +3765,61 @@ sub pci_dev_bind_to_stub { return -d $testdir; } +sub pci_dev_bind_to_vfio { + my ($dev) = @_; + + my $name = $dev->{name}; + + my $vfio_basedir = "$pcisysfs/drivers/vfio-pci"; + + if (!-d $vfio_basedir) { + system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null"); + } + die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir; + + my $testdir = "$vfio_basedir/$name"; + return 1 if -d $testdir; + + my $data = "$dev->{vendor} $dev->{product}"; + return undef if !file_write("$vfio_basedir/new_id", $data); + + my $fn = "$pcisysfs/devices/$name/driver/unbind"; + if (!file_write($fn, $name)) { + return undef if -f $fn; + } + + $fn = "$vfio_basedir/bind"; + if (! -d $testdir) { + return undef if !file_write($fn, $name); + } + + return -d $testdir; +} + +sub pci_dev_group_bind_to_vfio { + my ($pciid) = @_; + + my $vfio_basedir = "$pcisysfs/drivers/vfio-pci"; + + if (!-d $vfio_basedir) { + system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null"); + } + die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir; + + # get IOMMU group devices + opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n"; + my @devs = grep /^0000:/, readdir($D); + closedir($D); + + foreach my $pciid (@devs) { + $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n"; + my $info = pci_device_info($1); + pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n"; + } + + return 1; +} + sub print_pci_addr { my ($id, $bridges) = @_; @@ -3600,6 +3902,26 @@ sub print_pci_addr { } +sub print_pcie_addr { + my ($id) = @_; + + my $res = ''; + my $devices = { + hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 }, + hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 }, + hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 }, + hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 }, + }; + + if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) { + my $addr = sprintf("0x%x", $devices->{$id}->{addr}); + my $bus = $devices->{$id}->{bus}; + $res = ",bus=$bus,addr=$addr"; + } + return $res; + +} + # vzdump restore implementaion sub tar_archive_read_firstfile { @@ -4073,6 +4395,10 @@ sub restore_vma_archive { my ($dev_id, $size, $devname) = ($1, $2, $3); $devinfo->{$devname} = { size => $size, dev_id => $dev_id }; } elsif ($line =~ m/^CTIME: /) { + # we correctly received the vma config, so we can disable + # the timeout now for disk allocation (set to 10 minutes, so + # that we always timeout if something goes wrong) + alarm(600); &$print_devmap(); print $fifofh "done\n"; my $tmp = $oldtimeout || 0; @@ -4919,4 +5245,17 @@ sub get_current_qemu_machine { return $current || $default || 'pc'; } +sub lspci { + + my $devices = {}; + + dir_glob_foreach("$pcisysfs/devices", '[a-f0-9]{4}:([a-f0-9]{2}:[a-f0-9]{2})\.([0-9])', sub { + my (undef, $id, $function) = @_; + my $res = { id => $id, function => $function}; + push @{$devices->{$id}}, $res; + }); + + return $devices; +} + 1;