Merge lp:~gl-az/percona-xtrabackup/BT-32889-history-on-server-2.2 into lp:percona-xtrabackup/2.2

Proposed by George Ormond Lorch III
Status: Merged
Approved by: Alexey Kopytov
Approved revision: no longer in the source branch.
Merged at revision: 4879
Proposed branch: lp:~gl-az/percona-xtrabackup/BT-32889-history-on-server-2.2
Merge into: lp:percona-xtrabackup/2.2
Diff against target: 667 lines (+542/-1) (has conflicts)
2 files modified
xtrabackup/innobackupex (+332/-1)
xtrabackup/test/t/history_on_server.sh (+210/-0)
Text conflict in xtrabackup/innobackupex
To merge this branch: bzr merge lp:~gl-az/percona-xtrabackup/BT-32889-history-on-server-2.2
Reviewer Review Type Date Requested Status
Alexey Kopytov (community) Approve
Review via email: mp+184860@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Alexey Kopytov (akopytov) wrote :

George,

   - I propose we rename ‘mysql_version’ to ‘server_version’, since we
     support not only MySQL.

   - let’s also rename xtrabackup_history to xtrabackup_info. It
     contains meta information about the backup where it’s stored, not
     the history. I think we can even deprecate xtrabackup_checkpoints
     with xtrabackup_info in future.

   - the table does not define the character set as we discussed
     previously. This means the character set will depend on
     server/connection settings.

   - update_history() is vulnerable to SQL injection attacks :) what if
     something like this is passed to --history, for example:

     --history="foo'; DROP DATABASE customers".

     Please use DBI prepared statements. This way you don’t have to roll
     your own quoting in other places.

   - it also appears that xtrabackup_history will not be created if
     --history is not passed to innobackupex. I think it should always
     be created. I.e. update_history() should collect all info
     unconditionally, but just avoid executing the actual INSERT if
     $option_history if false.

   - for start_time / end_time it’s better to use time() and then
     convert values it returns to TIMESTAMP with FROM_UNIXTIME():

  $history_start_time = time();
  ...

  # start_time
  $insert_vals .= ", FROM_UNIXTIME($history_start_time)";

  # end_time
  $tmp = time();
  $insert_vals .= ", FROM_UNIXTIME($tmp)";

    - /proc is only available on Linux, so the following will fail on
      everything else. And there are really no reasons to use it,
      there are many portable ways to get the cmd line from a Perl script

+ $tmp = sprintf("cat /proc/%d/cmdline", $$);
+ $tmp = qx($tmp);
+ system("cp /proc/$$/cmdline /tmp");
+ $tmp =~ s/\0/ /g; # transform embedded nulls between args to spaces

    - system("rm -f $file") is equivalent to unlink($file)

    - do you really want to scrub --encrypt and --encrypt-key-file?

    - here’s a more elegant and correct scrub_option() implementation:

sub scrub_option {
  my $scrub = shift;
  my $command = shift;

  return $command =~ s/--$scrub=[^ ]+ /--$scrub=... /g

}

   - in case --incremental-history-name or --incremental-history-uuid is
     used, it might be useful to have a diagnostic message about the LSN
     value being used and why it is being used.

   - I guess built-in innobackupex help needs some updates (still
     mentions --history-name, --no-history, etc.)

   - in the test case: it is not necessary to include inc/common.sh
     anymore. it is also not necessary to use “shift” if you don’t
     access the parameters array later in a function

review: Needs Fixing
Revision history for this message
George Ormond Lorch III (gl-az) wrote :
Download full text (5.0 KiB)

On 9/23/2013 9:58 AM, Alexey Kopytov wrote:
> Review: Needs Fixing
>
> George,
>
> - I propose we rename ‘mysql_version’ to ‘server_version’, since we
> support not only MySQL.
Makes sense, can do.
> - let’s also rename xtrabackup_history to xtrabackup_info. It
> contains meta information about the backup where it’s stored, not
> the history. I think we can even deprecate xtrabackup_checkpoints
> with xtrabackup_info in future.
Do you want to rename the entire feature and all options (--no-info,
--info=<name>, --incremental-info-name, --incremental-info-uuid) as well
or just the table name?
> - the table does not define the character set as we discussed
> previously. This means the character set will depend on
> server/connection settings.
Very true, forgot about that. Will add utf8 specifier.
> - update_history() is vulnerable to SQL injection attacks :) what if
> something like this is passed to --history, for example:
>
> --history="foo'; DROP DATABASE customers".
>
> Please use DBI prepared statements. This way you don’t have to roll
> your own quoting in other places.
Yes, I actually had a discussion w/ Vadim on whether or not we are
concerned w/ SQL injection at that level and the answer was not really
so I just stuck with the original prototype code I had that used all
literals. I suppose it wouldn't hurt and would be more proper to use DBD
prepare and bind values instead of all the literal bits.
>
> - it also appears that xtrabackup_history will not be created if
> --history is not passed to innobackupex. I think it should always
> be created. I.e. update_history() should collect all info
> unconditionally, but just avoid executing the actual INSERT if
> $option_history if false.
Correct. There were some XB test case failures that would need to be
fixed up and permissions manipulated if we always created the
schema/filled the record. At that point I thought too that it may not be
a great idea to change the default assumption that XB was read only on a
server database by now having it start to write stuff back into it,
possibly requiring a user to change their backup user privileges. So, I
changed it to be entirely optional and any upgrades would be compatible
with any existing backup user privileges without having to explicitly
disable it or alter backup user privileges. I should have checked that
out w/ you first. That being said, if you do still want it on/creating
schema and filling the table and generating the file by default, I can
change it back and go fix up whatever failing tests occur as a result
(just adding --no-history/--no-info) and make sure that I give Hrvoje
enough info doc wise to try to avoid confusion.
> - for start_time / end_time it’s better to use time() and then
> convert values it returns to TIMESTAMP with FROM_UNIXTIME():
>
> $history_start_time = time();
> ...
>
> # start_time
> $insert_vals .= ", FROM_UNIXTIME($history_start_time)";
>
> # end_time
> $tmp = time();
> $insert_vals .= ", FROM_UNIXTIME($tmp)";
OK, will do.
> - /proc is only available on Li...

