]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
correctly handle undef values when calling qemu_block_set_io_throttle
[qemu-server.git] / PVE / QemuServer.pm
index 2202b7ac673854c86fe160746416383a43386b28..d2b942fec0d8364361639b611bfec3a834a66bb9 100644 (file)
@@ -21,7 +21,7 @@ use PVE::SafeSyslog;
 use Storable qw(dclone);
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::Storage;
-use PVE::Tools qw(run_command lock_file file_read_firstline);
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline);
 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;
@@ -166,7 +166,7 @@ my $confdesc = {
     hotplug => {
         optional => 1,
         type => 'boolean',
-        description => "Activate hotplug for disk and network device",
+        description => "Allow hotplug for disk and network device",
         default => 0,
     },
     reboot => {
@@ -358,6 +358,12 @@ EODESC
        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.",
     },
+    template => {
+       optional => 1,
+       type => 'boolean',
+       description => "Enable/disable Template.",
+       default => 0,
+    },
     args => {
        optional => 1,
        type => 'string',
@@ -398,7 +404,7 @@ EODESCR
        description => "Emulated CPU type.",
        type => 'string',
        enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom Conroe Penryn Nehalem Westmere SandyBridge Haswell Opteron_G1 Opteron_G2 Opteron_G3 Opteron_G4 Opteron_G5 host) ],
-       default => 'qemu64',
+       default => 'kvm64',
     },
     parent => get_standard_option('pve-snapshot-name', {
        optional => 1,
@@ -415,6 +421,13 @@ EODESCR
        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,
+    },
 };
 
 # what about other qemu settings ?
@@ -894,6 +907,10 @@ sub parse_drive {
 
     return undef if !$res->{file};
 
+    if($res->{file} =~ m/\.(raw|cow|qcow|qcow2|vmdk|cloop)$/){
+       $res->{format} = $1;
+    }
+
     return undef if $res->{cache} &&
        $res->{cache} !~ m/^(off|none|writethrough|writeback|unsafe|directsync)$/;
     return undef if $res->{snapshot} && $res->{snapshot} !~ m/^(on|off)$/;
@@ -1106,6 +1123,8 @@ sub print_drive_full {
        }
     }
 
+    $opts .= ",cache=none" if !$drive->{cache} && !drive_is_cdrom($drive);
+
     my $pathinfo = $path ? "file=$path," : '';
 
     return "${pathinfo}if=none,id=drive-$drive->{interface}$drive->{index}$opts";
