package PVE::QemuServer;
use strict;
+use warnings;
use POSIX;
use IO::Handle;
use IO::Select;
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;
optional => 1,
type => 'string',
description => "scsi controller model",
- enum => [qw(lsi virtio-scsi-pci megasas)],
+ enum => [qw(lsi lsi53c810 virtio-scsi-pci megasas pvscsi)],
default => 'lsi',
},
description => {
ostype => {
optional => 1,
type => 'string',
- enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26)],
+ enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
description => <<EODESC,
Used to enable special optimization/features for specific
operating systems:
win8 => Microsoft Windows 8/2012
l24 => Linux 2.4 Kernel
l26 => Linux 2.6/3.X Kernel
+solaris => solaris/opensolaris/openindiania kernel
-other|l24|l26 ... no special behaviour
+other|l24|l26|solaris ... no special behaviour
wxp|w2k|w2k3|w2k8|wvista|win7|win8 ... use --localtime switch
EODESC
},
minimum => 1,
default => 1,
},
+ maxcpus => {
+ optional => 1,
+ type => 'integer',
+ description => "Maximum cpus for hotplug.",
+ minimum => 1,
+ default => 1,
+ },
acpi => {
optional => 1,
type => 'boolean',
vga => {
optional => 1,
type => 'string',
- description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types",
- enum => [qw(std cirrus vmware)],
+ description => "Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and 'cirrur' for other OS types. Option 'qxl' enables the SPICE display sever. You can also run without any graphic card using a serial devive as terminal.",
+ enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
},
watchdog => {
optional => 1,
optional => 1,
type => 'boolean',
default => 1,
- description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning. Else the mouse runs out of sync with normal vnc clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches.",
+ description => "Enable/disable the usb tablet device. This device is usually needed to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with normal VNC clients. If you're running lots of console-only guests on one host, you may consider disabling this to save some context switches. This is turned of by default if you use spice (vga=qxl).",
},
migrate_speed => {
optional => 1,
type => 'string', format => 'pve-volume-id',
description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
},
+ machine => {
+ description => "Specific the Qemu machine type.",
+ type => 'string',
+ pattern => '(pc|pc(-i440fx)?-\d+\.\d+|q35|pc-q35-\d+\.\d+)',
+ 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 ?
my $MAX_PARALLEL_PORTS = 3;
my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000', 'pcnet', 'virtio',
- 'ne2k_isa', 'i82551', 'i82557b', 'i82559er'];
+ 'ne2k_isa', 'i82551', 'i82557b', 'i82559er', 'vmxnet3'];
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=<dev>][,rate=<mbps>][,tag=<vlanid>]",
+ typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,queues=<nbqueues>][,rate=<mbps>][,tag=<vlanid>][,firewall=0|1]",
description => <<EODESCR,
Specify network devices.
my $idedesc = {
optional => 1,
type => 'string', format => 'pve-qm-drive',
- typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
+ typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads] [,discard=ignore|on]',
description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS -1) . ").",
};
PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
my $scsidesc = {
optional => 1,
type => 'string', format => 'pve-qm-drive',
- typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
+ typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads] [,discard=ignore|on]',
description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
};
PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
my $satadesc = {
optional => 1,
type => 'string', format => 'pve-qm-drive',
- typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
+ typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads] [,discard=ignore|on]',
description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
};
PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
my $virtiodesc = {
optional => 1,
type => 'string', format => 'pve-qm-drive',
- typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
+ typetext => '[volume=]volume,] [,media=cdrom|disk] [,cyls=c,heads=h,secs=s[,trans=t]] [,snapshot=on|off] [,cache=none|writethrough|writeback|unsafe|directsync] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads] [,discard=ignore|on]',
description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
};
PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
my $usbdesc = {
optional => 1,
type => 'string', format => 'pve-qm-usb-device',
- typetext => 'host=HOSTUSBDEVICE',
+ typetext => 'host=HOSTUSBDEVICE|spice',
description => <<EODESCR,
Configure an USB device (n is 0 to 4). This can be used to
pass-through usb devices to the guest. HOSTUSBDEVICE syntax is:
Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+The value 'spice' can be used to add a usb redirection devices for spice.
+
EODESCR
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
my $hostpcidesc = {
optional => 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 => <<EODESCR,
Map host pci devices. HOSTPCIDEVICE syntax is:
You can us the 'lspci' command to list existing pci devices.
+The 'rombar' option determines whether or not the device's ROM will be visible in the guest's memory map (default is 'on').
+
Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
Experimental: user reported problems with this option.
my $serialdesc = {
optional => 1,
type => 'string',
- pattern => '/dev/ttyS\d+',
+ pattern => '(/dev/ttyS\d+|socket)',
description => <<EODESCR,
-Map host serial devices (n is 0 to 3).
+Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device, or create a unix socket on the host side (use 'qm terminal' to open a terminal connection).
Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
my $paralleldesc= {
optional => 1,
type => 'string',
- pattern => '/dev/parport\d+',
+ pattern => '/dev/parport\d+|/dev/usb/lp\d+',
description => <<EODESCR,
Map host parallel devices (n is 0 to 2).
# ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
# [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
# [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
-# [,aio=native|threads]
+# [,aio=native|threads][,discard=ignore|on]
sub parse_drive {
my ($key, $data) = @_;
foreach my $p (split (/,/, $data)) {
next if $p =~ m/^\s*$/;
- if ($p =~ m/^(file|volume|cyls|heads|secs|trans|media|snapshot|cache|format|rerror|werror|backup|aio|bps|mbps|bps_rd|mbps_rd|bps_wr|mbps_wr|iops|iops_rd|iops_wr|size)=(.+)$/) {
+ if ($p =~ m/^(file|volume|cyls|heads|secs|trans|media|snapshot|cache|format|rerror|werror|backup|aio|bps|mbps|mbps_max|bps_rd|mbps_rd|mbps_rd_max|bps_wr|mbps_wr|mbps_wr_max|iops|iops_max|iops_rd|iops_rd_max|iops_wr|iops_wr_max|size|discard)=(.+)$/) {
my ($k, $v) = ($1, $2);
$k = 'file' if $k eq 'volume';
return undef if $res->{werror} && $res->{werror} !~ m/^(enospc|ignore|report|stop)$/;
return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
return undef if $res->{aio} && $res->{aio} !~ m/^(native|threads)$/;
+ return undef if $res->{discard} && $res->{discard} !~ m/^(ignore|on)$/;
-
return undef if $res->{mbps_rd} && $res->{mbps};
return undef if $res->{mbps_wr} && $res->{mbps};
return undef if $res->{mbps} && $res->{mbps} !~ m/^\d+(\.\d+)?$/;
+ return undef if $res->{mbps_max} && $res->{mbps_max} !~ m/^\d+(\.\d+)?$/;
return undef if $res->{mbps_rd} && $res->{mbps_rd} !~ m/^\d+(\.\d+)?$/;
+ return undef if $res->{mbps_rd_max} && $res->{mbps_rd_max} !~ m/^\d+(\.\d+)?$/;
return undef if $res->{mbps_wr} && $res->{mbps_wr} !~ m/^\d+(\.\d+)?$/;
+ return undef if $res->{mbps_wr_max} && $res->{mbps_wr_max} !~ m/^\d+(\.\d+)?$/;
return undef if $res->{iops_rd} && $res->{iops};
return undef if $res->{iops_wr} && $res->{iops};
+
+
return undef if $res->{iops} && $res->{iops} !~ m/^\d+$/;
+ return undef if $res->{iops_max} && $res->{iops_max} !~ m/^\d+$/;
return undef if $res->{iops_rd} && $res->{iops_rd} !~ m/^\d+$/;
+ return undef if $res->{iops_rd_max} && $res->{iops_rd_max} !~ m/^\d+$/;
return undef if $res->{iops_wr} && $res->{iops_wr} !~ m/^\d+$/;
+ return undef if $res->{iops_wr_max} && $res->{iops_wr_max} !~ m/^\d+$/;
if ($res->{size}) {
- return undef if !defined($res->{size} = &$parse_size($res->{size}));
+ return undef if !defined($res->{size} = &$parse_size($res->{size}));
}
if ($res->{media} && ($res->{media} eq 'cdrom')) {
return $res;
}
-my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio iops iops_rd iops_wr);
+my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard iops iops_rd iops_wr iops_max iops_rd_max iops_wr_max);
sub print_drive {
my ($vmid, $drive) = @_;
my $opts = '';
- foreach my $o (@qemu_drive_options, 'mbps', 'mbps_rd', 'mbps_wr', 'backup') {
+ foreach my $o (@qemu_drive_options, 'mbps', 'mbps_rd', 'mbps_wr', 'mbps_max', 'mbps_rd_max', 'mbps_wr_max', 'backup') {
$opts .= ",$o=$drive->{$o}" if $drive->{$o};
}
my $buf = "\x00" x 36;
my $sensebuf = "\x00" x 8;
- my $cmd = pack("C x3 C x11", 0x12, 36);
+ 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 $res = {};
- ($res->{device}, $res->{removable}, $res->{venodor},
+ (my $byte0, my $byte1, $res->{vendor},
$res->{product}, $res->{revision}) = unpack("C C x6 A8 A16 A4", $buf);
+ $res->{removable} = $byte1 & 128 ? 1 : 0;
+ $res->{type} = $byte0 & 31;
+
return $res;
}
return $res;
}
+sub machine_type_is_q35 {
+ my ($conf) = @_;
+
+ return $conf->{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) = @_;
my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}", $bridges);
$device = "virtio-blk-pci,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}$pciaddr";
} elsif ($drive->{interface} eq 'scsi') {
- $maxdev = ($conf->{scsihw} && $conf->{scsihw} ne 'lsi') ? 256 : 7;
+ $maxdev = ($conf->{scsihw} && ($conf->{scsihw} !~ m/^lsi/)) ? 256 : 7;
my $controller = int($drive->{index} / $maxdev);
my $unit = $drive->{index} % $maxdev;
my $devicetype = 'hd';
my $path = '';
if (drive_is_cdrom($drive)) {
$devicetype = 'cd';
- } else {
+ } else {
if ($drive->{file} =~ m|^/|) {
$path = $drive->{file};
} else {
}
if($path =~ m/^iscsi\:\/\//){
- $devicetype = 'generic';
- }
- else {
- $devicetype = 'block' if path_is_scsi($path);
+ $devicetype = 'generic';
+ } else {
+ if (my $info = path_is_scsi($path)) {
+ if ($info->{type} == 0) {
+ $devicetype = 'block';
+ } elsif ($info->{type} == 1) { # tape
+ $devicetype = 'generic';
+ }
+ }
}
}
- if (!$conf->{scsihw} || $conf->{scsihw} eq 'lsi'){
- $device = "scsi-$devicetype,bus=scsihw$controller.0,scsi-id=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}" if !$conf->{scsihw} || $conf->{scsihw} eq 'lsi';
+ if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)){
+ $device = "scsi-$devicetype,bus=scsihw$controller.0,scsi-id=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
} else {
$device = "scsi-$devicetype,bus=scsihw$controller.0,channel=0,scsi-id=0,lun=$drive->{index},drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
}
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) = @_;
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;
}
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 {
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;
}
foreach my $kvp (split(/,/, $data)) {
- if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
+ if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er|vmxnet3)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
my $model = lc($1);
my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr();
$res->{model} = $model;
$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;
}
$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;
}
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) = @_;
$found = 1;
$res->{hostbus} = $1;
$res->{hostport} = $2;
+ } elsif ($v =~ m/^spice$/) {
+ $found = 1;
+ $res->{spice} = 1;
} else {
return undef;
}
my @lines = split(/\n/, $raw);
foreach my $line (@lines) {
next if $line =~ m/^\s*$/;
-
+
if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
my $snapname = $1;
$conf->{description} = $descr if $descr;
$descr = '';
- $conf = $res->{snapshots}->{$snapname} = {};
+ $conf = $res->{snapshots}->{$snapname} = {};
next;
}
delete $conf->{smp};
}
+ if ($conf->{maxcpus} && $conf->{sockets}) {
+ delete $conf->{sockets};
+ }
+
my $used_volids = {};
my $cleanup_config = sub {
- my ($cref) = @_;
+ my ($cref, $snapname) = @_;
foreach my $key (keys %$cref) {
next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots' ||
$cref->{$key} = $value;
- if (valid_drivename($key)) {
+ if (!$snapname && valid_drivename($key)) {
my $drive = parse_drive($key, $value);
$used_volids->{$drive->{file}} = 1 if $drive && $drive->{file};
}
&$cleanup_config($conf);
foreach my $snapname (keys %{$conf->{snapshots}}) {
- &$cleanup_config($conf->{snapshots}->{$snapname});
+ &$cleanup_config($conf->{snapshots}->{$snapname}, $snapname);
}
# remove 'unusedX' settings if we re-add a volume
delete $conf->{$key};
}
}
-
+
my $generate_raw_config = sub {
my ($conf) = @_;
$loc_res = 1 if $conf->{hostpci}; # old syntax
foreach my $k (keys %$conf) {
+ next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
$loc_res = 1 if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
}
return $loc_res;
}
-# check is used storages are available on all nodes (use by migrate)
+# check if used storages are available on all nodes (use by migrate)
sub check_storage_availability {
my ($storecfg, $conf, $node) = @_;
});
}
+# list nodes where all VM images are available (used by has_feature API)
+sub shared_nodes {
+ my ($conf, $storecfg) = @_;
+
+ my $nodelist = PVE::Cluster::get_nodelist();
+ my $nodehash = { map { $_ => 1 } @$nodelist };
+ my $nodename = PVE::INotify::nodename();
+
+ 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}) {
+ $nodehash = {};
+ } elsif (my $avail = $scfg->{nodes}) {
+ foreach my $node (keys %$nodehash) {
+ delete $nodehash->{$node} if !$avail->{$node};
+ }
+ } elsif (!$scfg->{shared}) {
+ foreach my $node (keys %$nodehash) {
+ delete $nodehash->{$node} if $node ne $nodename
+ }
+ }
+ }
+ });
+
+ return $nodehash
+}
+
sub check_lock {
my ($conf) = @_;
my $info = $resp->{'return'};
return if !$info->{max_mem};
-
+
my $d = $res->{$vmid};
# use memory assigned to VM
$d->{maxmem} = $info->{max_mem};
$d->{balloon} = $info->{actual};
-
+
if (defined($info->{total_mem}) && defined($info->{free_mem})) {
$d->{mem} = $info->{total_mem} - $info->{free_mem};
$d->{freemem} = $info->{free_mem};
sub foreach_volid {
my ($conf, $func) = @_;
-
+
my $volhash = {};
my $test_volid = sub {
my ($volid, $is_cdrom) = @_;
return if !$volid;
-
+
$volhash->{$volid} = $is_cdrom || 0;
};
}
foreach my $volid (keys %$volhash) {
- &$func($volid, $volhash->{$volid});
+ &$func($volid, $volhash->{$volid});
}
}
+sub vga_conf_has_spice {
+ my ($vga) = @_;
+
+ return 0 if !$vga || $vga !~ m/^qxl([234])?$/;
+
+ return $1 || 1;
+}
+
sub config_to_command {
- my ($storecfg, $vmid, $conf, $defaults) = @_;
+ my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
my $cmd = [];
my $globalFlags = [];
my $machineFlags = [];
my $rtcFlags = [];
+ my $cpuFlags = [];
my $devices = [];
my $pciaddr = '';
my $bridges = {};
my $have_ovz = -f '/proc/vz/vestat';
+ my $q35 = machine_type_is_q35($conf);
+
push @$cmd, '/usr/bin/kvm';
push @$cmd, '-id', $vmid;
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;
+ }
+
+ my $vga = $conf->{vga};
+
+ my $qxlnum = vga_conf_has_spice($vga);
+ $vga = 'qxl' if $qxlnum;
+
+ if (!$vga) {
+ if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
+ $conf->{ostype} eq 'win7' ||
+ $conf->{ostype} eq 'w2k8')) {
+ $vga = 'std';
+ } else {
+ $vga = 'cirrus';
+ }
}
- # include usb device config
- push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
# enable absolute mouse coordinates (needed by vnc)
- my $tablet = defined($conf->{tablet}) ? $conf->{tablet} : $defaults->{tablet};
- push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet;
+ my $tablet;
+ if (defined($conf->{tablet})) {
+ $tablet = $conf->{tablet};
+ } else {
+ $tablet = $defaults->{tablet};
+ $tablet = 0 if $qxlnum; # disable for spice because it is not needed
+ $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
+ }
+
+ 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
push @$devices, '-device', "usb-host,vendorid=0x$d->{vendorid},productid=0x$d->{productid}";
} elsif (defined($d->{hostbus}) && defined($d->{hostport})) {
push @$devices, '-device', "usb-host,hostbus=$d->{hostbus},hostport=$d->{hostport}";
+ } elsif ($d->{spice}) {
+ # usb redir support for spice
+ push @$devices, '-chardev', "spicevmc,id=usbredirchardev$i,name=usbredir";
+ push @$devices, '-device', "usb-redir,chardev=usbredirchardev$i,id=usbredirdev$i,bus=ehci.0";
}
}
# serial devices
for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {
if (my $path = $conf->{"serial$i"}) {
- die "no such serial device\n" if ! -c $path;
- push @$devices, '-chardev', "tty,id=serial$i,path=$path";
- push @$devices, '-device', "isa-serial,chardev=serial$i";
+ if ($path eq 'socket') {
+ my $socket = "/var/run/qemu-server/${vmid}.serial$i";
+ push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server,nowait";
+ push @$devices, '-device', "isa-serial,chardev=serial$i";
+ } else {
+ die "no such serial device\n" if ! -c $path;
+ push @$devices, '-chardev', "tty,id=serial$i,path=$path";
+ push @$devices, '-device', "isa-serial,chardev=serial$i";
+ }
}
}
for (my $i = 0; $i < $MAX_PARALLEL_PORTS; $i++) {
if (my $path = $conf->{"parallel$i"}) {
die "no such parallel device\n" if ! -c $path;
- push @$devices, '-chardev', "parport,id=parallel$i,path=$path";
+ my $devtype = $path =~ m!^/dev/usb/lp! ? 'tty' : 'parport';
+ push @$devices, '-chardev', "$devtype,id=parallel$i,path=$path";
push @$devices, '-device', "isa-parallel,chardev=parallel$i";
}
}
$sockets = $conf->{sockets} if $conf->{sockets};
my $cores = $conf->{cores} || 1;
+ my $maxcpus = $conf->{maxcpus} if $conf->{maxcpus};
- push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
-
- push @$cmd, '-cpu', $conf->{cpu} if $conf->{cpu};
+ if ($maxcpus) {
+ push @$cmd, '-smp', "cpus=$cores,maxcpus=$maxcpus";
+ } else {
+ push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
+ }
push @$cmd, '-nodefaults';
push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
- my $vga = $conf->{vga};
- if (!$vga) {
- if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || $conf->{ostype} eq 'win7' || $conf->{ostype} eq 'w2k8')) {
- $vga = 'std';
- } else {
- $vga = 'cirrus';
- }
- }
-
- push @$cmd, '-vga', $vga if $vga; # for kvm 77 and later
+ push @$cmd, '-vga', $vga if $vga && $vga !~ m/^serial\d+$/; # for kvm 77 and later
# time drift fix
my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
my $useLocaltime = $conf->{localtime};
if (my $ost = $conf->{ostype}) {
- # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26
+ # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26, solaris
if ($ost =~ m/^w/) { # windows
$useLocaltime = 1 if !defined($conf->{localtime});
}
}
- if ($ost eq 'win7' || $ost eq 'win8' || $ost eq 'w2k8' ||
+ if ($ost eq 'win7' || $ost eq 'win8' || $ost eq 'w2k8' ||
$ost eq 'wvista') {
push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
push @$cmd, '-no-hpet';
+ #push @$cpuFlags , 'hv_vapic" if !$nokvm; #fixme, my win2008R2 hang at boot with this
+ push @$cpuFlags , 'hv_spinlocks=0xffff' if !$nokvm;
+ }
+
+ if ($ost eq 'win7' || $ost eq 'win8') {
+ push @$cpuFlags , 'hv_relaxed' if !$nokvm;
}
}
die "No accelerator found!\n" if !$cpuinfo->{hvm};
}
+ my $machine_type = $forcemachine || $conf->{machine};
+ if ($machine_type) {
+ push @$machineFlags, "type=${machine_type}";
+ }
+
if ($conf->{startdate}) {
push @$rtcFlags, "base=$conf->{startdate}";
} elsif ($useLocaltime) {
push @$rtcFlags, 'base=localtime';
}
+ my $cpu = $nokvm ? "qemu64" : "kvm64";
+ $cpu = $conf->{cpu} if $conf->{cpu};
+
+ push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64';
+
+ push @$cpuFlags , '+x2apic' if !$nokvm && $conf->{ostype} ne 'solaris';
+
+ push @$cpuFlags , '-x2apic' if $conf->{ostype} eq 'solaris';
+
+ push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+
+ $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};
# set keyboard layout
push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
}
+ my $spice_port;
+
+ if ($qxlnum) {
+ if ($qxlnum > 1) {
+ if ($conf->{ostype} && $conf->{ostype} =~ m/^w/){
+ for(my $i = 1; $i < $qxlnum; $i++){
+ my $pciaddr = print_pci_addr("vga$i", $bridges);
+ push @$cmd, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
+ }
+ } else {
+ # assume other OS works like Linux
+ push @$cmd, '-global', 'qxl-vga.ram_size=134217728';
+ push @$cmd, '-global', 'qxl-vga.vram_size=67108864';
+ }
+ }
+
+ my $pciaddr = print_pci_addr("spice", $bridges);
+
+ $spice_port = PVE::Tools::next_spice_port();
+
+ push @$cmd, '-spice', "tls-port=${spice_port},addr=127.0.0.1,tls-ciphers=DES-CBC3-SHA,seamless-migration=on";
+
+ push @$cmd, '-device', "virtio-serial,id=spice$pciaddr";
+ push @$cmd, '-chardev', "spicevmc,id=vdagent,name=vdagent";
+ push @$cmd, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
+ }
+
# enable balloon by default, unless explicitly disabled
if (!defined($conf->{balloon}) || $conf->{balloon}) {
$pciaddr = print_pci_addr("balloon0", $bridges);
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) = @_;
if ($drive->{interface} eq 'scsi') {
- my $maxdev = ($scsihw ne 'lsi') ? 256 : 7;
+ my $maxdev = ($scsihw !~ m/^lsi/) ? 256 : 7;
my $controller = int($drive->{index} / $maxdev);
$pciaddr = print_pci_addr("scsihw$controller", $bridges);
push @$devices, '-device', "$scsihw,id=scsihw$controller$pciaddr" if !$scsicontroller->{$controller};
$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};
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) {
}
push @$cmd, @$devices;
- push @$cmd, '-rtc', join(',', @$rtcFlags)
+ push @$cmd, '-rtc', join(',', @$rtcFlags)
if scalar(@$rtcFlags);
- push @$cmd, '-machine', join(',', @$machineFlags)
+ push @$cmd, '-machine', join(',', @$machineFlags)
if scalar(@$machineFlags);
push @$cmd, '-global', join(',', @$globalFlags)
if scalar(@$globalFlags);
- return wantarray ? ($cmd, $vollist) : $cmd;
+ return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
sub vnc_socket {
return "${var_run_tmpdir}/$vmid.vnc";
}
+sub spice_port {
+ my ($vmid) = @_;
+
+ my $res = vm_mon_cmd($vmid, 'query-spice');
+
+ return $res->{'tls-port'} || $res->{'port'} || die "no spice port\n";
+}
+
sub qmp_socket {
my ($vmid) = @_;
return "${var_run_tmpdir}/$vmid.qmp";
return "${var_run_tmpdir}/$vmid.pid";
}
-sub next_migrate_port {
-
- for (my $p = 60000; $p < 60010; $p++) {
-
- my $sock = IO::Socket::INET->new(Listen => 5,
- LocalAddr => 'localhost',
- LocalPort => $p,
- ReuseAddr => 1,
- Proto => 0);
-
- if ($sock) {
- close($sock);
- return $p;
- }
- }
-
- die "unable to find free migration port";
-}
-
sub vm_devices_list {
my ($vmid) = @_;
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;
}
}
if ($deviceid =~ m/^(scsi)(\d+)$/) {
- return 1 if ($conf->{scsihw} && $conf->{scsihw} ne 'lsi'); #virtio-scsi not yet support hotplug
+ return 1 if ($conf->{scsihw} && ($conf->{scsihw} !~ m/^lsi/)); #virtio-scsi not yet support hotplug
return undef if !qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device);
return undef if !qemu_driveadd($storecfg, $vmid, $device);
my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
}
}
- 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";
sub qemu_findorcreatescsihw {
my ($storecfg, $conf, $vmid, $device) = @_;
- my $maxdev = ($conf->{scsihw} && $conf->{scsihw} ne 'lsi') ? 256 : 7;
+ my $maxdev = ($conf->{scsihw} && ($conf->{scsihw} !~ m/^lsi/)) ? 256 : 7;
my $controller = int($device->{index} / $maxdev);
my $scsihwid="scsihw$controller";
my $devices_list = vm_devices_list($vmid);
while (my ($k, $v) = each %$bridges) {
$bridgeid = $k;
}
- return if $bridgeid < 1;
+ return if !$bridgeid || $bridgeid < 1;
my $bridge = "pci.$bridgeid";
my $devices_list = vm_devices_list($vmid);
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) = @_;
return if !check_running($vmid) ;
- $bps = 0 if !$bps;
- $bps_rd = 0 if !$bps_rd;
- $bps_wr = 0 if !$bps_wr;
- $iops = 0 if !$iops;
- $iops_rd = 0 if !$iops_rd;
- $iops_wr = 0 if !$iops_wr;
-
vm_mon_cmd($vmid, "block_set_io_throttle", device => $deviceid, bps => int($bps), bps_rd => int($bps_rd), bps_wr => int($bps_wr), iops => int($iops), iops_rd => int($iops_rd), iops_wr => int($iops_wr));
}
#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) = @_;
+ my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_;
lock_config($vmid, sub {
my $conf = load_config($vmid, $migratedfrom);
# set environment variable useful inside network script
$ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
- my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults);
+ 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') {
- $migrate_port = next_migrate_port();
- my $migrate_uri = "tcp:localhost:${migrate_port}";
+ my $localip = "localhost";
+ my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+ if ($datacenterconf->{migration_unsecure}) {
+ my $nodename = PVE::INotify::nodename();
+ $localip = PVE::Cluster::remote_node_ip($nodename, 1);
+ }
+ $migrate_port = PVE::Tools::next_migrate_port();
+ $migrate_uri = "tcp:${localip}:${migrate_port}";
push @$cmd, '-incoming', $migrate_uri;
push @$cmd, '-S';
} else {
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);
my $err = $@;
die "start failed: $err" if $err;
- print "migration listens on port $migrate_port\n" if $migrate_port;
+ print "migration listens on $migrate_uri\n" if $migrate_uri;
if ($statefile && $statefile ne 'tcp') {
eval { vm_mon_cmd_nocheck($vmid, "cont"); };
warn $@ if $@;
}
- if($migratedfrom) {
- my $capabilities = {};
- $capabilities->{capability} = "xbzrle";
- $capabilities->{state} = JSON::true;
- eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
- }
- else{
+ if ($migratedfrom) {
+
+ eval {
+ PVE::QemuServer::set_migration_caps($vmid);
+ };
+ warn $@ if $@;
+
+ if ($spice_port) {
+ print "spice listens on port $spice_port\n";
+ if ($spice_ticket) {
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
+ PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
+ }
+ }
+
+ } else {
if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
- vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+ vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
if $conf->{balloon};
- vm_mon_cmd_nocheck($vmid, 'qom-set',
- path => "machine/peripheral/balloon0",
- property => "guest-stats-polling-interval",
+ vm_mon_cmd_nocheck($vmid, 'qom-set',
+ path => "machine/peripheral/balloon0",
+ property => "guest-stats-polling-interval",
value => 2);
}
}
$timeout = $cmd->{arguments}->{timeout};
delete $cmd->{arguments}->{timeout};
}
-
+
eval {
die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
my $sname = qmp_socket($vmid);
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) = @_;
scsihw1 => { bus => 0, addr => 6 },
ahci0 => { bus => 0, addr => 7 },
qga0 => { bus => 0, addr => 8 },
+ spice => { bus => 0, addr => 9 },
virtio0 => { bus => 0, addr => 10 },
virtio1 => { bus => 0, addr => 11 },
virtio2 => { bus => 0, addr => 12 },
net3 => { bus => 0, addr => 21 },
net4 => { bus => 0, addr => 22 },
net5 => { bus => 0, addr => 23 },
+ vga1 => { bus => 0, addr => 24 },
+ vga2 => { bus => 0, addr => 25 },
+ vga3 => { bus => 0, addr => 26 },
#addr29 : usb-host (pve-usb.cfg)
'pci.1' => { bus => 0, addr => 30 },
'pci.2' => { bus => 0, addr => 31 },
}
+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 {
return if $line =~ m/^lock:/;
return if $line =~ m/^unused\d+:/;
return if $line =~ m/^parent:/;
+ return if $line =~ m/^template:/; # restored VM is never a template
if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
# try to convert old 1.X settings
print $outfd "#$line";
} elsif ($virtdev && $map->{$virtdev}) {
my $di = parse_drive($virtdev, $value);
+ delete $di->{format}; # format can change on restore
$di->{file} = $map->{$virtdev};
$value = print_drive($vmid, $di);
print $outfd "$virtdev: $value\n";
foreach my $storeid (keys %$info) {
foreach my $item (@{$info->{$storeid}}) {
next if !($item->{volid} && $item->{size});
+ $item->{path} = PVE::Storage::path($cfg, $item->{volid});
$volid_hash->{$item->{volid}} = $item;
}
}
return $volid_hash;
}
+sub get_used_paths {
+ my ($vmid, $storecfg, $conf, $scan_snapshots, $skip_drive) = @_;
+
+ my $used_path = {};
+
+ my $scan_config = sub {
+ my ($cref, $snapname) = @_;
+
+ foreach my $key (keys %$cref) {
+ my $value = $cref->{$key};
+ if (valid_drivename($key)) {
+ next if $skip_drive && $key eq $skip_drive;
+ my $drive = parse_drive($key, $value);
+ next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
+ if ($drive->{file} =~ m!^/!) {
+ $used_path->{$drive->{file}}++; # = 1;
+ } else {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
+ next if !$storeid;
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
+ next if !$scfg;
+ my $path = PVE::Storage::path($storecfg, $drive->{file}, $snapname);
+ $used_path->{$path}++; # = 1;
+ }
+ }
+ }
+ };
+
+ &$scan_config($conf);
+
+ undef $skip_drive;
+
+ if ($scan_snapshots) {
+ foreach my $snapname (keys %{$conf->{snapshots}}) {
+ &$scan_config($conf->{snapshots}->{$snapname}, $snapname);
+ }
+ }
+
+ return $used_path;
+}
+
sub update_disksize {
my ($vmid, $conf, $volid_hash) = @_;
-
+
my $changes;
my $used = {};
+ # Note: it is allowed to define multiple storages with same path (alias), so
+ # we need to check both 'volid' and real 'path' (two different volid can point
+ # to the same path).
+
+ my $usedpath = {};
+
# update size info
foreach my $opt (keys %$conf) {
if (valid_drivename($opt)) {
next if !$volid;
$used->{$volid} = 1;
+ if ($volid_hash->{$volid} &&
+ (my $path = $volid_hash->{$volid}->{path})) {
+ $usedpath->{$path} = 1;
+ }
next if drive_is_cdrom($drive);
next if !$volid_hash->{$volid};
$drive->{size} = $volid_hash->{$volid}->{size};
+ my $new = print_drive($vmid, $drive);
+ if ($new ne $conf->{$opt}) {
+ $changes = 1;
+ $conf->{$opt} = $new;
+ }
+ }
+ }
+
+ # remove 'unusedX' entry if volume is used
+ foreach my $opt (keys %$conf) {
+ next if $opt !~ m/^unused\d+$/;
+ my $volid = $conf->{$opt};
+ my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
+ if ($used->{$volid} || ($path && $usedpath->{$path})) {
$changes = 1;
- $conf->{$opt} = print_drive($vmid, $drive);
+ delete $conf->{$opt};
}
}
foreach my $volid (sort keys %$volid_hash) {
next if $volid =~ m/vm-$vmid-state-/;
next if $used->{$volid};
+ my $path = $volid_hash->{$volid}->{path};
+ next if !$path; # just to be sure
+ next if $usedpath->{$path};
$changes = 1;
add_unused_volume($conf, $volid);
+ $usedpath->{$path} = 1; # avoid to add more than once (aliases)
}
return $changes;
my ($vmid) = @_;
my $conf = load_config($vmid);
-
+
check_lock($conf);
my $vm_volids = {};
&$updatefn($vmid);
} else {
lock_config($vmid, $updatefn, $vmid);
- }
+ }
}
}
}
} else {
die "unknown compression method '$comp'\n";
}
-
+
}
my $tmpdir = "/var/tmp/vzdumptmp$$";
$devinfo->{$devname}->{format} = $format;
$devinfo->{$devname}->{storeid} = $storeid;
- # check permission on storage
+ # check permission on storage
my $pool = $opts->{pool}; # todo: do we need that?
if ($user ne 'root@pam') {
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
}
foreach my $devname (keys %$devinfo) {
- die "found no device mapping information for device '$devname'\n"
- if !$devinfo->{$devname}->{virtdev};
+ die "found no device mapping information for device '$devname'\n"
+ if !$devinfo->{$devname}->{virtdev};
}
my $cfg = cfs_read_file('storage.cfg');
# create empty/temp config
- if ($oldconf) {
+ if ($oldconf) {
PVE::Tools::file_set_contents($conffile, "memory: 128\n");
foreach_drive($oldconf, sub {
my ($ds, $drive) = @_;
my $d = $virtdev_hash->{$virtdev};
my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+
+ # test if requested format is supported
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+ my $supported = grep { $_ eq $d->{format} } @$validFormats;
+ $d->{format} = $defFormat if !$supported;
+
my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
$d->{format}, undef, $alloc_size);
print STDERR "new volume ID is '$volid'\n";
my $write_zeros = 1;
# fixme: what other storages types initialize volumes with zero?
- if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' ||
+ if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'glusterfs' ||
$scfg->{type} eq 'sheepdog' || $scfg->{type} eq 'rbd') {
$write_zeros = 0;
}
my $cookie = { netcount => 0 };
while (defined(my $line = <$fh>)) {
- restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
}
$fh->close();
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;
close($fifofh);
}
};
-
+
print "restore vma archive: $cmd\n";
run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
};
my $cookie = { netcount => 0 };
while (defined (my $line = <$srcfd>)) {
- restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
}
$srcfd->close();
next if $k eq 'digest';
next if $k eq 'description';
next if $k =~ m/^unused\d+$/;
-
+
$dest->{$k} = $source->{$k};
}
};
my $volid = $drive->{file};
my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
- $sidhash->{$sid} = $sid if $sid;
+ $sidhash->{$sid} = $sid if $sid;
}
foreach my $sid (sort keys %$sidhash) {
my $alloc_vmstate_volid = sub {
my ($storecfg, $vmid, $conf, $snapname) = @_;
-
+
# Note: we try to be smart when selecting a $target storage
my $target;
my $conf = load_config($vmid);
- die "you can't take a snapshot if it's a template\n"
+ die "you can't take a snapshot if it's a template\n"
if is_template($conf);
check_lock($conf);
$conf->{lock} = 'snapshot';
- die "snapshot name '$snapname' already used\n"
- if defined($conf->{snapshots}->{$snapname});
+ die "snapshot name '$snapname' already used\n"
+ if defined($conf->{snapshots}->{$snapname});
my $storecfg = PVE::Storage::config();
die "snapshot feature is not available" if !has_feature('snapshot', $conf, $storecfg);
$snap->{snaptime} = time();
$snap->{description} = $comment if $comment;
+ # always overwrite machine if we save vmstate. This makes sure we
+ # can restore it later using correct machine type
+ $snap->{machine} = get_current_qemu_machine($vmid) if $snap->{vmstate};
+
update_config_nolock($vmid, $conf, 1);
};
my $conf = load_config($vmid);
- die "missing snapshot lock\n"
- if !($conf->{lock} && $conf->{lock} eq 'snapshot');
+ die "missing snapshot lock\n"
+ if !($conf->{lock} && $conf->{lock} eq 'snapshot');
my $snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+ die "wrong snapshot state\n"
+ if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
- die "wrong snapshot state\n"
- if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
delete $snap->{snapstate};
delete $conf->{lock};
my $prepare = 1;
my $storecfg = PVE::Storage::config();
-
+
my $updatefn = sub {
my $conf = load_config($vmid);
$snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
- die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
+ die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
if $snap->{snapstate};
if ($prepare) {
delete $conf->{lock};
}
+ my $forcemachine;
+
if (!$prepare) {
+ my $has_machine_config = defined($conf->{machine});
+
# copy snapshot config to current config
$conf = &$snapshot_apply_config($conf, $snap);
$conf->{parent} = $snapname;
+
+ # Note: old code did not store 'machine', so we try to be smart
+ # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
+ $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
+ # we remove the 'machine' configuration if not explicitly specified
+ # in the original config.
+ delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
}
update_config_nolock($vmid, $conf, 1);
if (!$prepare && $snap->{vmstate}) {
my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
- vm_start($storecfg, $vmid, $statefile);
+ vm_start($storecfg, $vmid, $statefile, undef, undef, undef, $forcemachine);
}
};
lock_config($vmid, $updatefn);
-
+
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
if ($running) {
if ($snap->{vmstate}) {
- my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
+ my $path = PVE::Storage::path($storecfg, $snap->{vmstate});
vm_mon_cmd($vmid, "savevm-start", statefile => $path);
&$savevm_wait($vmid);
} else {
};
qga_freezefs($vmid) if $running && $freezefs;
-
+
foreach_drive($snap, sub {
my ($ds, $drive) = @_;
};
my $err = $@;
- eval { gqa_unfreezefs($vmid) if $running && $freezefs; };
+ eval { qga_unfreezefs($vmid) if $running && $freezefs; };
warn $@ if $@;
eval { vm_mon_cmd($vmid, "savevm-end") if $running; };
}
}
};
-
+
my $updatefn = sub {
my ($remove_drive) = @_;
if (!$drivehash) {
check_lock($conf);
- die "you can't delete a snapshot if vm is a template\n"
+ die "you can't delete a snapshot if vm is a template\n"
if is_template($conf);
}
$snap = $conf->{snapshots}->{$snapname};
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
+ die "snapshot '$snapname' does not exist\n" if !defined($snap);
# remove parent refs
&$unlink_parent($conf, $snap->{parent});
sub has_feature {
my ($feature, $conf, $storecfg, $snapname, $running) = @_;
- my $err = undef;
+ my $err;
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
$err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running);
});
- return 1 if !$err;
+ return $err ? 0 : 1;
}
sub template_create {
my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);
$drive->{file} = $voliddst;
- $conf->{$ds} = PVE::QemuServer::print_drive($vmid, $drive);
- PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+ $conf->{$ds} = print_drive($vmid, $drive);
+ update_config_nolock($vmid, $conf, 1);
});
}
my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
my $cmd = [];
- push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-C';
+ push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-n';
push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path, $dst_path;
sub qemu_img_format {
my ($scfg, $volname) = @_;
- if ($scfg->{path} && $volname =~ m/\.(raw|qcow2|qed|vmdk)$/){
+ if ($scfg->{path} && $volname =~ m/\.(raw|qcow2|qed|vmdk)$/) {
return $1;
- }
- elsif ($scfg->{type} eq 'nexenta' || $scfg->{type} eq 'iscsidirect'){
- return "iscsi";
- }
- elsif ($scfg->{type} eq 'lvm' || $scfg->{type} eq 'iscsi'){
+ } elsif ($scfg->{type} eq 'iscsi') {
return "host_device";
- }
- elsif ($scfg->{type} eq 'rbd'){
+ } else {
return "raw";
}
- #sheepdog other qemu block driver
- else{
- return $scfg->{type};
- }
}
sub qemu_drive_mirror {
if ($dst_storeid) {
my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
- my $format = undef;
+ my $format;
if ($dst_volname =~ m/\.(raw|qcow2)$/){
$format = $1;
}
my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
- if($format){
- #fixme : sometime drive-mirror timeout, but works fine after. (I have see the problem with big volume > 200GB), so we need to eval
- eval{ PVE::QemuServer::vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $dst_path, format => $format); };
- }else{
- eval{ PVE::QemuServer::vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $dst_path); };
+ if ($format) {
+ #fixme : sometime drive-mirror timeout, but works fine after.
+ # (I have see the problem with big volume > 200GB), so we need to eval
+ eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
+ sync => "full", target => $dst_path, format => $format); };
+ } else {
+ eval { vm_mon_cmd($vmid, "drive-mirror", timeout => 10, device => "drive-$drive", mode => "existing",
+ sync => "full", target => $dst_path); };
}
- eval{
+
+ eval {
while (1) {
- my $stats = PVE::QemuServer::vm_mon_cmd($vmid, "query-block-jobs");
+ my $stats = vm_mon_cmd($vmid, "query-block-jobs");
my $stat = @$stats[0];
+ die "mirroring job seem to have die. Maybe do you have bad sectors?" if !$stat;
+ die "error job is not mirroring" if $stat->{type} ne "mirror";
+
my $transferred = $stat->{offset};
my $total = $stat->{len};
my $remaining = $total - $transferred;
my $percent = sprintf "%.2f", ($transferred * 100 / $total);
- die "error job is not mirroring" if $stat->{type} ne "mirror";
print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent %\n";
vm_suspend($vmid,1);
$count = 0;
$frozen = 1;
- }else {
+ } else {
$count++ unless $frozen;
}
- }
- elsif ($frozen) {
- vm_resume($vmid,1);
- $count = 0;
+ } elsif ($frozen) {
+ vm_resume($vmid,1);
+ $count = 0;
}
$old_len = $stat->{offset};
sleep 1;
}
+
+ if ($vmiddst == $vmid) {
+ # switch the disk if source and destination are on the same guest
+ vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive");
+ }
};
- if ($@) {
- eval{ PVE::QemuServer::vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive"); };
- die "mirroring error: $@";
+ if (my $err = $@) {
+ eval { vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive"); };
+ die "mirroring error: $err";
}
- if($vmiddst != $vmid){
- #if we clone a disk for a new target vm, we don't switch the disk
- PVE::QemuServer::vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
- }else{
- #if source and destination are on the same guest
- PVE::QemuServer::vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive");
+ if ($vmiddst != $vmid) {
+ # if we clone a disk for a new target vm, we don't switch the disk
+ vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
+ }
+ }
+}
+
+sub clone_disk {
+ my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
+ $newvmid, $storage, $format, $full, $newvollist) = @_;
+
+ my $newvolid;
+
+ if (!$full) {
+ print "create linked clone of drive $drivename ($drive->{file})\n";
+ $newvolid = PVE::Storage::vdisk_clone($storecfg, $drive->{file}, $newvmid);
+ push @$newvollist, $newvolid;
+ } else {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+ $storeid = $storage if $storage;
+
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+ if (!$format) {
+ $format = $drive->{format} || $defFormat;
+ }
+
+ # test if requested format is supported - else use default
+ my $supported = grep { $_ eq $format } @$validFormats;
+ $format = $defFormat if !$supported;
+
+ my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
+
+ print "create full clone of drive $drivename ($drive->{file})\n";
+ $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $format, undef, ($size/1024));
+ push @$newvollist, $newvolid;
+
+ if (!$running || $snapname) {
+ qemu_img_convert($drive->{file}, $newvolid, $size, $snapname);
+ } else {
+ qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid);
}
}
+
+ my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
+
+ my $disk = $drive;
+ $disk->{format} = undef;
+ $disk->{file} = $newvolid;
+ $disk->{size} = $size;
+
+ return $disk;
+}
+
+# this only works if VM is running
+sub get_current_qemu_machine {
+ my ($vmid) = @_;
+
+ my $cmd = { execute => 'query-machines', arguments => {} };
+ my $res = PVE::QemuServer::vm_qmp_command($vmid, $cmd);
+
+ my ($current, $default);
+ foreach my $e (@$res) {
+ $default = $e->{name} if $e->{'is-default'};
+ $current = $e->{name} if $e->{'is-current'};
+ }
+
+ # fallback to the default machine if current is not supported by qemu
+ 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;