Read more...

Revision history for this message
Alexey Kopytov (akopytov) wrote :
Download full text (4.8 KiB)

Hi George,

On Mon, 23 Sep 2013 15:49:15 -0700, George Lorch wrote:
> On 9/23/2013 9:58 AM, Alexey Kopytov wrote:
>> Review: Needs Fixing
>>
>> George,
>>
>> - I propose we rename ‘mysql_version’ to ‘server_version’, since we
>> support not only MySQL.
> Makes sense, can do.
>> - let’s also rename xtrabackup_history to xtrabackup_info. It
>> contains meta information about the backup where it’s stored, not
>> the history. I think we can even deprecate xtrabackup_checkpoints
>> with xtrabackup_info in future.
> Do you want to rename the entire feature and all options (--no-info,
> --info=<name>, --incremental-info-name, --incremental-info-uuid) as well
> or just the table name?

No, sorry I should have been more specific. I was referring to the file
created in the backup directory. 'xtrabackup_info' reflects its content
better than 'xtrabackup_history' IMO.

>> - the table does not define the character set as we discussed
>> previously. This means the character set will depend on
>> server/connection settings.
> Very true, forgot about that. Will add utf8 specifier.
>> - update_history() is vulnerable to SQL injection attacks :) what if
>> something like this is passed to --history, for example:
>>
>> --history="foo'; DROP DATABASE customers".
>>
>> Please use DBI prepared statements. This way you don’t have to roll
>> your own quoting in other places.
> Yes, I actually had a discussion w/ Vadim on whether or not we are
> concerned w/ SQL injection at that level and the answer was not really
> so I just stuck with the original prototype code I had that used all
> literals. I suppose it wouldn't hurt and would be more proper to use DBD
> prepare and bind values instead of all the literal bits.

Yes, it's certainly not a critical issue in the XtraBackup context, but
still opens the way for users shooting themselves in the foot with some
bad argument to --history.

>>
>> - it also appears that xtrabackup_history will not be created if
>> --history is not passed to innobackupex. I think it should always
>> be created. I.e. update_history() should collect all info
>> unconditionally, but just avoid executing the actual INSERT if
>> $option_history if false.
> Correct. There were some XB test case failures that would need to be
> fixed up and permissions manipulated if we always created the
> schema/filled the record. At that point I thought too that it may not be
> a great idea to change the default assumption that XB was read only on a
> server database by now having it start to write stuff back into it,
> possibly requiring a user to change their backup user privileges. So, I
> changed it to be entirely optional and any upgrades would be compatible
> with any existing backup user privileges without having to explicitly
> disable it or alter backup user privileges. I should have checked that
> out w/ you first. That being said, if you do still want it on/creating
> schema and filling the table and generating the file by default, I can
> change it back and go fix up whatever failing tests occur as a result
> (just adding --no-history/--no-inf...

Read more...

Revision history for this message
George Ormond Lorch III (gl-az) wrote :
Download full text (3.5 KiB)

Hey there Alexey...

On 9/24/2013 4:18 AM, Alexey Kopytov wrote:
> - let’s also rename xtrabackup_history to xtrabackup_info. It
> contains meta information about the backup where it’s stored, not
> the history. I think we can even deprecate xtrabackup_checkpoints
> with xtrabackup_info in future.
>> Do you want to rename the entire feature and all options (--no-info,
>> --info=<name>, --incremental-info-name, --incremental-info-uuid) as well
>> or just the table name?
> No, sorry I should have been more specific. I was referring to the file
> created in the backup directory. 'xtrabackup_info' reflects its content
> better than 'xtrabackup_history' IMO.
>
>>> - it also appears that xtrabackup_history will not be created if
>>> --history is not passed to innobackupex. I think it should always
>>> be created. I.e. update_history() should collect all info
>>> unconditionally, but just avoid executing the actual INSERT if
>>> $option_history if false.
>> Correct. There were some XB test case failures that would need to be
>> fixed up and permissions manipulated if we always created the
>> schema/filled the record. At that point I thought too that it may not be
>> a great idea to change the default assumption that XB was read only on a
>> server database by now having it start to write stuff back into it,
>> possibly requiring a user to change their backup user privileges. So, I
>> changed it to be entirely optional and any upgrades would be compatible
>> with any existing backup user privileges without having to explicitly
>> disable it or alter backup user privileges. I should have checked that
>> out w/ you first. That being said, if you do still want it on/creating
>> schema and filling the table and generating the file by default, I can
>> change it back and go fix up whatever failing tests occur as a result
>> (just adding --no-history/--no-info) and make sure that I give Hrvoje
>> enough info doc wise to try to avoid confusion.
> Sorry, I should have been more specific here to. I just meant to say
> that we have to create the xtrabackup_history (or xtrabackup_info) file
> regardless of whether --history is enabled.
>
> The reason we are implementing that file is
> https://bugs.launchpad.net/percona-xtrabackup/2.2/+bug/1133017
Ahh, OK, Now that makes a lot more sense. Yes, I can easily do this,
just have to shuffle around the means of data collection and writing in
update_history with the new DBD prepare and bind_param stuff so there
are not 20 if (!defined($option_history)).
>>> - here’s a more elegant and correct scrub_option() implementation:
>>>
>>> sub scrub_option {
>>> my $scrub = shift;
>>> my $command = shift;
>>>
>>> return $command =~ s/--$scrub=[^ ]+ /--$scrub=... /g
>>>
>>> }
>> Nice.
> Actually that won't work (I didn't verify it before posting). The
> following will work:
>
> sub scrub_option {
> my $scrub = shift;
> my $command = shift;
>
> $command =~ s/--$scrub=[^ ]+/--$scrub=.../g;
> return $command;
> }
>
>
>
Yup, I figured that out yesterday and fixed it myself. Actually, I just
removed the whole scrub_option call and just us...

