[pve-devel] [PATCH] Added differential backup.

Kamil Trzcinski ayufan at osk-net.pl
Wed Aug 15 02:51:40 CEST 2012


Signed-off-by: Kamil Trzcinski <ayufan at ayufan.eu>
---
 PVE/API2/OpenVZ.pm       |    7 ++++
 PVE/VZDump.pm            |   71 +++++++++++++++++++++++++++++++++++++++++++--
 PVE/VZDump/OpenVZ.pm     |    3 +-
 vzdump.conf              |    3 ++
 www/manager/dc/Backup.js |   21 +++++++++++++-
 5 files changed, 99 insertions(+), 6 deletions(-)

diff --git a/PVE/API2/OpenVZ.pm b/PVE/API2/OpenVZ.pm
index 938e4a4..535f5da 100644
--- a/PVE/API2/OpenVZ.pm
+++ b/PVE/API2/OpenVZ.pm
@@ -177,6 +177,13 @@ my $restore_openvz = sub {
 	
 	my $cmd = ['tar', 'xpf', $archive, '--totals', '--sparse', '-C', $private];
 
+	if ($archive =~ m!([^/]*vzdump-([a-z]*)-(\d*)-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|(tar(\.(gz|lzo))?)))--differential-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.vcdiff(\.(gz|lzo))?$!) {
+	    my $fullbackup = $archive;
+	    $fullbackup =~ s!([^/]*vzdump-([a-z]+)-(\d+)-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.(tgz|(tar(\.(gz|lzo))?)))--differential-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.vcdiff(\.(gz|lzo))?!$1!;
+	    print "extracting from differential archive, using full backup '$fullbackup'\n";
+	    $cmd = "pve-xdelta3 -q -d -c -R -s '$fullbackup' '$archive' |tar xpf - --totals --sparse -C '$private'";
+	}
+	
 	if ($archive eq '-') {
 	    print "extracting archive from STDIN\n";
 	    run_command($cmd, input => "<&STDIN");
diff --git a/PVE/VZDump.pm b/PVE/VZDump.pm
index b9595be..3c2c060 100644
--- a/PVE/VZDump.pm
+++ b/PVE/VZDump.pm
@@ -10,6 +10,7 @@ use IO::Select;
 use IPC::Open3;
 use POSIX qw(strftime);
 use File::Path;
+use File::Basename;
 use PVE::RPCEnvironment;
 use PVE::Storage;
 use PVE::Cluster qw(cfs_read_file);
@@ -184,6 +185,7 @@ sub read_vzdump_defaults {
 	stopwait => 10, # 10 minutes
 	mode => 'snapshot',
 	maxfiles => 1, 
+	fullbackup => 0,
     };
 
     my $fh = IO::File->new ("<$fn");
@@ -214,6 +216,8 @@ sub read_vzdump_defaults {
 	    $res->{size} = int($1);
 	} elsif ($line =~ m/maxfiles:\s*(\d+)\s*$/) {
 	    $res->{maxfiles} = int($1);
+	} elsif ($line =~ m/fullbackup:\s*(\d+)\s*$/) {
+	    $res->{fullbackup} = int($1);
 	} elsif ($line =~ m/exclude-path:\s*(.*)\s*$/) {
 	    $res->{'exclude-path'} = PVE::Tools::split_args($1); 
 	} elsif ($line =~ m/mode:\s*(stop|snapshot|suspend)\s*$/) {
@@ -681,6 +685,22 @@ sub get_backup_file_list {
 
     return $bklist;
 }
+
+sub get_differential_backup_file_list {
+    my ($dir, $bkname, $exclude_fn) = @_;
+
+    my $bklist = [];
+    foreach my $fn (<$dir/${bkname}-*>) {
+	next if $exclude_fn && $fn eq $exclude_fn;
+	if ($fn =~ m!/(${bkname}--differential-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2})\.vcdiff(\.(gz|lzo))?)$!) {
+	    $fn = "$dir/$1"; # untaint
+	    my $t = timelocal ($7, $6, $5, $4, $3 - 1, $2 - 1900);
+	    push @$bklist, [$fn, $t];
+	}
+    }
+
+    return $bklist;
+}
  
 sub exec_backup_task {
     my ($self, $task) = @_;
@@ -720,9 +740,37 @@ sub exec_backup_task {
 		if scalar(@$bklist) >= $maxfiles;
 	}
 
+	my $ext = '.tar';
+
+	my $fullbackup = undef;
+	if ($opts->{fullbackup} && !$opts->{stdout}) {
+	    my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname);
+	    $bklist = [ sort { $b->[1] <=> $a->[1] } @$bklist ];
+	    my $mintime = timelocal ($lt->sec, $lt->min, $lt->hour, 
+		$lt->mday, $lt->mon, $lt->year) - 
+		$opts->{fullbackup} * 24 * 60 * 60 - 
+		12 * 60 * 60; # - 12h just to make sure that on last day we create full backup
+
+	    foreach my $d (@$bklist) {
+		next if $mintime > $d->[1];
+
+		$fullbackup = $d->[0];
+		$basename = basename($fullbackup);
+		$basename = sprintf "${basename}--differential-%04d_%02d_%02d-%02d_%02d_%02d", 
+			$lt->year + 1900, $lt->mon + 1, $lt->mday, 
+			$lt->hour, $lt->min, $lt->sec;
+		$ext = ".vcdiff";
+
+	    	debugmsg ('info', "doing differential backup against '$fullbackup'");
+		last;
+	    }	    
+
+	    debugmsg ('info', "doing full backup, because no backup found in last $opts->{fullbackup} day(s)") 
+		if !$fullbackup;
+	}
+
 	my $logfile = $task->{logfile} = "$opts->{dumpdir}/$basename.log";
 
