X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=8aee7a1e1ab4ce719c5e959f8a2f68fdf7aaaa90;hb=fc46aff9fa36c06eeac6e02fa83c74f539ec9d52;hp=449de751fb4678d4182d4dfe7f2b6d140391e0ad;hpb=a265061979c5ab384adf10786b1f0cfe76eb4a6f;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 449de75..8aee7a1 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -217,7 +217,7 @@ my $confdesc = { optional => 1, type => 'string', description => "scsi controller model", - enum => [qw(lsi virtio-scsi-pci)], + enum => [qw(lsi virtio-scsi-pci megasas)], default => 'lsi', }, description => { @@ -258,7 +258,7 @@ EODESC 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, @@ -287,6 +287,12 @@ EODESC description => "Enable/disable ACPI.", default => 1, }, + agent => { + optional => 1, + type => 'boolean', + description => "Enable/disable Qemu GuestAgent.", + default => 0, + }, kvm => { optional => 1, type => 'boolean', @@ -774,6 +780,40 @@ sub create_conf_nolock { 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] @@ -798,13 +838,18 @@ sub parse_drive { 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|bps_rd|bps_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|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/=/) { @@ -831,32 +876,23 @@ sub parse_drive { return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/; return undef if $res->{aio} && $res->{aio} !~ m/^(native|threads)$/; - return undef if $res->{bps_rd} && $res->{bps}; - return undef if $res->{bps_wr} && $res->{bps}; + + 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->{bps} && $res->{bps} !~ m/^\d+$/; - return undef if $res->{bps_rd} && $res->{bps_rd} !~ m/^\d+$/; - return undef if $res->{bps_wr} && $res->{bps_wr} !~ m/^\d+$/; 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 $res->{size} !~ m/^([1-9]\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; - } - } - $res->{size} = int($size); + return undef if !defined($res->{size} = &$parse_size($res->{size})); } if ($res->{media} && ($res->{media} eq 'cdrom')) { @@ -873,30 +909,13 @@ sub parse_drive { return $res; } -my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio bps bps_rd bps_wr iops iops_rd iops_wr); - -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"; -}; +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}; } @@ -988,7 +1007,13 @@ sub print_drivedevice_full { } 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); + } } if (!$conf->{scsihw} || $conf->{scsihw} eq 'lsi'){ @@ -1029,6 +1054,11 @@ sub print_drive_full { $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}; @@ -1499,9 +1529,9 @@ sub destroy_vm { } 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); @@ -1757,9 +1787,9 @@ sub check_cmdline { } 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; @@ -1940,7 +1970,7 @@ sub vmstatus { } } - return $res if !$full; + return $res if !$full; my $qmpclient = PVE::QMPClient->new(); @@ -2037,6 +2067,8 @@ sub config_to_command { 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"}; @@ -2184,6 +2216,15 @@ sub config_to_command { #my $soundhw = $conf->{soundhw} || $defaults->{soundhw}; #push @$cmd, '-soundhw', 'es1370'; #push @$cmd, '-soundhw', $soundhw if $soundhw; + + 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}; @@ -2302,6 +2343,11 @@ sub qmp_socket { return "${var_run_tmpdir}/$vmid.qmp"; } +sub qga_socket { + my ($vmid) = @_; + return "${var_run_tmpdir}/$vmid.qga"; +} + sub pidfile_name { my ($vmid) = @_; return "${var_run_tmpdir}/$vmid.pid"; @@ -2586,7 +2632,7 @@ sub qemu_block_set_io_throttle { } -# old code, only used to shutdown old VM after update +# old code, only used to shutdown old VM after update sub __read_avail { my ($fh, $timeout) = @_; @@ -2615,14 +2661,14 @@ sub __read_avail { } die "monitor read timeout\n" if !scalar(@ready); - + return $res; } -# old code, only used to shutdown old VM after update +# old code, only used to shutdown old VM after update sub vm_monitor_command { my ($vmid, $cmdstr, $nocheck) = @_; - + my $res; eval { @@ -2676,9 +2722,9 @@ sub vm_monitor_command { if ($res = __read_avail($sock, $timeout)) { my @lines = split("\r?\n", $res); - + shift @lines if $lines[0] !~ m/^unknown command/; # skip echo - + $res = join("\n", @lines); $res .= "\n"; } @@ -2690,7 +2736,7 @@ sub vm_monitor_command { syslog("err", "VM $vmid monitor command failed - $err"); die $err; } - + return $res; } @@ -2707,15 +2753,75 @@ sub qemu_block_resize { } +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) = @_; + my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom) = @_; lock_config($vmid, sub { - my $conf = load_config($vmid); + my $conf = load_config($vmid, $migratedfrom); check_lock($conf) if !$skiplock; - die "VM $vmid already running\n" if check_running($vmid); + die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom); my $migrate_uri; my $migrate_port = 0; @@ -2735,6 +2841,9 @@ sub vm_start { 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++) { @@ -2779,6 +2888,13 @@ sub vm_start { 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}; }); @@ -2803,13 +2919,19 @@ sub vm_qmp_command { 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) { + if (-e $sname) { my $qmpclient = PVE::QMPClient->new(); - $res = $qmpclient->cmd($vmid, $cmd); + $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}}); @@ -2831,7 +2953,7 @@ sub vm_human_monitor_command { my $res; - my $cmd = { + my $cmd = { execute => 'human-monitor-command', arguments => { 'command-line' => $cmdline}, }; @@ -2894,7 +3016,7 @@ sub vm_stop_cleanup { 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"; } }; @@ -2905,12 +3027,18 @@ sub vm_stop_cleanup { # 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); @@ -2920,8 +3048,14 @@ sub vm_stop { 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) { $nocheck ? vm_mon_cmd_nocheck($vmid, "system_powerdown") : vm_mon_cmd($vmid, "system_powerdown"); @@ -3010,7 +3144,7 @@ sub vm_sendkey { lock_config($vmid, sub { my $conf = load_config($vmid); - + # there is no qmp command, so we use the human monitor command vm_human_monitor_command($vmid, "sendkey $key"); }); @@ -3127,6 +3261,7 @@ sub print_pci_addr { 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 },