From 3e16d5fc605b1a5475c1e82630a7e37211626e65 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 17 Oct 2011 13:49:48 +0200 Subject: [PATCH] implement qmrestore Restore is a special case of create_vm. --- Makefile | 9 +- PVE/API2/Qemu.pm | 67 ++++-- PVE/QemuServer.pm | 161 ++++++++++++- PVE/VZDump/QemuServer.pm | 3 +- qmextract | 186 +++++++++++++++ qmrestore | 485 +++++++-------------------------------- 6 files changed, 489 insertions(+), 422 deletions(-) create mode 100755 qmextract diff --git a/Makefile b/Makefile index 6217a8e6..1638f1d2 100644 --- a/Makefile +++ b/Makefile @@ -54,10 +54,13 @@ sparsecp: sparsecp.c utils.c qm.1.pod: qm PVE/QemuServer.pm perl -I. ./qm printmanpod >$@ +qmrestore.1.pod: qmrestore + perl -I. ./qmrestore printmanpod >$@ + vm.conf.5.pod: gen-vmconf-pod.pl PVE/QemuServer.pm perl -I. ./gen-vmconf-pod.pl >$@ -PKGSOURCES=qm qm.1.gz qm.1.pod qmrestore qmrestore.1.gz sparsecp vmtar qemu.init.d qmupdate control vm.conf.5.pod vm.conf.5.gz +PKGSOURCES=qm qm.1.gz qm.1.pod qmrestore qmrestore.1.pod qmrestore.1.gz qmextract sparsecp vmtar qemu.init.d qmupdate control vm.conf.5.pod vm.conf.5.gz .PHONY: install install: ${PKGSOURCES} @@ -78,9 +81,11 @@ install: ${PKGSOURCES} install -m 0755 pve-bridge ${DESTDIR}${VARLIBDIR}/pve-bridge install -s -m 0755 vmtar ${DESTDIR}${LIBDIR} install -s -m 0755 sparsecp ${DESTDIR}${LIBDIR} + install -m 0755 qmextract ${DESTDIR}${LIBDIR} install -m 0644 qm.1.gz ${DESTDIR}/usr/share/man/man1/ install -m 0644 qm.1.pod ${DESTDIR}/${PODDIR} install -m 0644 qmrestore.1.gz ${DESTDIR}/usr/share/man/man1/ + install -m 0644 qmrestore.1.pod ${DESTDIR}/${PODDIR} install -m 0644 vm.conf.5.pod ${DESTDIR}/${PODDIR} install -m 0644 vm.conf.5.gz ${DESTDIR}/usr/share/man/man5/ @@ -114,7 +119,7 @@ upload: .PHONY: clean clean: - rm -rf build *.deb qm.1.gz control vzsyscalls.ph _h2ph_pre.ph ${PACKAGE}-*.tar.gz dist *.1,gz *.pod vmtar sparsecp + rm -rf build *.deb control vzsyscalls.ph _h2ph_pre.ph ${PACKAGE}-*.tar.gz dist *.1.gz *.pod vmtar sparsecp find . -name '*~' -exec rm {} ';' diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index c6e8ea0c..aedfb89b 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -66,7 +66,7 @@ __PACKAGE__->register_method({ name => 'create_vm', path => '', method => 'POST', - description => "Create new virtual machine.", + description => "Create or restore a virtual machine.", protected => 1, proxyto => 'node', parameters => { @@ -75,6 +75,21 @@ __PACKAGE__->register_method({ { node => get_standard_option('pve-node'), vmid => get_standard_option('pve-vmid'), + archive => { + description => "The backup file.", + type => 'string', + optional => 1, + maxLength => 255, + }, + storage => get_standard_option('pve-storage-id', { + description => "Default storage.", + optional => 1, + }), + force => { + optional => 1, + type => 'boolean', + description => "Allow to overwrite existing VM.", + }, }), }, returns => { @@ -91,26 +106,50 @@ __PACKAGE__->register_method({ my $vmid = extract_param($param, 'vmid'); + my $archive = extract_param($param, 'archive'); + + my $storage = extract_param($param, 'storage'); + my $filename = PVE::QemuServer::config_file($vmid); - # first test (befor locking) - die "unable to create vm $vmid: config file already exists\n" - if -f $filename; my $storecfg = PVE::Storage::config(); - &$resolve_cdrom_alias($param); + PVE::Cluster::check_cfs_quorum(); - foreach my $opt (keys %$param) { - if (PVE::QemuServer::valid_drivename($opt)) { - my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); - raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; + if (!$archive) { + &$resolve_cdrom_alias($param); - PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); - $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + foreach my $opt (keys %$param) { + if (PVE::QemuServer::valid_drivename($opt)) { + my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}); + raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive; + + PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); + $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + } } + + PVE::QemuServer::add_random_macs($param); } - PVE::QemuServer::add_random_macs($param); + # fixme: archive eq '-' (read from stdin) + + my $restorefn = sub { + + if (-f $filename) { + die "unable to restore vm $vmid: config file already exists\n" + if !$param->{force}; + + die "unable to restore vm $vmid: vm is running\n" + if PVE::QemuServer::check_running($vmid); + } + + my $realcmd = sub { + PVE::QemuServer::restore_archive($archive, $vmid, { storage => $storage}); + }; + + return $rpcenv->fork_worker('qmrestore', $vmid, $user, $realcmd); + }; my $createfn = sub { @@ -123,7 +162,7 @@ __PACKAGE__->register_method({ my $vollist = []; eval { - $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param); + $vollist = PVE::QemuServer::create_disks($storecfg, $vmid, $param, $storage); # try to be smart about bootdisk my @disks = PVE::QemuServer::disknames(); @@ -155,7 +194,7 @@ __PACKAGE__->register_method({ return $rpcenv->fork_worker('qmcreate', $vmid, $user, $realcmd); }; - return PVE::QemuServer::lock_config($vmid, $createfn); + return PVE::QemuServer::lock_config($vmid, $archive ? $restorefn : $createfn); }}); __PACKAGE__->register_method({ diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 4773ec94..48c6cba6 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -1265,7 +1265,7 @@ sub touch_config { } sub create_disks { - my ($storecfg, $vmid, $settings, $conf) = @_; + my ($storecfg, $vmid, $settings, $conf, $default_storage) = @_; my $vollist = []; @@ -1278,7 +1278,7 @@ sub create_disks { my $file = $disk->{file}; if ($file =~ m/^(([^:\s]+):)?(\d+(\.\d+)?)$/) { - my $storeid = $2 || 'local'; + my $storeid = $2 || $default_storage; my $size = $3; my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid); my $fmt = $disk->{format} || $defformat; @@ -2918,4 +2918,161 @@ sub vm_balloonset { vm_monitor_command($vmid, "balloon $value", 1); } +# vzdump restore implementaion + +sub archive_read_firstfile { + my $archive = shift; + + die "ERROR: file '$archive' does not exist\n" if ! -f $archive; + + # try to detect archive type first + my $pid = open (TMP, "tar tf '$archive'|") || + die "unable to open file '$archive'\n"; + my $firstfile = ; + kill 15, $pid; + close TMP; + + die "ERROR: archive contaions no data\n" if !$firstfile; + chomp $firstfile; + + return $firstfile; +} + +sub restore_cleanup { + my $statfile = shift; + + print STDERR "starting cleanup\n"; + + if (my $fd = IO::File->new($statfile, "r")) { + while (defined(my $line = <$fd>)) { + if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) { + my $volid = $2; + eval { + if ($volid =~ m|^/|) { + unlink $volid || die 'unlink failed\n'; + } else { + my $cfg = cfs_read_file('storage.cfg'); + PVE::Storage::vdisk_free($cfg, $volid); + } + print STDERR "temporary volume '$volid' sucessfuly removed\n"; + }; + print STDERR "unable to cleanup '$volid' - $@" if $@; + } else { + print STDERR "unable to parse line in statfile - $line"; + } + } + $fd->close(); + } +} + +sub restore_archive { + my ($archive, $vmid, $opts) = @_; + + my $firstfile = archive_read_firstfile($archive); + die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n" + if $firstfile ne 'qemu-server.conf'; + + my $tocmd = "/usr/lib/qemu-server/qmextract"; + + $tocmd .= " --storage $opts->{storage}" if $opts->{storage}; + $tocmd .= ' --prealloc' if $opts->{prealloc}; + $tocmd .= ' --info' if $opts->{info}; + + my $cmd = ['tar', 'xf', $archive, '--to-command', $tocmd]; + + my $tmpdir = "/var/tmp/vzdumptmp$$"; + mkpath $tmpdir; + + local $ENV{VZDUMP_TMPDIR} = $tmpdir; + local $ENV{VZDUMP_VMID} = $vmid; + + my $conffile = PVE::QemuServer::config_file($vmid); + my $tmpfn = "$conffile.$$.tmp"; + + # disable interrupts (always do cleanups) + local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { + print STDERR "got interrupt - ignored\n"; + }; + + eval { + # enable interrupts + local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { + die "interrupted by signal\n"; + }; + + PVE::Tools::run_command($cmd); + + return if $opts->{info}; + + # read new mapping + my $map = {}; + my $statfile = "$tmpdir/qmrestore.stat"; + if (my $fd = IO::File->new($statfile, "r")) { + while (defined (my $line = <$fd>)) { + if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) { + $map->{$1} = $2 if $1; + } else { + print STDERR "unable to parse line in statfile - $line\n"; + } + } + $fd->close(); + } + + my $confsrc = "$tmpdir/qemu-server.conf"; + + my $srcfd = new IO::File($confsrc, "r") || + die "unable to open file '$confsrc'\n"; + + my $outfd = new IO::File ($tmpfn, "w") || + die "unable to write config for VM $vmid\n"; + + 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+):(.*)$/) && ($opts->{unique})) { + my ($id,$ethcfg) = ($1,$3); + $ethcfg =~ s/^\s+//; + my ($model, $mac) = split(/\=/,$ethcfg); + my $printvlan = PVE::QemuServer::print_vlan(PVE::QemuServer::parse_vlan($model)); + print $outfd "$id: $printvlan\n"; + } elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) { + 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; + } + } + + $srcfd->close(); + $outfd->close(); + }; + my $err = $@; + + if ($err) { + + unlink $tmpfn; + + restore_cleanup("$tmpdir/qmrestore.stat") if !$opts->{info}; + + die $err; + } + + rmtree $tmpdir; + + rename $tmpfn, $conffile || + die "unable to commit configuration file '$conffile'\n"; +}; + 1; diff --git a/PVE/VZDump/QemuServer.pm b/PVE/VZDump/QemuServer.pm index d2eed8cd..3f26ef81 100644 --- a/PVE/VZDump/QemuServer.pm +++ b/PVE/VZDump/QemuServer.pm @@ -6,7 +6,8 @@ use File::Path; use File::Basename; use PVE::INotify; use PVE::VZDump; -use PVE::Cluster; +use PVE::Cluster qw(cfs_read_file); +use PVE::Tools; use PVE::Storage; use PVE::QemuServer; use IO::File; diff --git a/qmextract b/qmextract new file mode 100755 index 00000000..b5d3db29 --- /dev/null +++ b/qmextract @@ -0,0 +1,186 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Long; +use File::Path; +use IO::File; +use PVE::JSONSchema; +use PVE::Tools; +use PVE::Cluster qw(cfs_read_file); +use PVE::QemuServer; + +my @std_opts = ('storage=s', 'info', 'prealloc'); + +sub print_usage { + print STDERR "usage: $0 [--storage=] [--info] [--prealloc] \n\n"; +} + +my $opts = {}; +if (!GetOptions ($opts, @std_opts)) { + print_usage (); + exit (-1); +} + +sub extract_archive { + # NOTE: this is run as tar subprocess (--to-command) + + $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { + die "interrupted by signal\n"; + }; + + my $filename = $ENV{TAR_FILENAME}; + die "got strange environment - no TAR_FILENAME\n" if !$filename; + + my $filesize = $ENV{TAR_SIZE}; + die "got strange file size '$filesize'\n" if !$filesize; + + my $tmpdir = $ENV{VZDUMP_TMPDIR}; + die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir; + + my $filetype = $ENV{TAR_FILETYPE} || 'none'; + die "got strange filetype '$filetype'\n" if $filetype ne 'f'; + + my $vmid = $ENV{VZDUMP_VMID}; + PVE::JSONSchema::pve_verify_vmid($vmid); + + if ($opts->{info}) { + print STDERR "reading archive member '$filename'\n"; + } else { + print STDERR "extracting '$filename' from archive\n"; + } + + my $conffile = "$tmpdir/qemu-server.conf"; + my $statfile = "$tmpdir/qmrestore.stat"; + + if ($filename eq 'qemu-server.conf') { + my $outfd = IO::File->new($conffile, "w") || + die "unable to write file '$conffile'\n"; + + while (defined(my $line = <>)) { + print $outfd $line; + print STDERR "CONFIG: $line" if $opts->{info}; + } + + $outfd->close(); + + exit(0); + } + + if ($opts->{info}) { + exec 'dd', 'bs=256K', "of=/dev/null"; + die "couldn't exec dd: $!\n"; + } + + my $conffd = IO::File->new($conffile, "r") || + die "unable to read file '$conffile'\n"; + + my $map; + while (defined(my $line = <$conffd>)) { + if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) { + $map->{$2} = { virtdev => $1, size => $3, storeid => $4 }; + } + } + close($conffd); + + my $statfd = IO::File->new($statfile, "a") || + die "unable to open file '$statfile'\n"; + + if ($filename !~ m/^.*\.([^\.]+)$/){ + die "got strange filename '$filename'\n"; + } + my $format = $1; + + my $path; + + if (!$map) { + print STDERR "restoring old style vzdump archive - " . + "no device map inside archive\n"; + die "can't restore old style archive to storage '$opts->{storage}'\n" + if $opts->{storage} && $opts->{storage} ne 'local'; + + my $dir = "/var/lib/vz/images/$vmid"; + mkpath $dir; + + $path = "$dir/$filename"; + + print $statfd "vzdump::$path\n"; + $statfd->close(); + + } else { + + my $info = $map->{$filename}; + die "no vzdump info for '$filename'\n" if !$info; + + if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){ + die "got strange filename '$filename'\n"; + } + + if ($filesize != $info->{size}) { + die "detected size difference for '$filename' " . + "($filesize != $info->{size})\n"; + } + + my $storeid; + if ($opts->{storage}) { + $storeid = $opts->{storage}; + } else { + $storeid = $info->{storeid} || 'local'; + } + + my $cfg = cfs_read_file('storage.cfg'); + my $scfg = PVE::Storage::storage_config($cfg, $storeid); + + my $alloc_size = ($filesize + 1024 - 1)/1024; + if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { + # hack: we just alloc a small file (32K) - we overwrite it anyways + $alloc_size = 32; + } else { + die "unable to restore '$filename' to storage '$storeid'\n" . + "storage type '$scfg->{type}' does not support format '$format\n" + if $format ne 'raw'; + } + + my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid, + $format, undef, $alloc_size); + + print STDERR "new volume ID is '$volid'\n"; + + print $statfd "vzdump:$info->{virtdev}:$volid\n"; + $statfd->close(); + + $path = PVE::Storage::path($cfg, $volid); + } + + print STDERR "restore data to '$path' ($filesize bytes)\n"; + + if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) { + exec 'dd', 'ibs=256K', 'obs=256K', "of=$path"; + die "couldn't exec dd: $!\n"; + } else { + exec '/usr/lib/qemu-server/sparsecp', $path; + die "couldn't exec sparsecp: $!\n"; + } +} + + +if (scalar(@ARGV) == 2) { + my $archive = shift; + my $vmid = shift; + + # fixme: use API call + PVE::JSONSchema::pve_verify_vmid($vmid); + + PVE::Cluster::check_cfs_quorum(); + + PVE::QemuServer::restore_archive($archive, $vmid, $opts); + +} elsif (scalar(@ARGV) == 0 && $ENV{TAR_FILENAME}) { + extract_archive(); +} else { + print_usage (); + exit(-1); +} + +exit(0); + + diff --git a/qmrestore b/qmrestore index a27571fd..f256b39f 100755 --- a/qmrestore +++ b/qmrestore @@ -1,420 +1,99 @@ #!/usr/bin/perl -w -# -# Copyright (C) 2007-2009 Proxmox Server Solutions GmbH -# -# Copyright: vzdump is under GNU GPL, the GNU General Public License. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 dated June, 1991. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# Author: Dietmar Maurer -# use strict; -use Getopt::Long; -use Sys::Syslog; -use File::Path; -use PVE::VZDump; -use PVE::Storage; - -$ENV{LANG} = "C"; # avoid locale related issues/warnings - -openlog ('vzdump', 'cons,pid', 'daemon'); - -my @std_opts = ('extract', 'storage=s', 'info', 'prealloc', 'unique'); - -sub print_usage { - my $msg = shift; - - print STDERR "ERROR: $msg\n\n" if $msg; - - print STDERR "usage: $0 [OPTIONS] \n\n"; -} - -sub shellquote { - my $str = shift; - - return "''" if !defined ($str) || ($str eq ''); - - die "unable to quote string containing null (\\000) bytes" - if $str =~ m/\x00/; - - # from String::ShellQuote - if ($str =~ m|[^\w!%+,\-./:@^]|) { - - # ' -> '\'' - $str =~ s/'/'\\''/g; - - $str = "'$str'"; - $str =~ s/^''//; - $str =~ s/''$//; - } - - return $str; -} - -my $quoted_cmd = shellquote ($0); -foreach my $arg (@ARGV) { - $quoted_cmd .= " " . shellquote ($arg); -} - -my $opts = {}; -if (!GetOptions ($opts, @std_opts)) { - print_usage (); - exit (-1); -} - -if ($#ARGV != 1) { - print_usage (); - exit (-1); -} - -my $archive = shift; -my $vmid = PVE::VZDump::check_vmids ((shift))->[0]; - -$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { - die "interrupted by signal\n"; -}; - -sub debugmsg { PVE::VZDump::debugmsg (@_); } # just a shortcut - -sub run_command { PVE::VZDump::run_command (undef, @_); } # just a shortcut - -if ($opts->{extract}) { - - # NOTE: this is run as tar subprocess (--to-command) - - my $filename = $ENV{TAR_FILENAME}; - die "got strange environment - no TAR_FILENAME\n" if !$filename; - - my $filesize = $ENV{TAR_SIZE}; - die "got strange file size '$filesize'\n" if !$filesize; - - my $tmpdir = $ENV{VZDUMP_TMPDIR}; - die "got strange environment - no VZDUMP_TMPDIR\n" if !$tmpdir; - - my $filetype = $ENV{TAR_FILETYPE} || 'none'; - die "got strange filetype '$filetype'\n" if $filetype ne 'f'; - - my $conffile = "$tmpdir/qemu-server.conf"; - my $statfile = "$tmpdir/qmrestore.stat"; - - if ($opts->{info}) { - print STDERR "reading archive member '$filename'\n"; - } else { - print STDERR "extracting '$filename' from archive\n"; - } - - if ($filename eq 'qemu-server.conf') { - my $outfd = IO::File->new ($conffile, "w") || - die "unable to write file '$conffile'\n"; - - while (defined (my $line = <>)) { - print $outfd $line; - print STDERR "CONFIG: $line" if $opts->{info}; - } - - $outfd->close(); - - exit (0); - } - - if ($opts->{info}) { - exec 'dd', 'bs=256K', "of=/dev/null"; - die "couldn't exec dd: $!\n"; - } - - my $conffd = IO::File->new ($conffile, "r") || - die "unable to read file '$conffile'\n"; - - my $map; - while (defined (my $line = <$conffd>)) { - if ($line =~ m/^\#vzdump\#map:(\S+):(\S+):(\d+):(\S*):$/) { - $map->{$2} = { virtdev => $1, size => $3, storeid => $4 }; - } - } - close ($conffd); - - my $statfd = IO::File->new ($statfile, "a") || - die "unable to open file '$statfile'\n"; - - if ($filename !~ m/^.*\.([^\.]+)$/){ - die "got strange filename '$filename'\n"; - } - my $format = $1; - - my $path; - - if (!$map) { - print STDERR "restoring old style vzdump archive - " . - "no device map inside archive\n"; - die "can't restore old style archive to storage '$opts->{storage}'\n" - if $opts->{storage} && $opts->{storage} ne 'local'; - - my $dir = "/var/lib/vz/images/$vmid"; - mkpath $dir; - - $path = "$dir/$filename"; - - print $statfd "vzdump::$path\n"; - $statfd->close(); - - } else { - - my $info = $map->{$filename}; - die "no vzdump info for '$filename'\n" if !$info; - - if ($filename !~ m/^vm-disk-$info->{virtdev}\.([^\.]+)$/){ - die "got strange filename '$filename'\n"; - } - - if ($filesize != $info->{size}) { - die "detected size difference for '$filename' " . - "($filesize != $info->{size})\n"; - } - - my $storeid; - if ($opts->{storage}) { - $storeid = $opts->{storage}; - } else { - $storeid = $info->{storeid} || 'local'; - } - - my $cfg = PVE::Storage::load_config(); - my $scfg = PVE::Storage::storage_config ($cfg, $storeid); - - my $alloc_size = ($filesize + 1024 - 1)/1024; - if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { - # hack: we just alloc a small file (32K) - we overwrite it anyways - $alloc_size = 32; - } else { - die "unable to restore '$filename' to storage '$storeid'\n" . - "storage type '$scfg->{type}' does not support format '$format\n" - if $format ne 'raw'; - } - - my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $vmid, - $format, undef, $alloc_size); - - print STDERR "new volume ID is '$volid'\n"; - - print $statfd "vzdump:$info->{virtdev}:$volid\n"; - $statfd->close(); - - $path = PVE::Storage::path ($cfg, $volid); - } - - print STDERR "restore data to '$path' ($filesize bytes)\n"; - - if ($opts->{prealloc} || $format ne 'raw' || (-b $path)) { - exec 'dd', 'ibs=256K', 'obs=256K', "of=$path"; - die "couldn't exec dd: $!\n"; - } else { - exec '/usr/lib/qemu-server/sparsecp', $path; - die "couldn't exec sparsecp: $!\n"; - } -} - -sub restore_cleanup { - my $statfile = shift; - - return if $opts->{info}; - - debugmsg ('info', "starting cleanup"); - if (my $fd = IO::File->new ($statfile, "r")) { - while (defined (my $line = <$fd>)) { - if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) { - my $volid = $2; - eval { - if ($volid =~ m|^/|) { - unlink $volid || die 'unlink failed\n'; - } else { - my $cfg = PVE::Storage::load_config(); - PVE::Storage::vdisk_free ($cfg, $volid); - } - debugmsg ('info', "temporary volume '$volid' sucessfuly removed"); - }; - debugmsg ('err', "unable to cleanup '$volid' - $@") if $@; - } else { - debugmsg ('info', "unable to parse line in statfile - $line"); - } - } - $fd->close(); - } -} - -sub restore_qemu { - my ($archive, $vmid, $tmpdir) = @_; - - local $ENV{VZDUMP_TMPDIR} = $tmpdir; - - my $subcmd = shellquote ("--to-command=${quoted_cmd}\ --extract"); - my $cmd = "tar xf '$archive' $subcmd"; - run_command ($cmd); - - return if $opts->{info}; - - # reed new mapping - my $map = {}; - my $statfile = "$tmpdir/qmrestore.stat"; - if (my $fd = IO::File->new ($statfile, "r")) { - while (defined (my $line = <$fd>)) { - if ($line =~ m/vzdump:([^\s:]*):(\S+)$/) { - $map->{$1} = $2 if $1; - } else { - debugmsg ('info', "unable to parse line in statfile - $line"); - } - } - $fd->close(); - } - - my $confsrc = "$tmpdir/qemu-server.conf"; - - my $srcfd = new IO::File ($confsrc, "r") || - die "unable to open file '$confsrc'\n"; - - my $conffile = PVE::QemuServer::config_file ($vmid); - my $tmpfn = "$conffile.$$.tmp"; - - my $outfd = new IO::File ($tmpfn, "w") || - die "unable to write config for VM $vmid\n"; - - eval { - while (defined (my $line = <$srcfd>)) { - next if $line =~ m/^\#vzdump\#/; - next if $line =~ m/^lock:/; - - if (($line =~ m/^((vlan)\d+):(.*)$/) && ($opts->{unique})) { - my ($id,$ethcfg) = ($1,$3); - $ethcfg =~ s/^\s+//; - my ($model, $mac) = split(/\=/,$ethcfg); - my $printvlan = PVE::QemuServer::print_vlan (PVE::QemuServer::parse_vlan ($model)); - print $outfd "$id: $printvlan\n"; - } elsif ($line =~ m/^((ide|scsi|virtio)\d+):(.*)$/) { - 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; - } - } - }; - my $err = $@; - - $outfd->close(); - - if ($err) { - unlink $tmpfn; - die $err; - } else { - rename $tmpfn, $conffile; - } -} - -my $firstfile = PVE::VZDump::read_firstfile ($archive); -if ($firstfile ne 'qemu-server.conf') { - die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"; -} - -my $tmpdir = "/var/tmp/vzdumptmp$$"; - -PVE::QemuServer::lock_config ($vmid, sub { - - my $conffile = PVE::QemuServer::config_file ($vmid); - - die "unable to restore VM '$vmid' - VM already exists\n" - if -f $conffile; - - mkpath $tmpdir; - - eval { - debugmsg ('info', "restore QemuServer backup '$archive' " . - "using ID $vmid", undef, 1) if !$opts->{info}; - - restore_qemu ($archive, $vmid, $tmpdir); - - if ($opts->{info}) { - debugmsg ('info', "reading '$archive' successful"); - } else { - debugmsg ('info', "restore QemuServer backup '$archive' successful", - undef, 1); - } - }; - my $err = $@; - - if ($err) { - local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { - debugmsg ('info', "got interrupt - ignored (cleanup phase)"); - }; - - restore_cleanup ("$tmpdir/qmrestore.stat") if $err; - } - - die $err if $err; -}); - -my $err = $@; - -rmtree $tmpdir; - -if ($err) { - if ($opts->{info}) { - debugmsg ('info', "reading '$archive' failed - $err"); - } else { - - debugmsg ('err', "restore QemuServer backup '$archive' failed - $err", - undef, 1); - } - exit (-1); -} - -exit (0); +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::INotify; +use PVE::RPCEnvironment; +use PVE::CLIHandler; +use PVE::JSONSchema qw(get_standard_option); +use PVE::API2::Qemu; + +use Data::Dumper; # fixme: remove + +use base qw(PVE::CLIHandler); + +$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; + +initlog('qmrestore'); + +die "please run as root\n" if $> != 0; + +PVE::INotify::inotify_init(); + +my $rpcenv = PVE::RPCEnvironment->init('cli'); + +$rpcenv->init_request(); +$rpcenv->set_language($ENV{LANG}); +$rpcenv->set_user('root@pam'); + +__PACKAGE__->register_method({ + name => 'qmrestore', + path => 'qmrestore', + method => 'POST', + description => "Restore QemuServer vzdump backups.", + parameters => { + additionalProperties => 0, + properties => { + vmid => get_standard_option('pve-vmid'), + archive => { + description => "The backup file.", + type => 'string', + maxLength => 255, + }, + storage => get_standard_option('pve-storage-id', { + description => "Default storage.", + optional => 1, + }), + force => { + optional => 1, + type => 'boolean', + description => "Allow to overwrite existing VM.", + }, + }, + }, + returns => { + type => 'string', + }, + code => sub { + my ($param) = @_; + + $param->{node} = PVE::INotify::nodename(); + + return PVE::API2::Qemu->create_vm($param); + }}); + +my $cmddef = [ __PACKAGE__, 'qmrestore', ['archive', 'vmid'], undef, + sub { + my $upid = shift; + my $status = PVE::Tools::upid_read_status($upid); + exit($status eq 'OK' ? 0 : -1); + }]; + +push @ARGV, 'help' if !scalar(@ARGV); + +PVE::CLIHandler::handle_simple_cmd($cmddef, \@ARGV, undef, $0); + +exit 0; __END__ =head1 NAME - + qmrestore - restore QemuServer vzdump backups =head1 SYNOPSIS -qmrestore [OPTIONS] - - --info read/verify archive and print relevant - information (test run) - - --unique assign a unique random ethernet address - - --storage restore to storage - - --prealloc never generate sparse files +=include synopsis =head1 DESCRIPTION -Restore the QemuServer vzdump backup to virtual machine -. Volumes are allocated on the original storage if there is no -C<--storage> specified. +Restore the QemuServer vzdump backup C to virtual machine +C. Volumes are allocated on the original storage if there is no +C specified. =head1 SEE ALSO - vzdump(1) vzrestore(1) +vzdump(1) vzrestore(1) + +=include pve_copyright -- 2.39.5