use Fcntl ':flock';
use Cwd 'abs_path';
use IPC::Open3;
+use JSON;
use Fcntl;
use PVE::SafeSyslog;
use Storable qw(dclone);
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
use PVE::INotify;
use PVE::ProcFSTools;
+use PVE::QMPClient;
use Time::HiRes qw(gettimeofday);
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
# allowed when such lock is set. But you can ignore this kind of
# lock with the --skiplock flag.
-cfs_register_file('/qemu-server/',
+cfs_register_file('/qemu-server/',
\&parse_vm_config,
\&write_vm_config);
type => 'string', format => 'dns-name',
description => "Set a name for the VM. Only used on the configuration web interface.",
},
+ scsihw => {
+ optional => 1,
+ type => 'string',
+ description => "scsi controller model",
+ enum => [qw(lsi virtio-scsi-pci megasas)],
+ default => 'lsi',
+ },
description => {
optional => 1,
type => 'string',
optional => 1,
type => 'string', format => 'pve-qm-bootdisk',
description => "Enable booting from specified disk.",
- pattern => '(ide|scsi|virtio)\d+',
+ pattern => '(ide|sata|scsi|virtio)\d+',
},
smp => {
optional => 1,
description => "Enable/disable ACPI.",
default => 1,
},
+ agent => {
+ optional => 1,
+ type => 'boolean',
+ description => "Enable/disable Qemu GuestAgent.",
+ default => 0,
+ },
kvm => {
optional => 1,
type => 'boolean',
pattern => '(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)',
default => 'now',
},
+ startup => {
+ optional => 1,
+ type => 'string', format => 'pve-qm-startup',
+ typetext => '[[order=]\d+] [,up=\d+] [,down=\d+] ',
+ description => "Startup and shutdown behavior. Order is a non-negative number defining the general startup order. Shutdown in done with reverse ordering. Additionally you can set the 'up' or 'down' delay in seconds, which specifies a delay to wait before the next VM is started or stopped.",
+ },
args => {
optional => 1,
type => 'string',
my $MAX_IDE_DISKS = 4;
my $MAX_SCSI_DISKS = 14;
-my $MAX_VIRTIO_DISKS = 6;
+my $MAX_VIRTIO_DISKS = 16;
my $MAX_SATA_DISKS = 6;
my $MAX_USB_DEVICES = 5;
-my $MAX_NETS = 6;
+my $MAX_NETS = 32;
my $MAX_UNUSED_DISKS = 8;
my $MAX_HOSTPCI_DEVICES = 2;
my $MAX_SERIAL_PORTS = 4;
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] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
- description => "Use volume as IDE hard disk or CD-ROM (n is 0 to 3).",
+ 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]',
+ 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] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
- description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to 13).",
+ 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]',
+ 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] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
- description => "Use volume as SATA hard disk or CD-ROM (n is 0 to 5).",
+ 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]',
+ 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] [,format=f] [,backup=yes|no] [,rerror=ignore|report|stop] [,werror=enospc|ignore|report|stop] [,aio=native|threads]',
- description => "Use volume as VIRTIO hard disk (n is 0 to 5).",
+ 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]',
+ description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
};
PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
};
}
-sub disk_devive_info {
- my $dev = shift;
-
- die "unknown disk device format '$dev'" if $dev !~ m/^(ide|scsi|virtio)(\d+)$/;
-
- my $bus = $1;
- my $index = $2;
- my $maxdev = 1024;
-
- if ($bus eq 'ide') {
- $maxdev = 2;
- } elsif ($bus eq 'scsi') {
- $maxdev = 7;
- }
-
- my $controller = int($index / $maxdev);
- my $unit = $index % $maxdev;
-
-
- return { bus => $bus, desc => uc($bus) . " $controller:$unit",
- controller => $controller, unit => $unit, index => $index };
-
-}
-
-sub qemu_drive_name {
- my ($dev, $media) = @_;
-
- my $info = disk_devive_info($dev);
- my $mediastr = '';
-
- if (($info->{bus} eq 'ide') || ($info->{bus} eq 'scsi')) {
- $mediastr = ($media eq 'cdrom') ? "-cd" : "-hd";
- return sprintf("%s%i%s%i", $info->{bus}, $info->{controller},
- $mediastr, $info->{unit});
- } else {
- return sprintf("%s%i", $info->{bus}, $info->{index});
- }
-}
-
my $cdrom_path;
sub get_cdrom_path {
my $etype;
if ($media eq 'disk') {
- $etype = 'image';
+ $etype = 'images';
} elsif ($media eq 'cdrom') {
$etype = 'iso';
} else {
PVE::Tools::file_set_contents($filename, $data);
}
+my $parse_size = sub {
+ my ($value) = @_;
+
+ return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
+ my ($size, $unit) = ($1, $3);
+ if ($unit) {
+ if ($unit eq 'K') {
+ $size = $size * 1024;
+ } elsif ($unit eq 'M') {
+ $size = $size * 1024 * 1024;
+ } elsif ($unit eq 'G') {
+ $size = $size * 1024 * 1024 * 1024;
+ }
+ }
+ return int($size);
+};
+
+my $format_size = sub {
+ my ($size) = @_;
+
+ $size = int($size);
+
+ my $kb = int($size/1024);
+ return $size if $kb*1024 != $size;
+
+ my $mb = int($kb/1024);
+ return "${kb}K" if $mb*1024 != $kb;
+
+ my $gb = int($mb/1024);
+ return "${mb}M" if $gb*1024 != $mb;
+
+ return "${gb}G";
+};
+
# 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]
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)=(.+)$/) {
+ 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)=(.+)$/) {
my ($k, $v) = ($1, $2);
$k = 'file' if $k eq 'volume';
return undef if defined $res->{$k};
+ if ($k eq 'bps' || $k eq 'bps_rd' || $k eq 'bps_wr') {
+ return undef if !$v || $v !~ m/^\d+/;
+ $k = "m$k";
+ $v = sprintf("%.3f", $v / (1024*1024));
+ }
$res->{$k} = $v;
} else {
if (!$res->{file} && $p !~ m/=/) {
return undef if !$res->{file};
return undef if $res->{cache} &&
- $res->{cache} !~ m/^(off|none|writethrough|writeback|unsafe)$/;
+ $res->{cache} !~ m/^(off|none|writethrough|writeback|unsafe|directsync)$/;
return undef if $res->{snapshot} && $res->{snapshot} !~ m/^(on|off)$/;
return undef if $res->{cyls} && $res->{cyls} !~ m/^\d+$/;
return undef if $res->{heads} && $res->{heads} !~ m/^\d+$/;
return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
return undef if $res->{aio} && $res->{aio} !~ m/^(native|threads)$/;
+
+ 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_rd} && $res->{mbps_rd} !~ m/^\d+(\.\d+)?$/;
+ return undef if $res->{mbps_wr} && $res->{mbps_wr} !~ 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_rd} && $res->{iops_rd} !~ m/^\d+$/;
+ return undef if $res->{iops_wr} && $res->{iops_wr} !~ m/^\d+$/;
+
+
+ if ($res->{size}) {
+ return undef if !defined($res->{size} = &$parse_size($res->{size}));
+ }
+
if ($res->{media} && ($res->{media} eq 'cdrom')) {
return undef if $res->{snapshot} || $res->{trans} || $res->{format};
return undef if $res->{heads} || $res->{secs} || $res->{cyls};
return $res;
}
-my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio);
+my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio iops iops_rd iops_wr);
sub print_drive {
my ($vmid, $drive) = @_;
my $opts = '';
- foreach my $o (@qemu_drive_options, 'backup') {
+ foreach my $o (@qemu_drive_options, 'mbps', 'mbps_rd', 'mbps_wr', 'backup') {
$opts .= ",$o=$drive->{$o}" if $drive->{$o};
}
+ if ($drive->{size}) {
+ $opts .= ",size=" . &$format_size($drive->{size});
+ }
+
return "$drive->{file}$opts";
}
die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
return undef;
}
- my $version = unpack("I", $versionbuf);
+ my $version = unpack("I", $versionbuf);
if ($version < 30000) {
die "scsi generic interface too old\n" if !$noerr;
return undef;
}
-
+
my $buf = "\x00" x 36;
my $sensebuf = "\x00" x 8;
my $cmd = pack("C x3 C x11", 0x12, 36);
-
+
# see /usr/include/scsi/sg.h
my $sg_io_hdr_t = "i i C C s I P P P I I i P C C C C S S i I I";
- my $packet = pack($sg_io_hdr_t, ord('S'), -3, length($cmd),
- length($sensebuf), 0, length($buf), $buf,
+ my $packet = pack($sg_io_hdr_t, ord('S'), -3, length($cmd),
+ length($sensebuf), 0, length($buf), $buf,
$cmd, $sensebuf, 6000);
$ret = ioctl($fh, $SG_IO, $packet);
die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
return undef;
}
-
+
my @res = unpack($sg_io_hdr_t, $packet);
if ($res[17] || $res[18]) {
die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
}
sub print_drivedevice_full {
- my ($storecfg, $vmid, $drive) = @_;
+ my ($storecfg, $conf, $vmid, $drive, $bridges) = @_;
my $device = '';
my $maxdev = 0;
if ($drive->{interface} eq 'virtio') {
- my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}");
+ 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 = 7;
+ $maxdev = ($conf->{scsihw} && $conf->{scsihw} ne 'lsi') ? 256 : 7;
my $controller = int($drive->{index} / $maxdev);
my $unit = $drive->{index} % $maxdev;
my $devicetype = 'hd';
} else {
$path = PVE::Storage::path($storecfg, $drive->{file});
}
- $devicetype = 'block' if path_is_scsi($path);
+
+ if($path =~ m/^iscsi\:\/\//){
+ $devicetype = 'generic';
+ }
+ else {
+ $devicetype = 'block' if path_is_scsi($path);
+ }
}
- $device = "scsi-$devicetype,bus=lsi$controller.0,scsi-id=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+ 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';
+ } 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}";
+ }
+
} elsif ($drive->{interface} eq 'ide'){
$maxdev = 2;
my $controller = int($drive->{index} / $maxdev);
$opts .= ",$o=$drive->{$o}" if $drive->{$o};
}
+ foreach my $o (qw(bps bps_rd bps_wr)) {
+ my $v = $drive->{"m$o"};
+ $opts .= ",$o=" . int($v*1024*1024) if $v;
+ }
+
# use linux-aio by default (qemu default is threads)
$opts .= ",aio=native" if !$drive->{aio};
}
sub print_netdevice_full {
- my ($vmid, $conf, $net, $netid) = @_;
+ my ($vmid, $conf, $net, $netid, $bridges) = @_;
my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
# qemu > 0.15 always try to boot from network - we disable that by
# not loading the pxe rom file
my $extra = ($bootorder !~ m/n/) ? "romfile=," : '';
- my $pciaddr = print_pci_addr("$netid");
+ my $pciaddr = print_pci_addr("$netid", $bridges);
my $tmpstr = "$device,${extra}mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
$tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
return $tmpstr;
}
die "To many unused volume - please delete them first.\n" if !$key;
-
+
$config->{$key} = $volid;
return $key;
return $res;
}
+PVE::JSONSchema::register_format('pve-qm-startup', \&verify_startup);
+sub verify_startup {
+ my ($value, $noerr) = @_;
+
+ return $value if parse_startup($value);
+
+ return undef if $noerr;
+
+ die "unable to parse startup options\n";
+}
+
+sub parse_startup {
+ my ($value) = @_;
+
+ return undef if !$value;
+
+ my $res = {};
+
+ foreach my $p (split(/,/, $value)) {
+ next if $p =~ m/^\s*$/;
+
+ if ($p =~ m/^(order=)?(\d+)$/) {
+ $res->{order} = $2;
+ } elsif ($p =~ m/^up=(\d+)$/) {
+ $res->{up} = $1;
+ } elsif ($p =~ m/^down=(\d+)$/) {
+ $res->{down} = $1;
+ } else {
+ return undef;
+ }
+ }
+
+ return $res;
+}
+
sub parse_usb_device {
my ($value) = @_;
warn $@ if $@;
}
-# fixme: remove?
-sub load_diskinfo_old {
- my ($storecfg, $vmid, $conf) = @_;
-
- my $info = {};
- my $res = {};
- my $vollist;
-
- foreach_drive($conf, sub {
- my ($ds, $di) = @_;
-
- $res->{$ds} = $di;
-
- return if drive_is_cdrom($di);
-
- if ($di->{file} =~ m|^/dev/.+|) {
- $info->{$di->{file}}->{size} = PVE::Storage::file_size_info($di->{file});
- } else {
- push @$vollist, $di->{file};
- }
- });
-
- eval {
- my $dl = PVE::Storage::vdisk_list($storecfg, undef, $vmid, $vollist);
-
- PVE::Storage::foreach_volid($dl, sub {
- my ($volid, $sid, $volname, $d) = @_;
- $info->{$volid} = $d;
- });
- };
- warn $@ if $@;
-
- foreach my $ds (keys %$res) {
- my $di = $res->{$ds};
-
- $res->{$ds}->{disksize} = $info->{$di->{file}} ?
- $info->{$di->{file}}->{size} / (1024*1024) : 0;
- }
-
- return $res;
-}
-
sub load_config {
- my ($vmid) = @_;
+ my ($vmid, $node) = @_;
- my $cfspath = cfs_config_path($vmid);
+ my $cfspath = cfs_config_path($vmid, $node);
my $conf = PVE::Cluster::cfs_read_file($cfspath);
my ($vmid, $conf, $skiplock) = @_;
check_lock($conf) if !$skiplock;
-
+
my $cfspath = cfs_config_path($vmid);
PVE::Cluster::cfs_write_file($cfspath, $conf);
return $loc_res;
}
+# check is used storages are available on all nodes (use by migrate)
+sub check_storage_availability {
+ my ($storecfg, $conf, $node) = @_;
+
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ my $volid = $drive->{file};
+ return if !$volid;
+
+ my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+ return if !$sid;
+
+ # check if storage is available on both nodes
+ my $scfg = PVE::Storage::storage_check_node($storecfg, $sid);
+ PVE::Storage::storage_check_node($storecfg, $sid, $node);
+ });
+}
+
sub check_lock {
my ($conf) = @_;
}
sub check_running {
- my ($vmid, $nocheck) = @_;
+ my ($vmid, $nocheck, $node) = @_;
- my $filename = config_file($vmid);
+ my $filename = config_file($vmid, $node);
die "unable to find configuration file for VM $vmid - no such machine\n"
if !$nocheck && ! -f $filename;
return $vzlist;
}
-my $storage_timeout_hash = {};
-
sub disksize {
my ($storecfg, $conf) = @_;
my $volid = $drive->{file};
return undef if !$volid;
- my $path;
- my $storeid;
- my $timeoutid;
-
- if ($volid =~ m|^/|) {
- $path = $timeoutid = $volid;
- } else {
- eval {
- $storeid = $timeoutid = PVE::Storage::parse_volume_id($volid);
- $path = PVE::Storage::path($storecfg, $volid);
- };
- if (my $err = $@) {
- warn $err;
- return undef;
- }
- }
-
- my $last_timeout = $storage_timeout_hash->{$timeoutid};
- if ($last_timeout) {
- if ((time() - $last_timeout) < 30) {
- # skip storage with errors
- return undef ;
- }
- delete $storage_timeout_hash->{$timeoutid};
- }
-
- my ($size, $format, $used);
-
- ($size, $format, $used) = PVE::Storage::file_size_info($path, 1);
-
- if (!defined($format)) {
- # got timeout
- $storage_timeout_hash->{$timeoutid} = time();
- return undef;
- }
-
- return wantarray ? ($size, $used) : $size;
+ return $drive->{size};
}
my $last_proc_pid_stat;
+# get VM status information
+# This must be fast and should not block ($full == false)
+# We only query KVM using QMP if $full == true (this can be slow)
sub vmstatus {
- my ($opt_vmid) = @_;
+ my ($opt_vmid, $full) = @_;
my $res = {};
# fixme: better status?
$d->{status} = $list->{$vmid}->{pid} ? 'running' : 'stopped';
- my ($size, $used) = disksize($storecfg, $conf);
- if (defined($size) && defined($used)) {
- $d->{disk} = $used;
+ my $size = disksize($storecfg, $conf);
+ if (defined($size)) {
+ $d->{disk} = 0; # no info available
$d->{maxdisk} = $size;
} else {
$d->{disk} = 0;
my $pid = $d->{pid};
next if !$pid;
- if (my $fh = IO::File->new("/proc/$pid/io", "r")) {
- my $data = {};
- while (defined(my $line = <$fh>)) {
- if ($line =~ m/^([rw]char):\s+(\d+)$/) {
- $data->{$1} = $2;
- }
- }
- close($fh);
- $d->{diskread} = $data->{rchar} || 0;
- $d->{diskwrite} = $data->{wchar} || 0;
- }
-
my $pstat = PVE::ProcFSTools::read_proc_pid_stat($pid);
next if !$pstat; # not running
}
}
+ return $res if !$full;
+
+ my $qmpclient = PVE::QMPClient->new();
+
+ my $blockstatscb = sub {
+ my ($vmid, $resp) = @_;
+ my $data = $resp->{'return'} || [];
+ my $totalrdbytes = 0;
+ my $totalwrbytes = 0;
+ for my $blockstat (@$data) {
+ $totalrdbytes = $totalrdbytes + $blockstat->{stats}->{rd_bytes};
+ $totalwrbytes = $totalwrbytes + $blockstat->{stats}->{wr_bytes};
+ }
+ $res->{$vmid}->{diskread} = $totalrdbytes;
+ $res->{$vmid}->{diskwrite} = $totalwrbytes;
+ };
+
+ my $statuscb = sub {
+ my ($vmid, $resp) = @_;
+ $qmpclient->queue_cmd($vmid, $blockstatscb, 'query-blockstats');
+
+ my $status = 'unknown';
+ if (!defined($status = $resp->{'return'}->{status})) {
+ warn "unable to get VM status\n";
+ return;
+ }
+
+ $res->{$vmid}->{qmpstatus} = $resp->{'return'}->{status};
+ };
+
+ foreach my $vmid (keys %$list) {
+ next if $opt_vmid && ($vmid ne $opt_vmid);
+ next if !$res->{$vmid}->{pid}; # not running
+ $qmpclient->queue_cmd($vmid, $statuscb, 'query-status');
+ }
+
+ $qmpclient->queue_execute();
+
+ foreach my $vmid (keys %$list) {
+ next if $opt_vmid && ($vmid ne $opt_vmid);
+ $res->{$vmid}->{qmpstatus} = $res->{$vmid}->{status} if !$res->{$vmid}->{qmpstatus};
+ }
+
return $res;
}
my ($storecfg, $vmid, $conf, $defaults, $migrate_uri) = @_;
my $cmd = [];
+ my $devices = [];
my $pciaddr = '';
+ my $bridges = {};
my $kvmver = kvm_user_version();
my $vernum = 0; # unknown
if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
my $use_virtio = 0;
- my $socket = monitor_socket($vmid);
- push @$cmd, '-chardev', "socket,id=monitor,path=$socket,server,nowait";
- push @$cmd, '-mon', "chardev=monitor,mode=readline";
+ my $qmpsocket = qmp_socket($vmid);
+ push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
+ push @$cmd, '-mon', "chardev=qmp,mode=control";
- $socket = vnc_socket($vmid);
+ my $socket = vnc_socket($vmid);
push @$cmd, '-vnc', "unix:$socket,x509,password";
push @$cmd, '-pidfile' , pidfile_name($vmid);
push @$cmd, '-incoming', $migrate_uri if $migrate_uri;
+ push @$cmd, '-S' if $migrate_uri;
+
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 @$cmd, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
+ 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};
if ($tablet) {
if ($use_usb2) {
- push @$cmd, '-device', 'usb-tablet,bus=ehci.0,port=6';
+ push @$devices, '-device', 'usb-tablet,bus=ehci.0,port=6';
} else {
- push @$cmd, '-usbdevice', 'tablet';
+ push @$devices, '-usbdevice', 'tablet';
}
}
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");
- push @$cmd, '-device', "pci-assign,host=$d->{pciid},id=hostpci$i$pciaddr";
+ $pciaddr = print_pci_addr("hostpci$i", $bridges);
+ push @$devices, '-device', "pci-assign,host=$d->{pciid},id=hostpci$i$pciaddr";
}
# usb devices
my $d = parse_usb_device($conf->{"usb$i"});
next if !$d;
if ($d->{vendorid} && $d->{productid}) {
- push @$cmd, '-device', "usb-host,vendorid=0x$d->{vendorid},productid=0x$d->{productid}";
+ push @$devices, '-device', "usb-host,vendorid=0x$d->{vendorid},productid=0x$d->{productid}";
} elsif (defined($d->{hostbus}) && defined($d->{hostport})) {
- push @$cmd, '-device', "usb-host,hostbus=$d->{hostbus},hostport=$d->{hostport}";
+ push @$devices, '-device', "usb-host,hostbus=$d->{hostbus},hostport=$d->{hostport}";
}
}
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 @$cmd, '-chardev', "tty,id=serial$i,path=$path";
- push @$cmd, '-device', "isa-serial,chardev=serial$i";
+ 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 @$cmd, '-chardev', "parport,id=parallel$i,path=$path";
- push @$cmd, '-device', "isa-parallel,chardev=parallel$i";
+ push @$devices, '-chardev', "parport,id=parallel$i,path=$path";
+ push @$devices, '-device', "isa-parallel,chardev=parallel$i";
}
}
}
}
+ if ($ost eq 'win7' || $ost eq 'w2k8' || $ost eq 'wvista') {
+ push @$cmd, '-no-kvm-pit-reinjection';
+ push @$cmd, '-no-hpet';
+ }
+
# -tdf ?
# -no-acpi
# -no-kvm
#my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
#push @$cmd, '-soundhw', 'es1370';
#push @$cmd, '-soundhw', $soundhw if $soundhw;
- $pciaddr = print_pci_addr("balloon0");
- push @$cmd, '-device', "virtio-balloon-pci,id=balloon0$pciaddr" if $conf->{balloon};
+
+ if($conf->{agent}) {
+ my $qgasocket = qga_socket($vmid);
+ my $pciaddr = print_pci_addr("qga0", $bridges);
+ push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
+ push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
+ push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
+ }
+
+ $pciaddr = print_pci_addr("balloon0", $bridges);
+ push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr" if $conf->{balloon};
if ($conf->{watchdog}) {
my $wdopts = parse_watchdog($conf->{watchdog});
- $pciaddr = print_pci_addr("watchdog");
+ $pciaddr = print_pci_addr("watchdog", $bridges);
my $watchdog = $wdopts->{model} || 'i6300esb';
- push @$cmd, '-device', "$watchdog$pciaddr";
- push @$cmd, '-watchdog-action', $wdopts->{action} if $wdopts->{action};
+ push @$devices, '-device', "$watchdog$pciaddr";
+ push @$devices, '-watchdog-action', $wdopts->{action} if $wdopts->{action};
}
my $vollist = [];
my $scsicontroller = {};
my $ahcicontroller = {};
+ my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : $defaults->{scsihw};
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
}
if ($drive->{interface} eq 'scsi') {
- my $maxdev = 7;
- my $controller = int($drive->{index} / $maxdev);
- $pciaddr = print_pci_addr("lsi$controller");
- push @$cmd, '-device', "lsi,id=lsi$controller$pciaddr" if !$scsicontroller->{$controller};
- $scsicontroller->{$controller}=1;
+
+ my $maxdev = ($scsihw ne '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};
+ $scsicontroller->{$controller}=1;
}
if ($drive->{interface} eq 'sata') {
my $controller = int($drive->{index} / $MAX_SATA_DISKS);
- $pciaddr = print_pci_addr("ahci$controller");
- push @$cmd, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller};
+ $pciaddr = print_pci_addr("ahci$controller", $bridges);
+ push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller};
$ahcicontroller->{$controller}=1;
}
- push @$cmd, '-drive',print_drive_full($storecfg, $vmid, $drive);
- push @$cmd, '-device',print_drivedevice_full($storecfg,$vmid, $drive);
+ push @$devices, '-drive',print_drive_full($storecfg, $vmid, $drive);
+ push @$devices, '-device',print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
});
push @$cmd, '-m', $conf->{memory} || $defaults->{memory};
}
my $netdevfull = print_netdev_full($vmid,$conf,$d,"net$i");
- push @$cmd, '-netdev', $netdevfull;
+ push @$devices, '-netdev', $netdevfull;
- my $netdevicefull = print_netdevice_full($vmid,$conf,$d,"net$i");
- push @$cmd, '-device', $netdevicefull;
+ my $netdevicefull = print_netdevice_full($vmid,$conf,$d,"net$i",$bridges);
+ 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;
}
push @$cmd, @$aa;
}
+ push @$cmd, @$devices;
return wantarray ? ($cmd, $vollist) : $cmd;
}
return "${var_run_tmpdir}/$vmid.vnc";
}
-sub monitor_socket {
+sub qmp_socket {
+ my ($vmid) = @_;
+ return "${var_run_tmpdir}/$vmid.qmp";
+}
+
+sub qga_socket {
my ($vmid) = @_;
- return "${var_run_tmpdir}/$vmid.mon";
+ return "${var_run_tmpdir}/$vmid.qga";
}
sub pidfile_name {
sub vm_devices_list {
my ($vmid) = @_;
- my $res = vm_monitor_command ($vmid, "info pci");
-
- my @lines = split ("\n", $res);
- my $devices;
- my $bus;
- my $addr;
- my $id;
+ my $res = vm_mon_cmd($vmid, 'query-pci');
- foreach my $line (@lines) {
- $line =~ s/^\s+//;
- if ($line =~ m/^Bus (\d+), device (\d+), function (\d+):$/) {
- $bus=$1;
- $addr=$2;
- }
- if ($line =~ m/^id "([a-z][a-z_\-]*\d*)"$/) {
- $id=$1;
- $devices->{$id}->{bus}=$bus;
- $devices->{$id}->{addr}=$addr;
+ my $devices = {};
+ foreach my $pcibus (@$res) {
+ foreach my $device (@{$pcibus->{devices}}) {
+ next if !$device->{'qdev_id'};
+ $devices->{$device->{'qdev_id'}} = $device;
}
}
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
+ qemu_bridgeadd($storecfg, $conf, $vmid, $deviceid); #add bridge if we need it for the device
+
if ($deviceid =~ m/^(virtio)(\d+)$/) {
return undef if !qemu_driveadd($storecfg, $vmid, $device);
- my $devicefull = print_drivedevice_full($storecfg, $vmid, $device);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
qemu_deviceadd($vmid, $devicefull);
if(!qemu_deviceaddverify($vmid, $deviceid)) {
qemu_drivedel($vmid, $deviceid);
}
}
- if ($deviceid =~ m/^(lsi)(\d+)$/) {
+ if ($deviceid =~ m/^(scsihw)(\d+)$/) {
+ my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : "lsi";
my $pciaddr = print_pci_addr($deviceid);
- my $devicefull = "lsi,id=$deviceid$pciaddr";
+ my $devicefull = "$scsihw,id=$deviceid$pciaddr";
qemu_deviceadd($vmid, $devicefull);
return undef if(!qemu_deviceaddverify($vmid, $deviceid));
}
if ($deviceid =~ m/^(scsi)(\d+)$/) {
- return undef if !qemu_findorcreatelsi($storecfg,$conf, $vmid, $device);
+ return 1 if ($conf->{scsihw} && $conf->{scsihw} ne '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, $vmid, $device);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
if(!qemu_deviceadd($vmid, $devicefull)) {
qemu_drivedel($vmid, $deviceid);
return undef;
}
}
+ if ($deviceid =~ m/^(pci\.)(\d+)$/) {
+ my $bridgeid = $2;
+ my $pciaddr = print_pci_addr($deviceid);
+ my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
+ qemu_deviceadd($vmid, $devicefull);
+ return undef if !qemu_deviceaddverify($vmid, $deviceid);
+ }
+
return 1;
}
sub qemu_deviceadd {
my ($vmid, $devicefull) = @_;
- my $ret = vm_monitor_command($vmid, "device_add $devicefull");
+ my $ret = vm_human_monitor_command($vmid, "device_add $devicefull");
$ret =~ s/^\s+//;
# Otherwise, if the command succeeds, no output is sent. So any non-empty string shows an error
return 1 if $ret eq "";
sub qemu_devicedel {
my($vmid, $deviceid) = @_;
- my $ret = vm_monitor_command($vmid, "device_del $deviceid");
+ my $ret = vm_human_monitor_command($vmid, "device_del $deviceid");
$ret =~ s/^\s+//;
return 1 if $ret eq "";
syslog("err", "detaching device $deviceid failed : $ret");
my($storecfg, $vmid, $device) = @_;
my $drive = print_drive_full($storecfg, $vmid, $device);
- my $ret = vm_monitor_command($vmid, "drive_add auto $drive");
+ my $ret = vm_human_monitor_command($vmid, "drive_add auto $drive");
# If the command succeeds qemu prints: "OK"
if ($ret !~ m/OK/s) {
syslog("err", "adding drive failed: $ret");
sub qemu_drivedel {
my($vmid, $deviceid) = @_;
- my $ret = vm_monitor_command($vmid, "drive_del drive-$deviceid");
+ my $ret = vm_human_monitor_command($vmid, "drive_del drive-$deviceid");
$ret =~ s/^\s+//;
if ($ret =~ m/Device \'.*?\' not found/s) {
# NB: device not found errors mean the drive was auto-deleted and we ignore the error
return undef;
}
-sub qemu_findorcreatelsi {
+sub qemu_findorcreatescsihw {
my ($storecfg, $conf, $vmid, $device) = @_;
- my $maxdev = 7;
+ my $maxdev = ($conf->{scsihw} && $conf->{scsihw} ne 'lsi') ? 256 : 7;
my $controller = int($device->{index} / $maxdev);
- my $lsiid="lsi$controller";
+ my $scsihwid="scsihw$controller";
my $devices_list = vm_devices_list($vmid);
- if(!defined($devices_list->{$lsiid})) {
- return undef if !vm_deviceplug($storecfg, $conf, $vmid, $lsiid);
+ if(!defined($devices_list->{$scsihwid})) {
+ return undef if !vm_deviceplug($storecfg, $conf, $vmid, $scsihwid);
+ }
+ return 1;
+}
+
+sub qemu_bridgeadd {
+ my ($storecfg, $conf, $vmid, $device) = @_;
+
+ my $bridges = {};
+ my $bridgeid = undef;
+ print_pci_addr($device, $bridges);
+
+ while (my ($k, $v) = each %$bridges) {
+ $bridgeid = $k;
+ }
+ return if $bridgeid < 1;
+ my $bridge = "pci.$bridgeid";
+ my $devices_list = vm_devices_list($vmid);
+
+ if(!defined($devices_list->{$bridge})) {
+ return undef if !vm_deviceplug($storecfg, $conf, $vmid, $bridge);
}
return 1;
}
my ($vmid, $conf, $device, $deviceid) = @_;
my $netdev = print_netdev_full($vmid, $conf, $device, $deviceid);
- my $ret = vm_monitor_command($vmid, "netdev_add $netdev");
+ my $ret = vm_human_monitor_command($vmid, "netdev_add $netdev");
$ret =~ s/^\s+//;
- #if the command succeeds, no output is sent. So any non-empty string shows an error
+ #if the command succeeds, no output is sent. So any non-empty string shows an error
return 1 if $ret eq "";
syslog("err", "adding netdev failed: $ret");
return undef;
sub qemu_netdevdel {
my ($vmid, $deviceid) = @_;
- my $ret = vm_monitor_command($vmid, "netdev_del $deviceid");
+ my $ret = vm_human_monitor_command($vmid, "netdev_del $deviceid");
$ret =~ s/^\s+//;
- #if the command succeeds, no output is sent. So any non-empty string shows an error
+ #if the command succeeds, no output is sent. So any non-empty string shows an error
return 1 if $ret eq "";
syslog("err", "deleting netdev failed: $ret");
return undef;
}
-sub vm_start {
- my ($storecfg, $vmid, $statefile, $skiplock) = @_;
-
- lock_config($vmid, sub {
- my $conf = load_config($vmid);
+sub qemu_block_set_io_throttle {
+ my ($vmid, $deviceid, $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr) = @_;
- check_lock($conf) if !$skiplock;
+ return if !check_running($vmid) ;
- die "VM $vmid already running\n" 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;
- my $migrate_uri;
- my $migrate_port = 0;
+ 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));
- if ($statefile) {
- if ($statefile eq 'tcp') {
- $migrate_port = next_migrate_port();
- $migrate_uri = "tcp:localhost:${migrate_port}";
- } else {
- if (-f $statefile) {
- $migrate_uri = "exec:cat $statefile";
- } else {
- warn "state file '$statefile' does not exist - doing normal startup\n";
- }
- }
- }
-
- my $defaults = load_defaults();
-
- my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults, $migrate_uri);
- # host pci devices
- 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);
- }
-
- PVE::Storage::activate_volumes($storecfg, $vollist);
-
- eval { run_command($cmd, timeout => $migrate_uri ? undef : 30); };
- my $err = $@;
- die "start failed: $err" if $err;
-
- if ($statefile) {
-
- if ($statefile eq 'tcp') {
- print "migration listens on port $migrate_port\n";
- } else {
- unlink $statefile;
- # fixme: send resume - is that necessary ?
- eval { vm_monitor_command($vmid, "cont"); };
- }
- }
-
- # always set migrate speed (overwrite kvm default of 32m)
- # we set a very hight default of 8192m which is basically unlimited
- my $migrate_speed = $defaults->{migrate_speed} || 8192;
- $migrate_speed = $conf->{migrate_speed} || $migrate_speed;
- eval {
- my $cmd = "migrate_set_speed ${migrate_speed}m";
- vm_monitor_command($vmid, $cmd);
- };
-
- if (my $migrate_downtime =
- $conf->{migrate_downtime} || $defaults->{migrate_downtime}) {
- my $cmd = "migrate_set_downtime ${migrate_downtime}";
- eval { vm_monitor_command($vmid, $cmd); };
- }
-
- vm_balloonset($vmid, $conf->{balloon}) if $conf->{balloon};
-
- });
}
+# old code, only used to shutdown old VM after update
sub __read_avail {
my ($fh, $timeout) = @_;
return $res;
}
+# old code, only used to shutdown old VM after update
sub vm_monitor_command {
my ($vmid, $cmdstr, $nocheck) = @_;
eval {
die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
- my $sname = monitor_socket($vmid);
+ my $sname = "${var_run_tmpdir}/$vmid.mon";
my $sock = IO::Socket::UNIX->new( Peer => $sname ) ||
die "unable to connect to VM $vmid socket - $!\n";
return $res;
}
+sub qemu_block_resize {
+ my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
+
+ my $running = PVE::QemuServer::check_running($vmid);
+
+ return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+
+ return if !$running;
+
+ vm_mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
+
+}
+
+sub qemu_volume_snapshot {
+ my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
+
+ my $running = PVE::QemuServer::check_running($vmid);
+
+ return if !PVE::Storage::volume_snapshot($storecfg, $volid, $snap, $running);
+
+ return if !$running;
+
+ vm_mon_cmd($vmid, "snapshot-drive", device => $deviceid, name => $snap);
+
+}
+
+sub qemu_volume_snapshot_delete {
+ my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
+
+ #need to implement statefile location
+ my $statefile="/tmp/$vmid-$snap";
+
+ unlink $statefile if -e $statefile;
+
+ my $running = PVE::QemuServer::check_running($vmid);
+
+ return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
+
+ return if !$running;
+
+ #need to split delvm monitor command like savevm
+
+}
+
+sub qemu_snapshot_start {
+ my ($vmid, $snap) = @_;
+
+ #need to implement statefile location
+ my $statefile="/tmp/$vmid-$snap";
+
+ vm_mon_cmd($vmid, "snapshot-start", statefile => $statefile);
+
+}
+
+sub qemu_snapshot_end {
+ my ($vmid) = @_;
+
+ vm_mon_cmd($vmid, "snapshot-end");
+
+}
+
+sub qga_freezefs {
+ my ($vmid) = @_;
+
+ #need to impplement call to qemu-ga
+}
+
+sub qga_unfreezefs {
+ my ($vmid) = @_;
+
+ #need to impplement call to qemu-ga
+}
+
+sub vm_start {
+ my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom) = @_;
+
+ lock_config($vmid, sub {
+ my $conf = load_config($vmid, $migratedfrom);
+
+ check_lock($conf) if !$skiplock;
+
+ die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
+
+ my $migrate_uri;
+ my $migrate_port = 0;
+
+ if ($statefile) {
+ if ($statefile eq 'tcp') {
+ $migrate_port = next_migrate_port();
+ $migrate_uri = "tcp:localhost:${migrate_port}";
+ } else {
+ if (-f $statefile) {
+ $migrate_uri = "exec:cat $statefile";
+ } else {
+ warn "state file '$statefile' does not exist - doing normal startup\n";
+ }
+ }
+ }
+
+ my $defaults = load_defaults();
+
+ # set environment variable useful inside network script
+ $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
+
+ my ($cmd, $vollist) = config_to_command($storecfg, $vmid, $conf, $defaults, $migrate_uri);
+ # host pci devices
+ 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);
+ }
+
+ PVE::Storage::activate_volumes($storecfg, $vollist);
+
+ eval { run_command($cmd, timeout => $migrate_uri ? undef : 30); };
+ my $err = $@;
+ die "start failed: $err" if $err;
+
+ if ($statefile) {
+
+ if ($statefile eq 'tcp') {
+ print "migration listens on port $migrate_port\n";
+ } else {
+ unlink $statefile;
+ # fixme: send resume - is that necessary ?
+ eval { vm_mon_cmd($vmid, "cont"); };
+ }
+ }
+
+ # always set migrate speed (overwrite kvm default of 32m)
+ # we set a very hight default of 8192m which is basically unlimited
+ my $migrate_speed = $defaults->{migrate_speed} || 8192;
+ $migrate_speed = $conf->{migrate_speed} || $migrate_speed;
+ $migrate_speed = $migrate_speed * 1048576;
+ eval {
+ vm_mon_cmd($vmid, "migrate_set_speed", value => $migrate_speed);
+ };
+
+ my $migrate_downtime = $defaults->{migrate_downtime};
+ $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime});
+ if (defined($migrate_downtime)) {
+ eval { vm_mon_cmd($vmid, "migrate_set_downtime", value => $migrate_downtime); };
+ }
+
+ if($migratedfrom) {
+ my $capabilities = {};
+ $capabilities->{capability} = "xbzrle";
+ $capabilities->{state} = JSON::true;
+ eval { PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
+ }
+
+ vm_balloonset($vmid, $conf->{balloon}) if $conf->{balloon};
+
+ });
+}
+
+sub vm_mon_cmd {
+ my ($vmid, $execute, %params) = @_;
+
+ my $cmd = { execute => $execute, arguments => \%params };
+ vm_qmp_command($vmid, $cmd);
+}
+
+sub vm_mon_cmd_nocheck {
+ my ($vmid, $execute, %params) = @_;
+
+ my $cmd = { execute => $execute, arguments => \%params };
+ vm_qmp_command($vmid, $cmd, 1);
+}
+
+sub vm_qmp_command {
+ my ($vmid, $cmd, $nocheck) = @_;
+
+ my $res;
+
+ my $timeout;
+ if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) {
+ $timeout = $cmd->{arguments}->{timeout};
+ delete $cmd->{arguments}->{timeout};
+ }
+
+ eval {
+ die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
+ my $sname = PVE::QemuServer::qmp_socket($vmid);
+ if (-e $sname) {
+ my $qmpclient = PVE::QMPClient->new();
+
+ $res = $qmpclient->cmd($vmid, $cmd, $timeout);
+ } elsif (-e "${var_run_tmpdir}/$vmid.mon") {
+ die "can't execute complex command on old monitor - stop/start your vm to fix the problem\n"
+ if scalar(%{$cmd->{arguments}});
+ vm_monitor_command($vmid, $cmd->{execute}, $nocheck);
+ } else {
+ die "unable to open monitor socket\n";
+ }
+ };
+ if (my $err = $@) {
+ syslog("err", "VM $vmid qmp command failed - $err");
+ die $err;
+ }
+
+ return $res;
+}
+
+sub vm_human_monitor_command {
+ my ($vmid, $cmdline) = @_;
+
+ my $res;
+
+ my $cmd = {
+ execute => 'human-monitor-command',
+ arguments => { 'command-line' => $cmdline},
+ };
+
+ return vm_qmp_command($vmid, $cmd);
+}
+
sub vm_commandline {
my ($storecfg, $vmid) = @_;
check_lock($conf) if !$skiplock;
- vm_monitor_command($vmid, "system_reset");
+ vm_mon_cmd($vmid, "system_reset");
});
}
PVE::Storage::deactivate_volumes($storecfg, $vollist);
}
- foreach my $ext (qw(mon pid vnc)) {
+ foreach my $ext (qw(mon qmp pid vnc qga)) {
unlink "/var/run/qemu-server/${vmid}.$ext";
}
};
# We need that when migration VMs to other nodes (files already moved)
# Note: we set $keepActive in vzdump stop mode - volumes need to stay active
sub vm_stop {
- my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive) = @_;
-
- $timeout = 60 if !defined($timeout);
+ my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive, $migratedfrom) = @_;
$force = 1 if !defined($force) && !$shutdown;
+ if ($migratedfrom){
+ my $pid = check_running($vmid, $nocheck, $migratedfrom);
+ kill 15, $pid if $pid;
+ my $conf = load_config($vmid, $migratedfrom);
+ vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive);
+ return;
+ }
+
lock_config($vmid, sub {
my $pid = check_running($vmid, $nocheck);
if (!$nocheck) {
$conf = load_config($vmid);
check_lock($conf) if !$skiplock;
+ if (!defined($timeout) && $shutdown && $conf->{startup}) {
+ my $opts = parse_startup($conf->{startup});
+ $timeout = $opts->{down} if $opts->{down};
+ }
}
+ $timeout = 60 if !defined($timeout);
+
eval {
if ($shutdown) {
- vm_monitor_command($vmid, "system_powerdown", $nocheck);
+ $nocheck ? vm_mon_cmd_nocheck($vmid, "system_powerdown") : vm_mon_cmd($vmid, "system_powerdown");
+
} else {
- vm_monitor_command($vmid, "quit", $nocheck);
+ $nocheck ? vm_mon_cmd_nocheck($vmid, "quit") : vm_mon_cmd($vmid, "quit");
}
};
my $err = $@;
check_lock($conf) if !$skiplock;
- vm_monitor_command($vmid, "stop");
+ vm_mon_cmd($vmid, "stop");
});
}
check_lock($conf) if !$skiplock;
- vm_monitor_command($vmid, "cont");
+ vm_mon_cmd($vmid, "cont");
});
}
my $conf = load_config($vmid);
- vm_monitor_command($vmid, "sendkey $key");
+ # there is no qmp command, so we use the human monitor command
+ vm_human_monitor_command($vmid, "sendkey $key");
});
}
});
}
-sub vm_stopall {
- my ($storecfg, $timeout) = @_;
-
- $timeout = 3*60 if !$timeout;
-
- my $cleanuphash = {};
-
- my $vzlist = vzlist();
- my $count = 0;
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
- $count++;
- $cleanuphash->{$vmid} = 1;
- }
-
- return if !$count;
-
- my $msg = "Stopping Qemu Server - sending shutdown requests to all VMs\n";
- syslog('info', $msg);
- warn $msg;
-
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
- eval { vm_monitor_command($vmid, "system_powerdown"); };
- warn $@ if $@;
- }
-
- my $wt = 5;
- my $maxtries = int(($timeout + $wt -1)/$wt);
- my $try = 0;
- while (($try < $maxtries) && $count) {
- $try++;
- sleep $wt;
-
- $vzlist = vzlist();
- $count = 0;
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
- $count++;
- }
- last if !$count;
- }
-
- if ($count) {
-
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
-
- warn "VM $vmid still running - sending stop now\n";
- eval { vm_monitor_command($vmid, "quit"); };
- warn $@ if $@;
- }
-
- $timeout = 30;
- $maxtries = int(($timeout + $wt -1)/$wt);
- $try = 0;
- while (($try < $maxtries) && $count) {
- $try++;
- sleep $wt;
-
- $vzlist = vzlist();
- $count = 0;
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
- $count++;
- }
- last if !$count;
- }
-
- if ($count) {
-
- foreach my $vmid (keys %$vzlist) {
- next if !$vzlist->{$vmid}->{pid};
-
- warn "VM $vmid still running - terminating now with SIGTERM\n";
- kill 15, $vzlist->{$vmid}->{pid};
- }
- sleep 1;
- }
-
- # this is called by system shotdown scripts, so remaining
- # processes gets killed anyways (no need to send kill -9 here)
- }
-
- $vzlist = vzlist();
- foreach my $vmid (keys %$cleanuphash) {
- next if $vzlist->{$vmid}->{pid};
- eval {
- my $conf = load_config($vmid);
- vm_stop_cleanup($storecfg, $vmid, $conf);
- };
- warn $@ if $@;
- }
-
- $msg = "Qemu Server stopped\n";
- syslog('info', $msg);
- print $msg;
-}
-
# pci helpers
sub file_write {
}
sub print_pci_addr {
- my ($id) = @_;
+ my ($id, $bridges) = @_;
my $res = '';
my $devices = {
#addr2 : first videocard
balloon0 => { bus => 0, addr => 3 },
watchdog => { bus => 0, addr => 4 },
- lsi0 => { bus => 0, addr => 5 },
- lsi1 => { bus => 0, addr => 6 },
+ scsihw0 => { bus => 0, addr => 5 },
+ scsihw1 => { bus => 0, addr => 6 },
ahci0 => { bus => 0, addr => 7 },
+ qga0 => { bus => 0, addr => 8 },
virtio0 => { bus => 0, addr => 10 },
virtio1 => { bus => 0, addr => 11 },
virtio2 => { bus => 0, addr => 12 },
net4 => { bus => 0, addr => 22 },
net5 => { bus => 0, addr => 23 },
#addr29 : usb-host (pve-usb.cfg)
+ 'pci.1' => { bus => 0, addr => 30 },
+ 'pci.2' => { bus => 0, addr => 31 },
+ 'net6' => { bus => 1, addr => 1 },
+ 'net7' => { bus => 1, addr => 2 },
+ 'net8' => { bus => 1, addr => 3 },
+ 'net9' => { bus => 1, addr => 4 },
+ 'net10' => { bus => 1, addr => 5 },
+ 'net11' => { bus => 1, addr => 6 },
+ 'net12' => { bus => 1, addr => 7 },
+ 'net13' => { bus => 1, addr => 8 },
+ 'net14' => { bus => 1, addr => 9 },
+ 'net15' => { bus => 1, addr => 10 },
+ 'net16' => { bus => 1, addr => 11 },
+ 'net17' => { bus => 1, addr => 12 },
+ 'net18' => { bus => 1, addr => 13 },
+ 'net19' => { bus => 1, addr => 14 },
+ 'net20' => { bus => 1, addr => 15 },
+ 'net21' => { bus => 1, addr => 16 },
+ 'net22' => { bus => 1, addr => 17 },
+ 'net23' => { bus => 1, addr => 18 },
+ 'net24' => { bus => 1, addr => 19 },
+ 'net25' => { bus => 1, addr => 20 },
+ 'net26' => { bus => 1, addr => 21 },
+ 'net27' => { bus => 1, addr => 22 },
+ 'net28' => { bus => 1, addr => 23 },
+ 'net29' => { bus => 1, addr => 24 },
+ 'net30' => { bus => 1, addr => 25 },
+ 'net31' => { bus => 1, addr => 26 },
+ 'virtio6' => { bus => 2, addr => 1 },
+ 'virtio7' => { bus => 2, addr => 2 },
+ 'virtio8' => { bus => 2, addr => 3 },
+ 'virtio9' => { bus => 2, addr => 4 },
+ 'virtio10' => { bus => 2, addr => 5 },
+ 'virtio11' => { bus => 2, addr => 6 },
+ 'virtio12' => { bus => 2, addr => 7 },
+ 'virtio13' => { bus => 2, addr => 8 },
+ 'virtio14' => { bus => 2, addr => 9 },
+ 'virtio15' => { bus => 2, addr => 10 },
};
if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) {
my $addr = sprintf("0x%x", $devices->{$id}->{addr});
- $res = ",bus=pci.$devices->{$id}->{bus},addr=$addr";
+ my $bus = $devices->{$id}->{bus};
+ $res = ",bus=pci.$bus,addr=$addr";
+ $bridges->{$bus} = 1 if $bridges;
}
return $res;
sub vm_balloonset {
my ($vmid, $value) = @_;
- vm_monitor_command($vmid, "balloon $value");
+ vm_mon_cmd($vmid, "balloon", value => $value);
}
# vzdump restore implementaion