Read more...

Revision history for this message
George Ormond Lorch III (gl-az) wrote :

New jenkins for changes based on comments above:
http://jenkins.percona.com/view/XtraBackup/job/percona-xtrabackup-2.2-param/13/

Don't know what that debian6/innodb51 failure is about in compact.sh. That has nothing to do with this feature.

Revision history for this message
Alexey Kopytov (akopytov) wrote :

The following lines:

$tmp =~ s/--password=[^ ]+ /--password=... /g;
$tmp =~ s/--encrypt-key=[^ ]+ /--encrypt-key=... /g;

Should not contain spaces, i.e.:

$tmp =~ s/--password=[^ ]+/--password=.../g;
$tmp =~ s/--encrypt-key=[^ ]+/--encrypt-key=.../g;

That's my fault, it was in the first incorrect version of scrub_option() I proposed, but then fixed in a later version.

The difference is that when one of the options to be scrubbed is at the end of the command line, there will be no space after it, so no substitution would occur with the current code then.

review: Needs Fixing
Revision history for this message
George Ormond Lorch III (gl-az) wrote :

> The following lines:
>
> $tmp =~ s/--password=[^ ]+ /--password=... /g;
> $tmp =~ s/--encrypt-key=[^ ]+ /--encrypt-key=... /g;
>
> Should not contain spaces, i.e.:
>
> $tmp =~ s/--password=[^ ]+/--password=.../g;
> $tmp =~ s/--encrypt-key=[^ ]+/--encrypt-key=.../g;
>
> That's my fault, it was in the first incorrect version of scrub_option() I
> proposed, but then fixed in a later version.
>
> The difference is that when one of the options to be scrubbed is at the end of
> the command line, there will be no space after it, so no substitution would
> occur with the current code then.

Yeah, I see that now, good catch. Fixed and went ahead and added a test for the command_tool with encrypt-key at the end of the line to validate that it gets scrubbed correctly.