@@ -1191,7 +1210,7 @@ sub parse_net {
 
        if ($kvp =~ m/^(ne2k_pci|e1000|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i) {
            my $model = lc($1);
-           my $mac = uc($3) || PVE::Tools::random_ether_addr();
+           my $mac = defined($3) ? uc($3) : PVE::Tools::random_ether_addr();
            $res->{model} = $model;
            $res->{macaddr} = $mac;
        } elsif ($kvp =~ m/^bridge=(\S+)$/) {
@@ -1471,6 +1490,18 @@ sub lock_config_full {
     return $res;
 }
 
+sub lock_config_mode {
+    my ($vmid, $timeout, $shared, $code, @param) = @_;
+
+    my $filename = config_file_lock($vmid);
+
+    my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
+
+    die $@ if $@;
+
+    return $res;
+}
+
 sub lock_config {
     my ($vmid, $code, @param) = @_;
 
@@ -1673,7 +1704,7 @@ sub write_vm_config {
     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' ||
@@ -1684,7 +1715,7 @@ sub write_vm_config {
 
            $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};
            }
@@ -1693,7 +1724,7 @@ sub write_vm_config {
 
     &$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
@@ -1797,7 +1828,7 @@ sub check_local_resources {
     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) = @_;
 
@@ -1816,6 +1847,40 @@ sub check_storage_availability {
    });
 }
 
+# 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) = @_;
 
@@ -1978,6 +2043,8 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
 
+        $d->{template} = is_template($conf);
+
        $res->{$vmid} = $d;
     }
 
@@ -2152,7 +2219,7 @@ sub foreach_volid {
 }
 
 sub config_to_command {
-    my ($storecfg, $vmid, $conf, $defaults) = @_;
+    my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
 
     my $cmd = [];
     my $globalFlags = [];
@@ -2190,6 +2257,9 @@ sub config_to_command {
 
     push @$cmd, '-daemonize';
 
+    $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"};
@@ -2200,13 +2270,7 @@ sub config_to_command {
 
     # enable absolute mouse coordinates (needed by vnc)
     my $tablet = defined($conf->{tablet}) ? $conf->{tablet} : $defaults->{tablet};
-    if ($tablet) {
-       if ($use_usb2) {
-           push @$devices, '-device', 'usb-tablet,bus=ehci.0,port=6';
-       } else {
-           push @$devices, '-usbdevice', 'tablet';
-       }
-    }
+    push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet;
 
     # host pci devices
     for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
@@ -2320,6 +2384,11 @@ sub config_to_command {
        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) {
@@ -2483,25 +2552,6 @@ sub pidfile_name {
     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) = @_;
 
@@ -2521,7 +2571,15 @@ sub vm_devices_list {
 sub vm_deviceplug {
     my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
 
-    return 1 if !check_running($vmid) || !$conf->{hotplug};
+    return 1 if !check_running($vmid);
+
+    if ($deviceid eq 'tablet') {
+       my $devicefull = "usb-tablet,id=tablet,bus=uhci.0,port=1";
+       qemu_deviceadd($vmid, $devicefull);
+       return 1;
+    }
+
+    return 1 if !$conf->{hotplug};
 
     my $devices_list = vm_devices_list($vmid);
     return 1 if defined($devices_list->{$deviceid});
@@ -2581,7 +2639,14 @@ sub vm_deviceplug {
 sub vm_deviceunplug {
     my ($vmid, $conf, $deviceid) = @_;
 
-    return 1 if !check_running ($vmid) || !$conf->{hotplug};
+    return 1 if !check_running ($vmid);
+
+    if ($deviceid eq 'tablet') {
+       qemu_devicedel($vmid, $deviceid);
+       return 1;
+    }
+
+    return 1 if !$conf->{hotplug};
 
     my $devices_list = vm_devices_list($vmid);
     return 1 if !defined($devices_list->{$deviceid});
@@ -2589,9 +2654,9 @@ sub vm_deviceunplug {
     die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
 
     if ($deviceid =~ m/^(virtio)(\d+)$/) {
-        return undef if !qemu_drivedel($vmid, $deviceid);
         qemu_devicedel($vmid, $deviceid);
         return undef if !qemu_devicedelverify($vmid, $deviceid);
+        return undef if !qemu_drivedel($vmid, $deviceid);
     }
 
     if ($deviceid =~ m/^(lsi)(\d+)$/) {
@@ -2604,9 +2669,9 @@ sub vm_deviceunplug {
     }
 
     if ($deviceid =~ m/^(net)(\d+)$/) {
-        return undef if !qemu_netdevdel($vmid, $deviceid);
         qemu_devicedel($vmid, $deviceid);
         return undef if !qemu_devicedelverify($vmid, $deviceid);
+        return undef if !qemu_netdevdel($vmid, $deviceid);
     }
 
     return 1;
@@ -2615,23 +2680,17 @@ sub vm_deviceunplug {
 sub qemu_deviceadd {
     my ($vmid, $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 "";
-    syslog("err", "error on hotplug device : $ret");
-    return undef;
+    $devicefull = "driver=".$devicefull;
+    my %options =  split(/[=,]/, $devicefull);
 
+    vm_mon_cmd($vmid, "device_add" , %options);
+    return 1;
 }
 
 sub qemu_devicedel {
     my($vmid, $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");
-    return undef;
+    my $ret = vm_mon_cmd($vmid, "device_del", id => $deviceid);
+    return 1;
 }
 
 sub qemu_driveadd {
@@ -2712,7 +2771,7 @@ sub qemu_bridgeadd {
     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);
 
@@ -2726,24 +2785,17 @@ sub qemu_netdevadd {
     my ($vmid, $conf, $device, $deviceid) = @_;
 
     my $netdev = print_netdev_full($vmid, $conf, $device, $deviceid);
-    my $ret = vm_human_monitor_command($vmid, "netdev_add $netdev");
-    $ret =~ s/^\s+//;
+    my %options =  split(/[=,]/, $netdev);
 
-    #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;
+    vm_mon_cmd($vmid, "netdev_add",  %options);
+    return 1;
 }
 
 sub qemu_netdevdel {
     my ($vmid, $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
-    return 1 if $ret eq "";
-    syslog("err", "deleting netdev failed: $ret");
-    return undef;
+    vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
+    return 1;
 }
 
 sub qemu_block_set_io_throttle {
@@ -2751,13 +2803,6 @@ sub qemu_block_set_io_throttle {
 
     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));
 
 }
@@ -2921,11 +2966,13 @@ sub qga_unfreezefs {
 }
 
 sub vm_start {
-    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused) = @_;
+    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine) = @_;
 
     lock_config($vmid, sub {
        my $conf = load_config($vmid, $migratedfrom);
 
+       die "you can't start a vm if it's a template\n" if is_template($conf);
+
        check_lock($conf) if !$skiplock;
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
@@ -2935,13 +2982,13 @@ sub vm_start {
        # 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) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
        my $migrate_port = 0;
 
        if ($statefile) {
            if ($statefile eq 'tcp') {
-               $migrate_port = next_migrate_port();
+               $migrate_port = PVE::Tools::next_migrate_port();
                my $migrate_uri = "tcp:localhost:${migrate_port}";
                push @$cmd, '-incoming', $migrate_uri;
                push @$cmd, '-S';
@@ -2985,7 +3032,7 @@ sub vm_start {
        }
        else{
 
-           if (!defined($conf->{balloon}) || $conf->{balloon}) {
+           if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
                vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024) 
                    if $conf->{balloon};
                vm_mon_cmd_nocheck($vmid, 'qom-set', 
@@ -3350,7 +3397,7 @@ sub print_pci_addr {
 
     my $res = '';
     my $devices = {
-       #addr1 : ide,parallel,serial (motherboard)
+       piix3 => { bus => 0, addr => 1 },
        #addr2 : first videocard
        balloon0 => { bus => 0, addr => 3 },
        watchdog => { bus => 0, addr => 4 },
@@ -3511,6 +3558,7 @@ sub restore_update_config_line {
     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
@@ -3541,6 +3589,7 @@ sub restore_update_config_line {
            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";
@@ -3561,6 +3610,7 @@ sub scan_volids {
     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;
        }
     }
@@ -3568,6 +3618,47 @@ sub scan_volids {
     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) = @_;
  
@@ -3575,6 +3666,12 @@ sub update_disksize {
 
     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)) {
@@ -3583,21 +3680,43 @@ sub update_disksize {
            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;
@@ -3617,7 +3736,13 @@ sub rescan {
            
        check_lock($conf);
 
-       my $changes = update_disksize($vmid, $conf, $volid_hash);
+       my $vm_volids = {};
+       foreach my $volid (keys %$volid_hash) {
+           my $info = $volid_hash->{$volid};
+           $vm_volids->{$volid} = $info if $info->{vmid} && $info->{vmid} == $vmid;
+       }
+
+       my $changes = update_disksize($vmid, $conf, $vm_volids);
 
        update_config_nolock($vmid, $conf, 1) if $changes;
     };
@@ -3761,6 +3886,12 @@ sub restore_vma_archive {
            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";
@@ -3769,7 +3900,8 @@ sub restore_vma_archive {
 
            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 'sheepdog' || $scfg->{type} eq 'rbd') {
                $write_zeros = 0;
            }
 
@@ -4088,6 +4220,9 @@ my $snapshot_prepare = sub {
 
        my $conf = load_config($vmid);
 
+       die "you can't take a snapshot if it's a template\n" 
+           if is_template($conf);
+
        check_lock($conf);
 
        $conf->{lock} = 'snapshot';
@@ -4110,6 +4245,10 @@ my $snapshot_prepare = sub {
        $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);
     };
 
@@ -4161,6 +4300,8 @@ sub snapshot_rollback {
 
        my $conf = load_config($vmid);
 
+       die "you can't rollback if vm is a template\n" if is_template($conf);
+
        $snap = $conf->{snapshots}->{$snapname};
 
        die "snapshot '$snapname' does not exist\n" if !defined($snap); 
@@ -4183,17 +4324,28 @@ sub snapshot_rollback {
            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);
        }
     };
 
@@ -4274,7 +4426,7 @@ sub snapshot_create {
     };
     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; };
@@ -4316,7 +4468,11 @@ sub snapshot_delete {
 
        my $conf = load_config($vmid);
 
-       check_lock($conf) if !$drivehash;
+       if (!$drivehash) {
+           check_lock($conf);
+           die "you can't delete a snapshot if vm is a template\n" 
+               if is_template($conf);
+       }
 
        $snap = $conf->{snapshots}->{$snapname};
 
@@ -4396,10 +4552,10 @@ sub snapshot_delete {
     lock_config($vmid, $updatefn);
 }
 
-sub has_feature{
+sub has_feature {
     my ($feature, $conf, $storecfg, $snapname, $running) = @_;
 
-    my $err = undef;
+    my $err;
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
 
@@ -4408,17 +4564,13 @@ sub has_feature{
        $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 ($vmid, $conf, $disk) = @_;
 
-    my $running = check_running($vmid);
-    die "you can't convert a vm to template if vm is running vm" if $running;
-
     my $storecfg = PVE::Storage::config();
-    my $i = 0;
 
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
@@ -4427,18 +4579,214 @@ sub template_create {
        return if $disk && $ds ne $disk;
 
        my $volid = $drive->{file};
+       return if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
+
        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);
     });
-    if($conf->{snapshots}){
-       delete $conf->{parent};
-       delete $conf->{snapshots};
-       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
-       #fixme : do we need to delete disks snapshots ?
+}
+
+sub is_template {
+    my ($conf) = @_;
+
+    return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
+
+sub qemu_img_convert {
+    my ($src_volid, $dst_volid, $size, $snapname) = @_;
+
+    my $storecfg = PVE::Storage::config();
+    my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1);
+    my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
+
+    if ($src_storeid && $dst_storeid) {
+       my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid);
+       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+
+       my $src_format = qemu_img_format($src_scfg, $src_volname);
+       my $dst_format = qemu_img_format($dst_scfg, $dst_volname);
+
+       my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
+       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+
+       my $cmd = [];
+       push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-C';
+       push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
+       push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path, $dst_path;
+
+       my $parser = sub {
+           my $line = shift;
+           if($line =~ m/\((\S+)\/100\%\)/){
+               my $percent = $1;
+               my $transferred = int($size * $percent / 100);
+               my $remaining = $size - $transferred;
+
+               print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
+           }
+
+       };
+
+       eval  { run_command($cmd, timeout => undef, outfunc => $parser); };
+       my $err = $@;
+       die "copy failed: $err" if $err;
+    }
+}
+
+sub qemu_img_format {
+    my ($scfg, $volname) = @_;
+
+    if ($scfg->{path} && $volname =~ m/\.(raw|qcow2|qed|vmdk)$/) {
+       return $1;
+    } elsif ($scfg->{type} eq 'iscsi') {
+       return "host_device";
+    } else { 
+       return "raw";
+    }
+}
+
+sub qemu_drive_mirror {
+    my ($vmid, $drive, $dst_volid, $vmiddst, $maxwait) = @_;
+
+    my $count = 1;
+    my $old_len = 0;
+    my $frozen = undef;
+
+    my $storecfg = PVE::Storage::config();
+    my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1);
+
+    if ($dst_storeid) {
+       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+
+       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 { 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 {
+           while (1) {
+               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);
+
+                print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent %\n";
+
+               last if ($stat->{len} == $stat->{offset});
+               if ($old_len == $stat->{offset}) {
+                   if ($maxwait && $count > $maxwait) {
+                   # if writes to disk occurs the disk needs to be freezed
+                   # to be able to complete the migration
+                       vm_suspend($vmid,1);
+                       $count = 0;
+                       $frozen = 1;
+                   } else {
+                       $count++ unless $frozen;
+                   }
+               } 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 (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
+           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';
 }
 
 1;