]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
set template flag earlier
[qemu-server.git] / PVE / QemuServer.pm
index b67d259c049892bec54bb701eef228904bbbca86..194b678d12f1edfa5ceb5f64a319c3b6b57889d7 100644 (file)
@@ -27,6 +27,7 @@ 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 PVE::RPCEnvironment;
 use Time::HiRes qw(gettimeofday);
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
@@ -165,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 => {
@@ -205,8 +206,16 @@ my $confdesc = {
     balloon => {
         optional => 1,
         type => 'integer',
-        description => "Amount of target RAM for the VM in MB.",
-       minimum => 16,
+        description => "Amount of target RAM for the VM in MB. Using zero disables the ballon driver.",
+       minimum => 0,
+    },
+    shares => {
+        optional => 1,
+        type => 'integer',
+        description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning",
+       minimum => 0,
+       maximum => 50000,
+       default => 1000,
     },
     keyboard => {
        optional => 1,
@@ -349,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',
@@ -373,10 +388,10 @@ EODESCR
     },
     migrate_downtime => {
        optional => 1,
-       type => 'integer',
+       type => 'number',
        description => "Set maximum tolerated downtime (in seconds) for migrations.",
        minimum => 0,
-       default => 1,
+       default => 0.1,
     },
     cdrom => {
        optional => 1,
@@ -388,8 +403,8 @@ EODESCR
        optional => 1,
        description => "Emulated CPU type.",
        type => 'string',
-       enum => [ qw(486 athlon pentium pentium2 pentium3 coreduo core2duo kvm32 kvm64 qemu32 qemu64 phenom cpu64-rhel6 cpu64-rhel5 Conroe Penryn Nehalem Westmere Opteron_G1 Opteron_G2 Opteron_G3 host) ],
-       default => 'qemu64',
+       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 => 'kvm64',
     },
     parent => get_standard_option('pve-snapshot-name', {
        optional => 1,
@@ -444,7 +459,6 @@ my $nic_model_list = ['rtl8139', 'ne2k_pci', 'e1000',  'pcnet',  'virtio',
                      'ne2k_isa', 'i82551', 'i82557b', 'i82559er'];
 my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 
-# fixme:
 my $netdesc = {
     optional => 1,
     type => 'string', format => 'pve-qm-net',
@@ -648,7 +662,7 @@ sub kvm_user_version {
 
     my $tmp = `kvm -help 2>/dev/null`;
 
-    if ($tmp =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?) /) {
+    if ($tmp =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)[,\s]/) {
        $kvm_user_version = $2;
     }
 
@@ -1096,11 +1110,10 @@ sub print_drive_full {
        } else {
            $path = PVE::Storage::path($storecfg, $volid);
        }
-       if (!$drive->{cache} && ($path =~ m|^/dev/| || $path =~ m|\.raw$|)) {
-           $opts .= ",cache=none";
-       }
     }
 
+    $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";
@@ -1186,7 +1199,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+)$/) {
@@ -1249,8 +1262,6 @@ sub add_unused_volume {
     return $key;
 }
 
-# fixme: remove all thos $noerr parameters?
-
 PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
 sub verify_bootdisk {
     my ($value, $noerr) = @_;
@@ -1435,6 +1446,9 @@ sub check_type {
     } elsif ($type eq 'integer') {
        return int($1) if $value =~ m/^(\d+)$/;
        die "type check ('integer') failed - got '$value'\n";
+    } elsif ($type eq 'number') {
+        return $value if $value =~ m/^(\d+)(\.\d+)?$/;
+        die "type check ('number') failed - got '$value'\n";
     } elsif ($type eq 'string') {
        if (my $fmt = $confdesc->{$key}->{format}) {
            if ($fmt eq 'pve-qm-drive') {
@@ -1523,6 +1537,7 @@ sub destroy_vm {
        return if drive_is_cdrom($drive);
 
        my $volid = $drive->{file};
+
        return if !$volid || $volid =~ m|^/|;
 
        my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
@@ -1678,7 +1693,7 @@ sub write_vm_config {
            $cref->{$key} = $value;
 
            if (valid_drivename($key)) {
-               my $drive = PVE::QemuServer::parse_drive($key, $value);
+               my $drive = parse_drive($key, $value);
                $used_volids->{$drive->{file}} = 1 if $drive && $drive->{file};
            }
        }
@@ -1826,7 +1841,7 @@ sub check_cmdline {
        my @param = split(/\0/, $line);
 
        my $cmd = $param[0];
-       return if !$cmd || ($cmd !~ m|kvm$|);
+       return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m|qemu-system-x86_64$|);
 
        for (my $i = 0; $i < scalar (@param); $i++) {
            my $p = $param[$i];
@@ -1956,6 +1971,11 @@ sub vmstatus {
        $d->{name} = $conf->{name} || "VM $vmid";
        $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0;
 
+       if ($conf->{balloon}) {
+           $d->{balloon_min} = $conf->{balloon}*(1024*1024);
+           $d->{shares} = defined($conf->{shares}) ? $conf->{shares} : 1000;
+       }
+
        $d->{uptime} = 0;
        $d->{cpu} = 0;
        $d->{mem} = 0;
@@ -1966,6 +1986,8 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
 
+        $d->{template} = is_template($conf);
+
        $res->{$vmid} = $d;
     }
 
@@ -2029,6 +2051,25 @@ sub vmstatus {
 
     my $qmpclient = PVE::QMPClient->new();
 
+    my $ballooncb = sub {
+       my ($vmid, $resp) = @_;
+
+       my $info = $resp->{'return'};
+       return if !$info->{max_mem};
+       
+       my $d = $res->{$vmid};
+
+       # use memory assigned to VM
+       $d->{maxmem} = $info->{max_mem};
+       $d->{balloon} = $info->{actual};
+       
+       if (defined($info->{total_mem}) && defined($info->{free_mem})) {
+           $d->{mem} = $info->{total_mem} - $info->{free_mem};
+           $d->{freemem} = $info->{free_mem};
+       }
+
+    };
+
     my $blockstatscb = sub {
        my ($vmid, $resp) = @_;
        my $data = $resp->{'return'} || [];
@@ -2044,7 +2085,11 @@ sub vmstatus {
 
     my $statuscb = sub {
        my ($vmid, $resp) = @_;
+
        $qmpclient->queue_cmd($vmid, $blockstatscb, 'query-blockstats');
+       # this fails if ballon driver is not loaded, so this must be
+       # the last commnand (following command are aborted if this fails).
+       $qmpclient->queue_cmd($vmid, $ballooncb, 'query-balloon');
 
        my $status = 'unknown';
        if (!defined($status = $resp->{'return'}->{status})) {
@@ -2097,7 +2142,7 @@ sub foreach_volid {
        $volhash->{$volid} = $is_cdrom || 0;
     };
 
-    PVE::QemuServer::foreach_drive($conf, sub {
+    foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
        &$test_volid($drive->{file}, drive_is_cdrom($drive));
     });
@@ -2105,7 +2150,7 @@ sub foreach_volid {
     foreach my $snapname (keys %{$conf->{snapshots}}) {
        my $snap = $conf->{snapshots}->{$snapname};
        &$test_volid($snap->{vmstate}, 0);
-       PVE::QemuServer::foreach_drive($snap, sub {
+       foreach_drive($snap, sub {
            my ($ds, $drive) = @_;
            &$test_volid($drive->{file}, drive_is_cdrom($drive));
         });
@@ -2155,6 +2200,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"};
@@ -2165,13 +2213,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++)  {
@@ -2310,8 +2352,11 @@ sub config_to_command {
        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};
+    # enable balloon by default, unless explicitly disabled
+    if (!defined($conf->{balloon}) || $conf->{balloon}) {
+       $pciaddr = print_pci_addr("balloon0", $bridges);
+       push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr";
+    }
 
     if ($conf->{watchdog}) {
        my $wdopts = parse_watchdog($conf->{watchdog});
@@ -2483,7 +2528,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});
@@ -2543,7 +2596,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});
@@ -2551,9 +2611,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+)$/) {
@@ -2566,9 +2626,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;
@@ -2577,23 +2637,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 {
@@ -2688,24 +2742,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 {
@@ -2835,7 +2882,7 @@ sub vm_monitor_command {
 sub qemu_block_resize {
     my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
 
-    my $running = PVE::QemuServer::check_running($vmid);
+    my $running = check_running($vmid);
 
     return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
 
@@ -2848,7 +2895,7 @@ sub qemu_block_resize {
 sub qemu_volume_snapshot {
     my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
 
-    my $running = PVE::QemuServer::check_running($vmid);
+    my $running = check_running($vmid);
 
     return if !PVE::Storage::volume_snapshot($storecfg, $volid, $snap, $running);
 
@@ -2861,7 +2908,7 @@ sub qemu_volume_snapshot {
 sub qemu_volume_snapshot_delete {
     my ($vmid, $deviceid, $storecfg, $volid, $snap) = @_;
 
-    my $running = PVE::QemuServer::check_running($vmid);
+    my $running = check_running($vmid);
 
     return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
 
@@ -2883,11 +2930,13 @@ sub qga_unfreezefs {
 }
 
 sub vm_start {
-    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom) = @_;
+    my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused) = @_;
 
     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);
@@ -2910,6 +2959,8 @@ sub vm_start {
            } else {
                push @$cmd, '-loadstate', $statefile;
            }
+       } elsif ($paused) {
+           push @$cmd, '-S';
        }
 
        # host pci devices
@@ -2933,34 +2984,27 @@ sub vm_start {
        print "migration listens on port $migrate_port\n" if $migrate_port;
 
        if ($statefile && $statefile ne 'tcp')  {
-           eval { vm_mon_cmd($vmid, "cont"); };
+           eval { vm_mon_cmd_nocheck($vmid, "cont"); };
            warn $@ if $@;
        }
 
-       # 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]); };
+           eval { vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => [$capabilities]); };
+       }
+       else{
+
+           if (!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', 
+                           path => "machine/peripheral/balloon0", 
+                           property => "guest-stats-polling-interval", 
+                           value => 2);
+           }
        }
-
-       vm_balloonset($vmid, $conf->{balloon}) if $conf->{balloon};
-
     });
 }
 
@@ -2991,7 +3035,7 @@ sub vm_qmp_command {
  
     eval {
        die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
-       my $sname = PVE::QemuServer::qmp_socket($vmid);
+       my $sname = qmp_socket($vmid);
        if (-e $sname) {
            my $qmpclient = PVE::QMPClient->new();
 
@@ -3182,7 +3226,7 @@ sub vm_suspend {
 
        my $conf = load_config($vmid);
 
-       check_lock($conf) if !$skiplock;
+       check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
 
        vm_mon_cmd($vmid, "stop");
     });
@@ -3195,7 +3239,7 @@ sub vm_resume {
 
        my $conf = load_config($vmid);
 
-       check_lock($conf) if !$skiplock;
+       check_lock($conf) if !($skiplock || ($conf->{lock} && $conf->{lock} eq 'backup'));
 
        vm_mon_cmd($vmid, "cont");
     });
@@ -3317,7 +3361,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 },
@@ -3390,15 +3434,9 @@ sub print_pci_addr {
 
 }
 
-sub vm_balloonset {
-    my ($vmid, $value) = @_;
-
-    vm_mon_cmd($vmid, "balloon", value => $value*1024*1024);
-}
-
 # vzdump restore implementaion
 
-sub archive_read_firstfile {
+sub tar_archive_read_firstfile {
     my $archive = shift;
 
     die "ERROR: file '$archive' does not exist\n" if ! -f $archive;
@@ -3416,8 +3454,8 @@ sub archive_read_firstfile {
     return $firstfile;
 }
 
-sub restore_cleanup {
-    my $statfile = shift;
+sub tar_restore_cleanup {
+    my ($storecfg, $statfile) = @_;
 
     print STDERR "starting cleanup\n";
 
@@ -3429,8 +3467,7 @@ sub restore_cleanup {
                    if ($volid =~ m|^/|) {
                        unlink $volid || die 'unlink failed\n';
                    } else {
-                       my $cfg = cfs_read_file('storage.cfg');
-                       PVE::Storage::vdisk_free($cfg, $volid);
+                       PVE::Storage::vdisk_free($storecfg, $volid);
                    }
                    print STDERR "temporary volume '$volid' sucessfuly removed\n";
                };
@@ -3446,12 +3483,417 @@ sub restore_cleanup {
 sub restore_archive {
     my ($archive, $vmid, $user, $opts) = @_;
 
+    my $format = $opts->{format};
+    my $comp;
+
+    if ($archive =~ m/\.tgz$/ || $archive =~ m/\.tar\.gz$/) {
+       $format = 'tar' if !$format;
+       $comp = 'gzip';
+    } elsif ($archive =~ m/\.tar$/) {
+       $format = 'tar' if !$format;
+    } elsif ($archive =~ m/.tar.lzo$/) {
+       $format = 'tar' if !$format;
+       $comp = 'lzop';
+    } elsif ($archive =~ m/\.vma$/) {
+       $format = 'vma' if !$format;
+    } elsif ($archive =~ m/\.vma\.gz$/) {
+       $format = 'vma' if !$format;
+       $comp = 'gzip';
+    } elsif ($archive =~ m/\.vma\.lzo$/) {
+       $format = 'vma' if !$format;
+       $comp = 'lzop';
+    } else {
+       $format = 'vma' if !$format; # default
+    }
+
+    # try to detect archive format
+    if ($format eq 'tar') {
+       return restore_tar_archive($archive, $vmid, $user, $opts);
+    } else {
+       return restore_vma_archive($archive, $vmid, $user, $opts, $comp);
+    }
+}
+
+sub restore_update_config_line {
+    my ($outfd, $cookie, $vmid, $map, $line, $unique) = @_;
+
+    return if $line =~ m/^\#qmdump\#/;
+    return if $line =~ m/^\#vzdump\#/;
+    return if $line =~ m/^lock:/;
+    return if $line =~ m/^unused\d+:/;
+    return if $line =~ m/^parent:/;
+
+    if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
+       # try to convert old 1.X settings
+       my ($id, $ind, $ethcfg) = ($1, $2, $3);
+       foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
+           my ($model, $macaddr) = split(/\=/, $devconfig);
+           $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $unique;
+           my $net = {
+               model => $model,
+               bridge => "vmbr$ind",
+               macaddr => $macaddr,
+           };
+           my $netstr = print_net($net);
+
+           print $outfd "net$cookie->{netcount}: $netstr\n";
+           $cookie->{netcount}++;
+       }
+    } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
+       my ($id, $netstr) = ($1, $2);
+       my $net = parse_net($netstr);
+       $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
+       $netstr = print_net($net);
+       print $outfd "$id: $netstr\n";
+    } elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
+       my $virtdev = $1;
+       my $value = $3;
+       if ($line =~ m/backup=no/) {
+           print $outfd "#$line";
+       } elsif ($virtdev && $map->{$virtdev}) {
+           my $di = parse_drive($virtdev, $value);
+           $di->{file} = $map->{$virtdev};
+           $value = print_drive($vmid, $di);
+           print $outfd "$virtdev: $value\n";
+       } else {
+           print $outfd $line;
+       }
+    } else {
+       print $outfd $line;
+    }
+}
+
+sub scan_volids {
+    my ($cfg, $vmid) = @_;
+
+    my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid);
+
+    my $volid_hash = {};
+    foreach my $storeid (keys %$info) {
+       foreach my $item (@{$info->{$storeid}}) {
+           next if !($item->{volid} && $item->{size});
+           $volid_hash->{$item->{volid}} = $item;
+       }
+    }
+
+    return $volid_hash;
+}
+
+sub update_disksize {
+    my ($vmid, $conf, $volid_hash) = @_;
+    my $changes;
+
+    my $used = {};
+
+    # update size info
+    foreach my $opt (keys %$conf) {
+       if (valid_drivename($opt)) {
+           my $drive = parse_drive($opt, $conf->{$opt});
+           my $volid = $drive->{file};
+           next if !$volid;
+
+           $used->{$volid} = 1;
+
+           next if drive_is_cdrom($drive);
+           next if !$volid_hash->{$volid};
+
+           $drive->{size} = $volid_hash->{$volid}->{size};
+           $changes = 1;
+           $conf->{$opt} = print_drive($vmid, $drive);
+       }
+    }
+
+    foreach my $volid (sort keys %$volid_hash) {
+       next if $volid =~ m/vm-$vmid-state-/;
+       next if $used->{$volid};
+       $changes = 1;
+       add_unused_volume($conf, $volid);
+    }
+
+    return $changes;
+}
+
+sub rescan {
+    my ($vmid, $nolock) = @_;
+
+    my $cfg = PVE::Cluster::cfs_read_file("storage.cfg");
+
+    my $volid_hash = scan_volids($cfg, $vmid);
+
+    my $updatefn =  sub {
+       my ($vmid) = @_;
+
+       my $conf = load_config($vmid);
+           
+       check_lock($conf);
+
+       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;
+    };
+
+    if (defined($vmid)) {
+       if ($nolock) {
+           &$updatefn($vmid);
+       } else {
+           lock_config($vmid, $updatefn, $vmid);
+       }
+    } else {
+       my $vmlist = config_list();
+       foreach my $vmid (keys %$vmlist) {
+           if ($nolock) {
+               &$updatefn($vmid);
+           } else {
+               lock_config($vmid, $updatefn, $vmid);
+           }    
+       }
+    }
+}
+
+sub restore_vma_archive {
+    my ($archive, $vmid, $user, $opts, $comp) = @_;
+
+    my $input = $archive eq '-' ? "<&STDIN" : undef;
+    my $readfrom = $archive;
+
+    my $uncomp = '';
+    if ($comp) {
+       $readfrom = '-';
+       my $qarchive = PVE::Tools::shellquote($archive);
+       if ($comp eq 'gzip') {
+           $uncomp = "zcat $qarchive|";
+       } elsif ($comp eq 'lzop') {
+           $uncomp = "lzop -d -c $qarchive|";
+       } else {
+           die "unknown compression method '$comp'\n";
+       }
+       
+    }
+
+    my $tmpdir = "/var/tmp/vzdumptmp$$";
+    rmtree $tmpdir;
+
+    # disable interrupts (always do cleanups)
+    local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub {
+       warn "got interrupt - ignored\n";
+    };
+
+    my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
+    POSIX::mkfifo($mapfifo, 0600);
+    my $fifofh;
+
+    my $openfifo = sub {
+       open($fifofh, '>', $mapfifo) || die $!;
+    };
+
+    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+
+    my $oldtimeout;
+    my $timeout = 5;
+
+    my $devinfo = {};
+
+    my $rpcenv = PVE::RPCEnvironment::get();
+
+    my $conffile = config_file($vmid);
+    my $tmpfn = "$conffile.$$.tmp";
+
+    # Note: $oldconf is undef if VM does not exists
+    my $oldconf = PVE::Cluster::cfs_read_file(cfs_config_path($vmid));
+
+    my $print_devmap = sub {
+       my $virtdev_hash = {};
+
+       my $cfgfn = "$tmpdir/qemu-server.conf";
+
+       # we can read the config - that is already extracted
+       my $fh = IO::File->new($cfgfn, "r") ||
+           "unable to read qemu-server.conf - $!\n";
+
+       while (defined(my $line = <$fh>)) {
+           if ($line =~ m/^\#qmdump\#map:(\S+):(\S+):(\S*):(\S*):$/) {
+               my ($virtdev, $devname, $storeid, $format) = ($1, $2, $3, $4);
+               die "archive does not contain data for drive '$virtdev'\n"
+                   if !$devinfo->{$devname};
+               if (defined($opts->{storage})) {
+                   $storeid = $opts->{storage} || 'local';
+               } elsif (!$storeid) {
+                   $storeid = 'local';
+               }
+               $format = 'raw' if !$format;
+               $devinfo->{$devname}->{devname} = $devname;
+               $devinfo->{$devname}->{virtdev} = $virtdev;
+               $devinfo->{$devname}->{format} = $format;
+               $devinfo->{$devname}->{storeid} = $storeid;
+
+               # check permission on storage 
+               my $pool = $opts->{pool}; # todo: do we need that?
+               if ($user ne 'root@pam') {
+                   $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
+               }
+
+               $virtdev_hash->{$virtdev} = $devinfo->{$devname};
+           }
+       }
+
+       foreach my $devname (keys %$devinfo) {
+           die "found no device mapping information for device '$devname'\n" 
+               if !$devinfo->{$devname}->{virtdev};        
+       }
+
+       my $cfg = cfs_read_file('storage.cfg');
+
+       # create empty/temp config
+       if ($oldconf) { 
+           PVE::Tools::file_set_contents($conffile, "memory: 128\n");
+           foreach_drive($oldconf, sub {
+               my ($ds, $drive) = @_;
+
+               return if drive_is_cdrom($drive);
+
+               my $volid = $drive->{file};
+
+               return if !$volid || $volid =~ m|^/|;
+
+               my ($path, $owner) = PVE::Storage::path($cfg, $volid);
+               return if !$path || !$owner || ($owner != $vmid);
+
+               # Note: only delete disk we want to restore
+               # other volumes will become unused
+               if ($virtdev_hash->{$ds}) {
+                   PVE::Storage::vdisk_free($cfg, $volid);
+               }
+           });
+       }
+
+       my $map = {};
+       foreach my $virtdev (sort keys %$virtdev_hash) {
+           my $d = $virtdev_hash->{$virtdev};
+           my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
+           my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+           my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+                                                 $d->{format}, undef, $alloc_size);
+           print STDERR "new volume ID is '$volid'\n";
+           $d->{volid} = $volid;
+           my $path = PVE::Storage::path($cfg, $volid);
+
+           my $write_zeros = 1;
+           # fixme: what other storages types initialize volumes with zero?
+           if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || 
+               $scfg->{type} eq 'sheepdog' || $scfg->{type} eq 'rbd') {
+               $write_zeros = 0;
+           }
+
+           print $fifofh "${write_zeros}:$d->{devname}=$path\n";
+
+           print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+           $map->{$virtdev} = $volid;
+       }
+
+       $fh->seek(0, 0) || die "seek failed - $!\n";
+
+       my $outfd = new IO::File ($tmpfn, "w") ||
+           die "unable to write config for VM $vmid\n";
+
+       my $cookie = { netcount => 0 };
+       while (defined(my $line = <$fh>)) {
+           restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});  
+       }
+
+       $fh->close();
+       $outfd->close();
+    };
+
+    eval {
+       # enable interrupts
+       local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
+           die "interrupted by signal\n";
+       };
+       local $SIG{ALRM} = sub { die "got timeout\n"; };
+
+       $oldtimeout = alarm($timeout);
+
+       my $parser = sub {
+           my $line = shift;
+
+           print "$line\n";
+
+           if ($line =~ m/^DEV:\sdev_id=(\d+)\ssize:\s(\d+)\sdevname:\s(\S+)$/) {
+               my ($dev_id, $size, $devname) = ($1, $2, $3);
+               $devinfo->{$devname} = { size => $size, dev_id => $dev_id };
+           } elsif ($line =~ m/^CTIME: /) {
+               &$print_devmap();
+               print $fifofh "done\n";
+               my $tmp = $oldtimeout || 0;
+               $oldtimeout = undef;
+               alarm($tmp);
+               close($fifofh);
+           }
+       };
+       print "restore vma archive: $cmd\n";
+       run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+    };
+    my $err = $@;
+
+    alarm($oldtimeout) if $oldtimeout;
+
+    unlink $mapfifo;
+
+    if ($err) {
+       rmtree $tmpdir;
+       unlink $tmpfn;
+
+       my $cfg = cfs_read_file('storage.cfg');
+       foreach my $devname (keys %$devinfo) {
+           my $volid = $devinfo->{$devname}->{volid};
+           next if !$volid;
+           eval {
+               if ($volid =~ m|^/|) {
+                   unlink $volid || die 'unlink failed\n';
+               } else {
+                   PVE::Storage::vdisk_free($cfg, $volid);
+               }
+               print STDERR "temporary volume '$volid' sucessfuly removed\n";
+           };
+           print STDERR "unable to cleanup '$volid' - $@" if $@;
+       }
+       die $err;
+    }
+
+    rmtree $tmpdir;
+
+    rename($tmpfn, $conffile) ||
+       die "unable to commit configuration file '$conffile'\n";
+
+    PVE::Cluster::cfs_update(); # make sure we read new file
+
+    eval { rescan($vmid, 1); };
+    warn $@ if $@;
+}
+
+sub restore_tar_archive {
+    my ($archive, $vmid, $user, $opts) = @_;
+
     if ($archive ne '-') {
-       my $firstfile = archive_read_firstfile($archive);
+       my $firstfile = tar_archive_read_firstfile($archive);
        die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
            if $firstfile ne 'qemu-server.conf';
     }
 
+    my $storecfg = cfs_read_file('storage.cfg');
+
+    # destroy existing data - keep empty config
+    my $vmcfgfn = PVE::QemuServer::config_file($vmid);
+    destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
+
     my $tocmd = "/usr/lib/qemu-server/qmextract";
 
     $tocmd .= " --storage " . PVE::Tools::shellquote($opts->{storage}) if $opts->{storage};
@@ -3471,7 +3913,7 @@ sub restore_archive {
     local $ENV{VZDUMP_VMID} = $vmid;
     local $ENV{VZDUMP_USER} = $user;
 
-    my $conffile = PVE::QemuServer::config_file($vmid);
+    my $conffile = config_file($vmid);
     my $tmpfn = "$conffile.$$.tmp";
 
     # disable interrupts (always do cleanups)
@@ -3517,50 +3959,9 @@ sub restore_archive {
        my $outfd = new IO::File ($tmpfn, "w") ||
            die "unable to write config for VM $vmid\n";
 
-       my $netcount = 0;
-
+       my $cookie = { netcount => 0 };
        while (defined (my $line = <$srcfd>)) {
-           next if $line =~ m/^\#vzdump\#/;
-           next if $line =~ m/^lock:/;
-           next if $line =~ m/^unused\d+:/;
-
-           if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
-               # try to convert old 1.X settings
-               my ($id, $ind, $ethcfg) = ($1, $2, $3);
-               foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
-                   my ($model, $macaddr) = split(/\=/, $devconfig);
-                   $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $opts->{unique};
-                   my $net = {
-                       model => $model,
-                       bridge => "vmbr$ind",
-                       macaddr => $macaddr,
-                   };
-                   my $netstr = print_net($net);
-                   print $outfd "net${netcount}: $netstr\n";
-                   $netcount++;
-               }
-           } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && ($opts->{unique})) {
-               my ($id, $netstr) = ($1, $2);
-               my $net = parse_net($netstr);
-               $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
-               $netstr = print_net($net);
-               print $outfd "$id: $netstr\n";
-           } elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
-               my $virtdev = $1;
-               my $value = $2;
-               if ($line =~ m/backup=no/) {
-                   print $outfd "#$line";
-               } elsif ($virtdev && $map->{$virtdev}) {
-                   my $di = PVE::QemuServer::parse_drive($virtdev, $value);
-                   $di->{file} = $map->{$virtdev};
-                   $value = PVE::QemuServer::print_drive($vmid, $di);
-                   print $outfd "$virtdev: $value\n";
-               } else {
-                   print $outfd $line;
-               }
-           } else {
-               print $outfd $line;
-           }
+           restore_update_config_line($outfd, $cookie, $vmid, $map, $line, $opts->{unique});  
        }
 
        $srcfd->close();
@@ -3572,7 +3973,7 @@ sub restore_archive {
 
        unlink $tmpfn;
 
-       restore_cleanup("$tmpdir/qmrestore.stat") if !$opts->{info};
+       tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
 
        die $err;
     }
@@ -3581,6 +3982,11 @@ sub restore_archive {
 
     rename $tmpfn, $conffile ||
        die "unable to commit configuration file '$conffile'\n";
+
+    PVE::Cluster::cfs_update(); # make sure we read new file
+
+    eval { rescan($vmid, 1); };
+    warn $@ if $@;
 };
 
 
@@ -3700,6 +4106,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';
@@ -3708,28 +4117,7 @@ my $snapshot_prepare = sub {
            if defined($conf->{snapshots}->{$snapname}); 
 
        my $storecfg = PVE::Storage::config();
-
-       foreach_drive($conf, sub {
-           my ($ds, $drive) = @_;
-
-           return if drive_is_cdrom($drive);
-           my $volid = $drive->{file};
-
-           my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
-           if ($storeid) {
-               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
-               die "can't snapshot volume '$volid'\n"          
-                   if !(($scfg->{path} && $volname =~ m/\.qcow2$/) ||
-                        ($scfg->{type} eq 'nexenta') || 
-                        ($scfg->{type} eq 'rbd') || 
-                        ($scfg->{type} eq 'sheepdog'));
-           } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
-               die "snapshot device '$volid' is not possible\n";
-           } else {
-               die "can't snapshot volume '$volid'\n";
-           }
-       });
-
+       die "snapshot feature is not available" if !has_feature('snapshot', $conf, $storecfg);
 
        $snap = $conf->{snapshots}->{$snapname} = {};
 
@@ -3794,6 +4182,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); 
@@ -3826,10 +4216,8 @@ sub snapshot_rollback {
 
        if (!$prepare && $snap->{vmstate}) {
            my $statefile = PVE::Storage::path($storecfg, $snap->{vmstate});
-           # fixme: this only forws for files currently
            vm_start($storecfg, $vmid, $statefile);
        }
-
     };
 
     lock_config($vmid, $updatefn);
@@ -3853,7 +4241,7 @@ my $savevm_wait = sub {
     my ($vmid) = @_;
 
     for(;;) {
-       my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-savevm");
+       my $stat = vm_mon_cmd_nocheck($vmid, "query-savevm");
        if (!$stat->{status}) {
            die "savevm not active\n";
        } elsif ($stat->{status} eq 'active') {
@@ -3951,7 +4339,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};
 
@@ -4031,4 +4423,49 @@ sub snapshot_delete {
     lock_config($vmid, $updatefn);
 }
 
+sub has_feature {
+    my ($feature, $conf, $storecfg, $snapname, $running) = @_;
+
+    my $err = undef;
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       return if drive_is_cdrom($drive);
+       my $volid = $drive->{file};
+       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running);
+    });
+
+    return 1 if !$err;
+}
+
+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\n" if $running;
+
+    my $storecfg = PVE::Storage::config();
+
+    foreach_drive($conf, sub {
+       my ($ds, $drive) = @_;
+
+       return if drive_is_cdrom($drive);
+       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);
+    });
+}
+
+sub is_template {
+    my ($conf) = @_;
+
+    return 1 if defined $conf->{template} && $conf->{template} == 1;
+}
+
 1;