Revision history for this message
George Ormond Lorch III (gl-az) wrote :
Revision history for this message
Alexey Kopytov (akopytov) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'xtrabackup/innobackupex'
--- xtrabackup/innobackupex 2013-09-23 10:58:53 +0000
+++ xtrabackup/innobackupex 2013-09-25 17:11:22 +0000
@@ -147,6 +147,10 @@
147my %mysql;147my %mysql;
148my $option_backup = '';148my $option_backup = '';
149149
150my $option_history;
151my $option_incremental_history_name = '';
152my $option_incremental_history_uuid = '';
153
150# name of the my.cnf configuration file154# name of the my.cnf configuration file
151#my $config_file = '';155#my $config_file = '';
152156
@@ -195,6 +199,9 @@
195# name of the file where slave info is written199# name of the file where slave info is written
196my $slave_info;200my $slave_info;
197201
202# name of the file where version and backup history is written
203my $backup_history;
204
198# mysql binlog position as given by "SHOW MASTER STATUS" command205# mysql binlog position as given by "SHOW MASTER STATUS" command
199my $mysql_binlog_position = '';206my $mysql_binlog_position = '';
200207
@@ -270,6 +277,7 @@
270my $copy_dir_overwrite;277my $copy_dir_overwrite;
271my $copy_dir_resolve_isl;278my $copy_dir_resolve_isl;
272279
280<<<<<<< TREE
273# ###########################################################################281# ###########################################################################
274# HTTPMicro package282# HTTPMicro package
275# This package is a copy without comments from the original. The original283# This package is a copy without comments from the original. The original
@@ -1511,6 +1519,14 @@
1511# End VersionCheck package1519# End VersionCheck package
1512# ###########################################################################1520# ###########################################################################
15131521
1522=======
1523# for history on server record
1524my $history_tool_command = "@ARGV";
1525my $history_start_time = time();
1526my $history_lock_time = 0;
1527my $history_delete_checkpoints = 0;
1528
1529>>>>>>> MERGE-SOURCE
1514######################################################################1530######################################################################
1515# program execution begins here1531# program execution begins here
1516######################################################################1532######################################################################
@@ -1919,6 +1935,43 @@
1919 detect_mysql_capabilities_for_backup(\%mysql);1935 detect_mysql_capabilities_for_backup(\%mysql);
1920 }1936 }
19211937
1938 #
1939 # if one of the history incrementals is being used, try to grab the
1940 # innodb_to_lsn from the history table and set the option_incremental_lsn
1941 #
1942 if ($option_incremental && !$option_incremental_lsn) {
1943 if ($option_incremental_history_name) {
1944 my $query = "SELECT innodb_to_lsn ".
1945 "FROM PERCONA_SCHEMA.xtrabackup_history ".
1946 "WHERE name = '$option_incremental_history_name' ".
1947 "AND innodb_to_lsn IS NOT NULL ".
1948 "ORDER BY innodb_to_lsn DESC LIMIT 1";
1949
1950 eval {
1951 $option_incremental_lsn =
1952 $mysql{dbh}->selectrow_hashref($query)->{innodb_to_lsn};
1953 } || die("Error while attempting to find history record for name ".
1954 "'$option_incremental_history_name'\n");
1955 print STDERR "$prefix --incremental-history-name=".
1956 "$option_incremental_history_name specified.".
1957 "Found and using lsn: $option_incremental_lsn";
1958 } elsif ($option_incremental_history_uuid) {
1959 my $query = "SELECT innodb_to_lsn ".
1960 "FROM PERCONA_SCHEMA.xtrabackup_history ".
1961 "WHERE uuid = '$option_incremental_history_uuid' ".
1962 "AND innodb_to_lsn IS NOT NULL";
1963
1964 eval {
1965 $option_incremental_lsn =
1966 $mysql{dbh}->selectrow_hashref($query)->{innodb_to_lsn};
1967 } || die("Error while attempting to find history record for uuid ".
1968 "'$option_incremental_history_uuid'\n");
1969 print STDERR "$prefix --incremental-history-uuid=".
1970 "$option_incremental_history_uuid specified.".
1971 "Found and using lsn: $option_incremental_lsn";
1972 }
1973 }
1974
1922 # start ibbackup as a child process1975 # start ibbackup as a child process
1923 start_ibbackup();1976 start_ibbackup();
19241977
@@ -1943,6 +1996,9 @@
1943 # make a prep copy before locking tables, if using rsync1996 # make a prep copy before locking tables, if using rsync
1944 backup_files(1);1997 backup_files(1);
19451998
1999 # start counting how long lock is held for history
2000 $history_lock_time = time();
2001
1946 # flush tables with read lock2002 # flush tables with read lock
1947 mysql_lockall(\%mysql);2003 mysql_lockall(\%mysql);
19482004
@@ -1976,6 +2032,13 @@
1976 # release read locks on all tables2032 # release read locks on all tables
1977 mysql_unlockall(\%mysql) if !$option_no_lock;2033 mysql_unlockall(\%mysql) if !$option_no_lock;
19782034
2035 # calculate how long lock was held for history
2036 if ($option_no_lock) {
2037 $history_lock_time = 0;
2038 } else {
2039 $history_lock_time = time() - $history_lock_time;
2040 }
2041
1979 my $ibbackup_exit_code = wait_for_ibbackup_finish();2042 my $ibbackup_exit_code = wait_for_ibbackup_finish();
19802043
1981 if ( $option_safe_slave_backup && $sql_thread_started) {2044 if ( $option_safe_slave_backup && $sql_thread_started) {
@@ -1999,6 +2062,8 @@
1999 print STDERR "$prefix MySQL slave binlog position: $mysql_slave_position\n";2062 print STDERR "$prefix MySQL slave binlog position: $mysql_slave_position\n";
2000 }2063 }
20012064
2065 write_xtrabackup_info(\%mysql);
2066
2002 return $ibbackup_exit_code;2067 return $ibbackup_exit_code;
2003}2068}
20042069
@@ -3510,8 +3575,14 @@
3510 $binlog_info = $work_dir . '/xtrabackup_binlog_info';3575 $binlog_info = $work_dir . '/xtrabackup_binlog_info';
3511 $galera_info = $work_dir . '/xtrabackup_galera_info';3576 $galera_info = $work_dir . '/xtrabackup_galera_info';
3512 $slave_info = $work_dir . '/xtrabackup_slave_info';3577 $slave_info = $work_dir . '/xtrabackup_slave_info';
3578 $backup_history = $work_dir . '/xtrabackup_info';
3513 write_backup_config_file($backup_config_file);3579 write_backup_config_file($backup_config_file);
35143580
3581 if (!$option_extra_lsndir) {
3582 $option_extra_lsndir = $option_tmpdir;
3583 $history_delete_checkpoints = 1;
3584 }
3585
3515 foreach (@xb_suspend_files) {3586 foreach (@xb_suspend_files) {
3516 my $suspend_file = $work_dir . "$_";3587 my $suspend_file = $work_dir . "$_";
3517 if ( -e "$suspend_file" ) {3588 if ( -e "$suspend_file" ) {
@@ -3615,6 +3686,7 @@
3615 'encrypt-threads=i' => \$option_encrypt_threads,3686 'encrypt-threads=i' => \$option_encrypt_threads,
3616 'encrypt-chunk-size=s' => \$option_encrypt_chunk_size,3687 'encrypt-chunk-size=s' => \$option_encrypt_chunk_size,
3617 'help' => \$option_help,3688 'help' => \$option_help,
3689 'history:s' => \$option_history,
3618 'version' => \$option_version,3690 'version' => \$option_version,
3619 'throttle=i' => \$option_throttle,3691 'throttle=i' => \$option_throttle,
3620 'log-copy-interval=i', \$option_log_copy_interval,3692 'log-copy-interval=i', \$option_log_copy_interval,
@@ -3642,6 +3714,8 @@
3642 'incremental' => \$option_incremental,3714 'incremental' => \$option_incremental,
3643 'incremental-basedir=s' => \$option_incremental_basedir,3715 'incremental-basedir=s' => \$option_incremental_basedir,
3644 'incremental-force-scan' => \$option_incremental_force_scan,3716 'incremental-force-scan' => \$option_incremental_force_scan,
3717 'incremental-history-name=s' => \$option_incremental_history_name,
3718 'incremental-history-uuid=s' => \$option_incremental_history_uuid,
3645 'incremental-lsn=s' => \$option_incremental_lsn,3719 'incremental-lsn=s' => \$option_incremental_lsn,
3646 'incremental-dir=s' => \$option_incremental_dir,3720 'incremental-dir=s' => \$option_incremental_dir,
3647 'extra-lsndir=s' => \$option_extra_lsndir,3721 'extra-lsndir=s' => \$option_extra_lsndir,
@@ -3750,7 +3824,9 @@
3750 # we are making a backup, get backup root directory3824 # we are making a backup, get backup root directory
3751 $option_backup = "1";3825 $option_backup = "1";
3752 $backup_root = $ARGV[0];3826 $backup_root = $ARGV[0];
3753 if ($option_incremental && !$option_incremental_lsn) {3827 if ($option_incremental && !$option_incremental_lsn &&
3828 !$option_incremental_history_name &&
3829 !$option_incremental_history_uuid) {
3754 if ($option_incremental_basedir) {3830 if ($option_incremental_basedir) {
3755 $incremental_basedir = $option_incremental_basedir;3831 $incremental_basedir = $option_incremental_basedir;
3756 } else {3832 } else {
@@ -4717,6 +4793,247 @@
4717 'WHERE PLUGIN_NAME LIKE "INNODB_CHANGED_PAGES"')4793 'WHERE PLUGIN_NAME LIKE "INNODB_CHANGED_PAGES"')
4718}4794}
47194795
4796#
4797# Writes xtrabackup_info file and if backup_history is enable creates
4798# PERCONA_SCHEMA.xtrabackup_history and writes a new history record to the
4799# table containing all the history info particular to the just completed
4800# backup.
4801#
4802sub write_xtrabackup_info()
4803{
4804 my $con = shift;
4805 my $sth;
4806 my $uuid;
4807 my $tmp;
4808 my $file_content;
4809
4810 eval {
4811 $sth = $con->{dbh}->prepare("insert into PERCONA_SCHEMA.xtrabackup_history(".
4812 "uuid, name, tool_name, tool_command, tool_version, ".
4813 "ibbackup_version, server_version, start_time, end_time, ".
4814 "lock_time, binlog_pos, innodb_from_lsn, innodb_to_lsn, ".
4815 "partial, incremental, format, compact, compressed, ".
4816 "encrypted) ".
4817 "values(?,?,?,?,?,?,?,from_unixtime(?),from_unixtime(?),?,?,".
4818 "?,?,?,?,?,?,?,?)");
4819 } || die("Eror while preparing history record\n");
4820
4821
4822 # uuid
4823 # we use the mysql UUID() function to avoid platform dependencies with uuidgen
4824 # and Data::UUID. We select uuid here individually here so that it can be
4825 # reported to stderr after successful history record insertion
4826 eval {
4827 $uuid = $con->{dbh}->selectrow_hashref("SELECT UUID() AS uuid")->{uuid};
4828 } || die("Error while attempting to create UUID for history record.\n");
4829 $sth->bind_param(1, $uuid);
4830 $file_content = "uuid = $uuid";
4831
4832 # name
4833 if ($option_history) {
4834 $sth->bind_param(2, $option_history);
4835 $file_content .= "\nname = $option_history";
4836 } else {
4837 $sth->bind_param(2, undef);
4838 $file_content .= "\nname = ";
4839 }
4840
4841 # tool_name
4842 $tmp = basename($0);
4843 $sth->bind_param(3, $tmp);
4844 $file_content .= "\ntool_name = $tmp";
4845
4846 # tool_command
4847 # scrub --password and --encrypt-key from tool_command
4848 $tmp = $history_tool_command;
4849 $tmp =~ s/--password=[^ ]+/--password=.../g;
4850 $tmp =~ s/--encrypt-key=[^ ]+/--encrypt-key=.../g;
4851 $sth->bind_param(4, $tmp);
4852 $file_content .= "\ntool_command = $tmp";
4853
4854 # tool_version
4855 $sth->bind_param(5, $innobackup_version);
4856 $file_content .= "\ntool_version = $innobackup_version";
4857
4858 # ibbackup_version
4859 $tmp = "$option_ibbackup_binary -v 2>&1";
4860 $tmp = qx($tmp);
4861 chomp($tmp);
4862 if (length $tmp) {
4863 $sth->bind_param(6, $tmp);
4864 $file_content .= "\nibbackup_version = $tmp";
4865 } else {
4866 $sth->bind_param(6, undef);
4867 $file_content .= "\nibbackup_version = ";
4868 }
4869
4870 # server_version
4871 $tmp =
4872 $con->{dbh}->selectrow_hashref("SELECT VERSION() as version")->{version};
4873 $sth->bind_param(7, $tmp);
4874 $file_content .= "\nserver_version = $tmp";
4875
4876 # start_time
4877 $sth->bind_param(8, $history_start_time);
4878 $file_content .= "\nstart_time = " . strftime("%Y-%m-%d %H:%M:%S", localtime($history_start_time));
4879
4880 # end_time
4881 $tmp = time();
4882 $sth->bind_param(9, $tmp);
4883 $file_content .= "\nend_time = " . strftime("%Y-%m-%d %H:%M:%S", localtime($tmp));
4884
4885 # lock_time
4886 $sth->bind_param(10, $history_lock_time);
4887 $file_content .= "\nlock_time = $history_lock_time";
4888
4889 # binlog_pos
4890 if ($mysql_binlog_position) {
4891 $sth->bind_param(11, $mysql_binlog_position);
4892 $file_content .= "\nbinlog_pos = $mysql_binlog_position";
4893 } else {
4894 $sth->bind_param(11, undef);
4895 $file_content .= "\nbinlog_pos = ";
4896 }
4897
4898 # innodb_from_lsn
4899 # grab the from_lsn from the extra checkpoints file, parse and delete it
4900 # if necessary. This could generate an error if the checkpoints file
4901 # isn't 100% correct in the format.
4902 $tmp = "cat $option_extra_lsndir/xtrabackup_checkpoints | grep from_lsn";
4903 $tmp = qx($tmp);
4904 chomp($tmp);
4905 substr($tmp, 0, 11, ""); # strip "from_lsn = "
4906 if (length $tmp) {
4907 $sth->bind_param(12, $tmp);
4908 $file_content .= "\ninnodb_from_lsn = $tmp";
4909 } else {
4910 print STDERR "WARNING : Unable to obtain from_lsn from " .
4911 "$option_extra_lsndir/xtrabackup_checkpoints, " .
4912 "writing NULL to history record.\n";
4913 $sth->bind_param(12, undef);
4914 $file_content .= "\ninnodb_from_lsn = ";
4915 }
4916
4917 # innodb_to_lsn
4918 # grab the to_lsn from the extra checkpoints file, parse and delete it
4919 # if necessary. This could generate an error if the checkpoints file
4920 # isn't 100% correct in the format.
4921 $tmp = "cat $option_extra_lsndir/xtrabackup_checkpoints | grep to_lsn";
4922 $tmp = qx($tmp);
4923 chomp($tmp);
4924 substr($tmp, 0, 9, ""); # strip "to_lsn = "
4925 if (length $tmp) {
4926 $sth->bind_param(13, $tmp);
4927 $file_content .= "\ninnodb_to_lsn = $tmp";
4928 } else {
4929 print STDERR "WARNING : Unable to obtain to_lsn from " .
4930 "$option_extra_lsndir/xtrabackup_checkpoints, " .
4931 "writing NULL to history record.\n";
4932 $sth->bind_param(13, undef);
4933 $file_content .= "\ninnodb_to_lsn = ";
4934 }
4935
4936 # we're finished with the checkpoints file, delete it if it was a temp
4937 # only for the history
4938 if ($history_delete_checkpoints > 0) {
4939 unlink("$option_extra_lsndir/xtrabackup_checkpoints");
4940 }
4941
4942 # partial (Y | N)
4943 if ($option_include || $option_databases || $option_tables_file || $option_export) {
4944 $sth->bind_param(14, "Y");
4945 $file_content .= "\npartial = Y";
4946 } else {
4947 $sth->bind_param(14, "N");
4948 $file_content .= "\npartial = N";
4949 }
4950
4951 # incremental (Y | N)
4952 if ($option_incremental) {
4953 $sth->bind_param(15, "Y");
4954 $file_content .= "\nincremental = Y";
4955 } else {
4956 $sth->bind_param(15, "N");
4957 $file_content .= "\nincremental = N";
4958 }
4959
4960 # format (file | tar | xbsream)
4961 if ($option_stream eq 'tar') {
4962 $sth->bind_param(16, "tar");
4963 $file_content .= "\nformat = tar";
4964 } elsif ($option_stream eq 'xbstream') {
4965 $sth->bind_param(16, "xbstream");
4966 $file_content .= "\nformat = xbstream";
4967 } else {
4968 $sth->bind_param(16, "file");
4969 $file_content .= "\nformat = file";
4970 }
4971
4972 # compact (Y | N)
4973 if ($option_compact) {
4974 $sth->bind_param(17, "Y");
4975 $file_content .= "\ncompact = Y";
4976 } else {
4977 $sth->bind_param(17, "N");
4978 $file_content .= "\ncompact = N";
4979 }
4980
4981 # compressed (Y | N)
4982 if ($option_compress) {
4983 $sth->bind_param(18, "Y");
4984 $file_content .= "\ncompressed = Y";
4985 } else {
4986 $sth->bind_param(18, "N");
4987 $file_content .= "\ncompressed = N";
4988 }
4989
4990 # encrypted (Y | N)
4991 if ($option_encrypt) {
4992 $sth->bind_param(19, "Y");
4993 $file_content .= "\nencrypted = Y";
4994 } else {
4995 $sth->bind_param(19, "N");
4996 $file_content .= "\nencrypted = N";
4997 }
4998
4999 # create the backup history file
5000 write_to_backup_file("$backup_history", "$file_content");
5001
5002 if (!defined($option_history)) {
5003 return;
5004 }
5005
5006 mysql_query($con, "CREATE DATABASE IF NOT EXISTS PERCONA_SCHEMA");
5007 mysql_query($con, "CREATE TABLE IF NOT EXISTS PERCONA_SCHEMA.xtrabackup_history(".
5008 "uuid VARCHAR(40) NOT NULL PRIMARY KEY,".
5009 "name VARCHAR(255) DEFAULT NULL,".
5010 "tool_name VARCHAR(255) DEFAULT NULL,".
5011 "tool_command TEXT DEFAULT NULL,".
5012 "tool_version VARCHAR(255) DEFAULT NULL,".
5013 "ibbackup_version VARCHAR(255) DEFAULT NULL,".
5014 "server_version VARCHAR(255) DEFAULT NULL,".
5015 "start_time TIMESTAMP NULL DEFAULT NULL,".
5016 "end_time TIMESTAMP NULL DEFAULT NULL,".
5017 "lock_time BIGINT UNSIGNED DEFAULT NULL,".
5018 "binlog_pos VARCHAR(128) DEFAULT NULL,".
5019 "innodb_from_lsn BIGINT UNSIGNED DEFAULT NULL,".
5020 "innodb_to_lsn BIGINT UNSIGNED DEFAULT NULL,".
5021 "partial ENUM('Y', 'N') DEFAULT NULL,".
5022 "incremental ENUM('Y', 'N') DEFAULT NULL,".
5023 "format ENUM('file', 'tar', 'xbstream') DEFAULT NULL,".
5024 "compact ENUM('Y', 'N') DEFAULT NULL,".
5025 "compressed ENUM('Y', 'N') DEFAULT NULL,".
5026 "encrypted ENUM('Y', 'N') DEFAULT NULL".
5027 ") CHARACTER SET utf8 ENGINE=innodb");
5028
5029 eval {
5030 $sth->execute;
5031 } || die("Error while attempting to insert history record.\n");
5032
5033 print STDERR "$prefix Backup history record uuid $uuid successfully written\n";
5034}
5035
5036
4720=pod5037=pod
47215038
4722=head1 NAME5039=head1 NAME
@@ -4735,8 +5052,10 @@
4735 [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME]5052 [--defaults-file=MY.CNF] [--defaults-group=GROUP-NAME]
4736 [--databases=LIST] [--no-lock] 5053 [--databases=LIST] [--no-lock]
4737 [--tmpdir=DIRECTORY] [--tables-file=FILE]5054 [--tmpdir=DIRECTORY] [--tables-file=FILE]
5055 [--history=NAME]
4738 [--incremental] [--incremental-basedir]5056 [--incremental] [--incremental-basedir]
4739 [--incremental-dir] [--incremental-force-scan] [--incremental-lsn]5057 [--incremental-dir] [--incremental-force-scan] [--incremental-lsn]
5058 [--incremental-history-name=NAME] [--incremental-history-uuid=UUID]
4740 [--compact] 5059 [--compact]
4741 BACKUP-ROOT-DIR5060 BACKUP-ROOT-DIR
47425061
@@ -4906,6 +5225,10 @@
49065225
4907This option displays a help screen and exits.5226This option displays a help screen and exits.
49085227
5228=item --history=NAME
5229
5230This option enables the tracking of backup history in the PERCONA_SCHEMA.xtrabackup_history table. An optional history series name may be specified that will be placed with the history record for the current backup being taken.
5231
4909=item --host=HOST5232=item --host=HOST
49105233
4911This option specifies the host to use when connecting to the database server with TCP/IP. The option accepts a string argument. It is passed to the mysql child process without alteration. See mysql --help for details.5234This option specifies the host to use when connecting to the database server with TCP/IP. The option accepts a string argument. It is passed to the mysql child process without alteration. See mysql --help for details.
@@ -4930,6 +5253,14 @@
49305253
4931This option specifies the directory where the incremental backup will be combined with the full backup to make a new full backup. The option accepts a string argument. It is used with the --incremental option.5254This option specifies the directory where the incremental backup will be combined with the full backup to make a new full backup. The option accepts a string argument. It is used with the --incremental option.
49325255
5256=item --incremental-history-name=NAME
5257
5258This option specifies the name of the backup series stored in the PERCONA_SCHEMA.xtrabackup_history history record to base an incremental backup on. Xtrabackup will search the history table looking for the most recent (highest innodb_to_lsn), successful backup in the series and take the to_lsn value to use as the starting lsn for the incremental backup. This will be mutually exclusive with --incremental-history-uuid, --incremental-basedir and --incremental-lsn. If no valid lsn can be found (no series by that name, no successful backups by that name) xtrabackup will return with an error. It is used with the --incremental option.
5259
5260=item --incremental-history-uuid=UUID
5261
5262This option specifies the UUID of the specific history record stored in the PERCONA_SCHEMA.xtrabackup_history to base an incremental backup on. --incremental-history-name, --incremental-basedir and --incremental-lsn. If no valid lsn can be found (no success record with that uuid) xtrabackup will return with an error. It is used with the --incremental option.
5263
4933=item --incremental-force-scan5264=item --incremental-force-scan
49345265
4935This options tells xtrabackup to perform full scan of data files for taking an incremental backup even if full changed page bitmap data is available to enable the backup without the full scan.5266This options tells xtrabackup to perform full scan of data files for taking an incremental backup even if full changed page bitmap data is available to enable the backup without the full scan.
49365267
=== added file 'xtrabackup/test/t/history_on_server.sh'
--- xtrabackup/test/t/history_on_server.sh 1970-01-01 00:00:00 +0000
+++ xtrabackup/test/t/history_on_server.sh 2013-09-25 17:11:22 +0000
@@ -0,0 +1,210 @@
1###############################################################################
2# Test history-on-server feature
3###############################################################################
4
5
6###############################################################################
7# Gets a single column value from the last history record added
8function get_one_value()
9{
10 local column=$1
11 val=`${MYSQL} ${MYSQL_ARGS} -Ns -e "SELECT $column FROM PERCONA_SCHEMA.xtrabackup_history ORDER BY start_time DESC LIMIT 1"`
12}
13
14
15###############################################################################
16# Checks a single column from the last history record added for some value and
17# not NULL.
18function check_for_not_NULL()
19{
20 local column=$1
21 get_one_value "$column"
22 if [ -z "$val" ] || [ "$val" = "NULL" ];
23 then
24 vlog "Error: $column in history record invalid, expected NOT NULL, got \"$val\""
25 exit 1
26 fi
27}
28
29
30###############################################################################
31# Checks a single column from the last history record added to see if is a
32# specific value.
33function check_for_value()
34{
35 local column=$1
36 shift
37 get_one_value "$column"
38 if [ -z "$val" ] || [ "$val" != "$@" ];
39 then
40 vlog "Error: $column in history record invalid, got \"$val\" expected \"$@\""
41 exit 1
42 fi
43}
44
45
46###############################################################################
47vlog "Prepping server"
48start_server
49load_dbase_schema incremental_sample
50multi_row_insert incremental_sample.test \({1..100},100\)
51backup_dir=$topdir/backups
52mkdir $backup_dir
53
54
55###############################################################################
56# This tests the to make sure that no xtrabackup_history unless --history is
57# specified
58#vlog "Testing no --history"
59#innobackupex --stream=tar $backup_dir > /dev/null
60
61#run_cmd_expect_failure get_one_value "uuid"
62
63
64###############################################################################
65# This tests the basic creation of a history record and that fields are
66# populated with some data. It also tests specifically that
67# partial, incremental, compact, compressed, encrypted and format are exactly
68# the correct values after the backup.
69# Missing is a test that binlog_pos is NULL, that would require restarting the
70# server without the log-bin option in the .cnf file but that has been tested
71# manually and doesn't seem to be something that would be required to be
72# validated.
73vlog "Testing basic history record"
74innobackupex --history=test1 --stream=tar $backup_dir > /dev/null
75
76for column in uuid name tool_name tool_command tool_version ibbackup_version \
77server_version start_time end_time lock_time binlog_pos innodb_from_lsn \
78innodb_to_lsn
79do
80 check_for_not_NULL "$column"
81done
82
83for column in partial incremental compact compressed encrypted
84do
85 check_for_value "$column" "N"
86done
87
88check_for_value "format" "tar"
89
90# saving for later
91get_one_value "innodb_to_lsn"
92first_to_lsn=$val
93
94
95###############################################################################
96# This tests the taking of an incremental backup based on the last record
97# of a history series and validates that the lsns in the record are correct.
98# It also tests that format, incremental and compact are exactly the correct
99# values after the backup.
100vlog "Testing incremental based on history name"
101
102multi_row_insert incremental_sample.test \({101..200},100\)
103
104innobackupex --history=test1 --incremental \
105--incremental-history-name=test1 --compact $backup_dir > /dev/null
106
107# saving for later
108get_one_value "uuid"
109second_uuid=$val
110get_one_value "innodb_from_lsn"
111second_from_lsn=$val
112get_one_value "innodb_to_lsn"
113second_to_lsn=$val
114
115check_for_value "format" "file"
116check_for_value "incremental" "Y"
117check_for_value "compact" "Y"
118
119if [ -z "$second_from_lsn" ] || [ "$second_from_lsn" != "$first_to_lsn" ]
120then
121 vlog "Second backup was not properly based on the to_lsn of the first"
122 exit 1
123fi
124
125multi_row_insert incremental_sample.test \({201..300},100\)
126
127# This will be a backup based on the last incremental just done, so, its
128# innodb_from_lsn (third_from_lsn) should be the same as the value in
129# second_to_lsn. This tests that we find the right record in the test1 series
130# out of the two records that should be present before the backup is done.
131innobackupex --history=test1 --incremental \
132--incremental-history-name=test1 $backup_dir > /dev/null
133
134# saving for later
135get_one_value "uuid"
136third_uuid=$val
137get_one_value "innodb_from_lsn"
138third_from_lsn=$val
139get_one_value "innodb_to_lsn"
140third_to_lsn=$val
141
142if [ -z "$third_from_lsn" ] || [ "$third_from_lsn" != "$second_to_lsn" ]
143then
144 vlog "Third backup was not properly based on the to_lsn of the second"
145 exit 1
146fi
147
148
149###############################################################################
150# This tests that we can base an incremental on a specific history record
151# identified by its uuid that we captured earlier from a history record or it
152# could be scraped from the output of innobackupex at some point in the past.
153# It also tests specifically that incremental, compressed, encrypted and format
154# are exactly the correct values after the backup.
155# It tests that --history can be specified, resulting in a history record with
156# no name
157vlog "Testing incremental based on history uuid"
158multi_row_insert incremental_sample.test \({301..400},100\)
159
160innobackupex --history --incremental --incremental-history-uuid=$third_uuid \
161--stream=xbstream --compress --encrypt=AES256 \
162$backup_dir --encrypt-key=percona_xtrabackup_is_awesome___ > /dev/null
163
164get_one_value "innodb_from_lsn"
165fourth_from_lsn=$val
166
167for column in incremental compressed encrypted
168do
169 check_for_value "$column" "Y"
170done
171
172check_for_value "format" "xbstream"
173check_for_value "name" "NULL"
174
175# validate command tool and encrypt key scrubbibng but need to pop off first
176# two arguments in the result added by test framework function innobackupex
177get_one_value "tool_command"
178val=${val:`expr index "$val" " "`}; val=${val:`expr index "$val" " "`}
179expected_val="--history --incremental "\
180"--incremental-history-uuid=$third_uuid --stream=xbstream --compress "\
181"--encrypt=AES256 $backup_dir --encrypt-key=..."
182
183if [ -z "$val" ] || [ "$val" != "$expected_val" ]
184then
185 vlog "Error: $column in history record invalid, got \"$val\" expected \"$expected_val\""
186 exit 1
187fi
188
189if [ -z "$fourth_from_lsn" ] || [ "$fourth_from_lsn" != "$third_to_lsn" ]
190then
191 vlog "Fourth backup was not properly based on the to_lsn of the third"
192 exit 1
193fi
194
195
196###############################################################################
197# This tests that innobackupex fails when an invalid --incremental-history-name
198# is given.
199vlog "Testing bad --incremental-history-name"
200run_cmd_expect_failure $IB_BIN $IB_ARGS --incremental \
201--incremental-history-name=foo --stream=tar $backup_dir > /dev/null
202
203
204
205###############################################################################
206# This tests that innobackupex fails when an invalid --incremental-history-uuid
207# is given.
208vlog "Testing bad --incremental-history-uuid"
209run_cmd_expect_failure $IB_BIN $IB_ARGS --incremental \
210--incremental-history-uuid=foo --stream=tar $backup_dir > /dev/null

Subscribers

People subscribed via source and target branches