-	my $ext = '.tar';
 	my ($comp, $comp_ext) = compressor_info($opts->{compress});
 	if ($comp && $comp_ext) {
 	    $ext .= ".${comp_ext}";
@@ -893,7 +941,7 @@ sub exec_backup_task {
 	}
 
 	debugmsg ('info', "creating archive '$task->{tarfile}'", $logfd);
-	$plugin->archive($task, $vmid, $task->{tmptar}, $comp);
+	$plugin->archive($task, $vmid, $task->{tmptar}, $comp, $fullbackup);
 
 	rename ($task->{tmptar}, $task->{tarfile}) ||
 	    die "unable to rename '$task->{tmptar}' to '$task->{tarfile}'\n";
@@ -905,7 +953,7 @@ sub exec_backup_task {
 
 	# purge older backup
 
-	if ($maxfiles && $opts->{remove}) {
+	if ($maxfiles && $opts->{remove} && !$fullbackup) {
 	    my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname, $task->{tarfile});
 	    $bklist = [ sort { $b->[1] <=> $a->[1] } @$bklist ];
 
@@ -916,6 +964,16 @@ sub exec_backup_task {
 		my $logfn = $d->[0];
 		$logfn =~ s/\.(tgz|(tar(\.(gz|lzo))?))$/\.log/;
 		unlink $logfn;
+
+		my $dbklist = get_differential_backup_file_list($opts->{dumpdir}, basename($d->[0]));
+
+		foreach my $df (@$dbklist) {
+		    debugmsg ('info', "delete old differential backup '$df->[0]'", $logfd);
+		    unlink $df->[0];
+		    $logfn = $df->[0];
+		    $logfn =~ s/\.(vcdiff(\.(gz|lzo))?)$/\.log/;
+		    unlink $logfn;
+		}
 	    }
 	}
 
@@ -1175,6 +1233,12 @@ my $confdesc = {
 	optional => 1,
 	minimum => 1,
     },
+    fullbackup => {
+	type => 'integer',
+	description => "Create full backup if last is older than 'fullbackup' days.",
+	optional => 1,
+	minimum => 0,
+    },
     remove => {
 	type => 'boolean',
 	description => "Remove old backup files if there are more than 'maxfiles' backup files.",
@@ -1209,7 +1273,6 @@ sub verify_vzdump_parameters {
 	if $param->{exclude} && $param->{vmid};
 
     $param->{all} = 1 if defined($param->{exclude});
-
     return if !$check_missing;
 
     raise_param_exc({ vmid => "property is missing"})
diff --git a/PVE/VZDump/OpenVZ.pm b/PVE/VZDump/OpenVZ.pm
index a185f61..887596f 100644
--- a/PVE/VZDump/OpenVZ.pm
+++ b/PVE/VZDump/OpenVZ.pm
@@ -231,7 +231,7 @@ sub assemble {
 }
 
 sub archive {
-    my ($self, $task, $vmid, $filename, $comp) = @_;
+    my ($self, $task, $vmid, $filename, $comp, $basefile) = @_;
     
     my $findexcl = $self->{vzdump}->{findexcl};
     my $findargs = join (' ', @$findexcl) . ' -print0';
@@ -256,6 +256,7 @@ sub archive {
     $cmd .= "tar cpf - $taropts --null -T -";
     my $bwl = $opts->{bwlimit}*1024; # bandwidth limit for cstream
     $cmd .= "|cstream -t $bwl" if $opts->{bwlimit};
+    $cmd .= "|pve-xdelta3 -q -e -c -s '$basefile'" if $basefile;
     $cmd .= "|$comp" if $comp;
 
     $cmd .= ")";
diff --git a/vzdump.conf b/vzdump.conf
index b5ff34f..a0a0728 100644
--- a/vzdump.conf
+++ b/vzdump.conf
@@ -12,3 +12,6 @@
 #maxfiles: N
 #script: FILENAME
 #exclude-path: PATHLIST
+#fullbackup: DAYS
+
+
diff --git a/www/manager/dc/Backup.js b/www/manager/dc/Backup.js
index d069d9c..eb440a4 100644
--- a/www/manager/dc/Backup.js
+++ b/www/manager/dc/Backup.js
@@ -171,6 +171,25 @@ Ext.define('PVE.dc.BackupEdit', {
 		value: 'snapshot',
 		name: 'mode'
 	    },
+	    {
+		xtype: 'numberfield',
+		fieldLabel: gettext('Max Backups'),
+		name: 'maxfiles',
+		minValue: 0,
+		maxValue: 365,
+		value: me.create ? '1' : undefined,
+		allowBlank: false
+	    },
+	    {
+		xtype: 'numberfield',
+		fieldLabel: gettext('Full Backup Every'),
+		name: 'fullbackup',
+		emptyText : gettext('Days'),
+		minValue: 0,
+		maxValue: 60,
+		value: me.create ? '0' : undefined,
+		allowBlank: true
+	    },
 	    vmidField
 	];
 
@@ -461,4 +480,4 @@ Ext.define('PVE.dc.BackupView', {
 	    { name: 'compress', type: 'boolean' }
 	]
     });
-});
\ No newline at end of file
+});
-- 
1.7.2.5



More information about the pve-devel mailing list