]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
add qemu_volume_snapshot_delete
[qemu-server.git] / PVE / QemuServer.pm
index 449de751fb4678d4182d4dfe7f2b6d140391e0ad..8aee7a1e1ab4ce719c5e959f8a2f68fdf7aaaa90 100644 (file)
@@ -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 },