From ae6bdecfaf47893346443620a0a57586cf22ebdf Mon Sep 17 00:00:00 2001 From: David Steele Date: Sun, 8 Mar 2015 13:26:09 -0400 Subject: [PATCH] Split command-line parameter processing out into a separate file. This is in preparation allowing all parameters to be specified/overridden on the command line, with pg_backrest.conf being option. --- bin/pg_backrest.pl | 69 +-- lib/BackRest/Backup.pm | 1 + lib/BackRest/Config.pm | 326 +---------- lib/BackRest/Exception.pm | 22 +- lib/BackRest/Param.pm | 785 +++++++++++++++++++++++++++ lib/BackRest/Restore.pm | 14 +- test/lib/BackRestTest/BackupTest.pm | 5 +- test/lib/BackRestTest/ConfigTest.pm | 392 +++++++++++++ test/lib/BackRestTest/UtilityTest.pm | 2 +- test/test.pl | 9 + 10 files changed, 1253 insertions(+), 372 deletions(-) create mode 100644 lib/BackRest/Param.pm create mode 100755 test/lib/BackRestTest/ConfigTest.pm diff --git a/bin/pg_backrest.pl b/bin/pg_backrest.pl index 651f7648a..cd95657ea 100755 --- a/bin/pg_backrest.pl +++ b/bin/pg_backrest.pl @@ -15,6 +15,7 @@ use Pod::Usage; use lib dirname($0) . '/../lib'; use BackRest::Utility; +use BackRest::Param; use BackRest::Config; use BackRest::Remote; use BackRest::File; @@ -196,7 +197,7 @@ else #################################################################################################################################### # ARCHIVE-PUSH Command #################################################################################################################################### -if (operation_get() eq OP_ARCHIVE_PUSH) +if (operationTest(OP_ARCHIVE_PUSH)) { # Make sure the archive push operation happens on the db side if ($strRemote eq DB) @@ -222,7 +223,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) if ($bArchiveLocal) { - $strStopFile = "${strArchivePath}/lock/" . param_get(PARAM_STANZA) . "-archive.stop"; + $strStopFile = "${strArchivePath}/lock/" . optionGet(OPTION_STANZA) . "-archive.stop"; } # If an archive file is defined, then push it @@ -244,7 +245,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) # Create the file object my $oFile = new BackRest::File ( - param_get(PARAM_STANZA), + optionGet(OPTION_STANZA), config_key_load($strSection, CONFIG_KEY_PATH, true), $bArchiveLocal ? NONE : $strRemote, remote_get($bArchiveLocal, config_key_load(CONFIG_SECTION_ARCHIVE, CONFIG_KEY_COMPRESS_LEVEL), @@ -272,7 +273,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) } # Fork and exit the parent process so the async process can continue - if (!param_get(PARAM_TEST_NO_FORK)) + if (!optionTest(OPTION_TEST_NO_FORK)) { if (fork()) { @@ -295,7 +296,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) &log(INFO, 'starting async archive-push'); # Create a lock file to make sure async archive-push does not run more than once - my $strLockPath = "${strArchivePath}/lock/" . param_get(PARAM_STANZA) . "-archive.lock"; + my $strLockPath = "${strArchivePath}/lock/" . optionGet(OPTION_STANZA) . "-archive.lock"; if (!lock_file_create($strLockPath)) { @@ -304,7 +305,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) } # Build the basic command string that will be used to modify the command during processing - my $strCommand = $^X . ' ' . $0 . " --stanza=" . param_get(PARAM_STANZA); + my $strCommand = $^X . ' ' . $0 . " --stanza=" . optionGet(OPTION_STANZA); # Get the new operational flags my $bCompress = config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_COMPRESS, true, 'y') eq 'y' ? true : false; @@ -315,7 +316,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) # Create the file object my $oFile = new BackRest::File ( - param_get(PARAM_STANZA), + optionGet(OPTION_STANZA), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true), $strRemote, remote_get(false, config_key_load(CONFIG_SECTION_ARCHIVE, CONFIG_KEY_COMPRESS_LEVEL), @@ -340,7 +341,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) while (!defined($iLogTotal) || $iLogTotal > 0) { - $iLogTotal = archive_xfer($strArchivePath . "/archive/" . param_get(PARAM_STANZA), $strStopFile, + $iLogTotal = archive_xfer($strArchivePath . "/archive/" . optionGet(OPTION_STANZA), $strStopFile, $strCommand, $iArchiveMaxMB); if ($iLogTotal > 0) @@ -402,7 +403,7 @@ if (operation_get() eq OP_ARCHIVE_PUSH) #################################################################################################################################### # ARCHIVE-GET Command #################################################################################################################################### -if (operation_get() eq OP_ARCHIVE_GET) +if (operationTest(OP_ARCHIVE_GET)) { # Make sure the archive file is defined if (!defined($ARGV[1])) @@ -419,7 +420,7 @@ if (operation_get() eq OP_ARCHIVE_GET) # Init the file object my $oFile = new BackRest::File ( - param_get(PARAM_STANZA), + optionGet(OPTION_STANZA), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true), $strRemote, remote_get(false, @@ -446,7 +447,7 @@ if (operation_get() eq OP_ARCHIVE_GET) #################################################################################################################################### my $oFile = new BackRest::File ( - param_get(PARAM_STANZA), + optionGet(OPTION_STANZA), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true), $strRemote, remote_get(false, @@ -457,7 +458,7 @@ my $oFile = new BackRest::File #################################################################################################################################### # RESTORE #################################################################################################################################### -if (operation_get() eq OP_RESTORE) +if (operationTest(OP_RESTORE)) { if ($strRemote eq DB) { @@ -465,31 +466,31 @@ if (operation_get() eq OP_RESTORE) } # Open the log file - log_file_set(config_key_load(CONFIG_SECTION_RESTORE, CONFIG_KEY_PATH, true) . '/log/' . param_get(PARAM_STANZA) . '-restore'); + log_file_set(config_key_load(CONFIG_SECTION_RESTORE, CONFIG_KEY_PATH, true) . '/log/' . optionGet(OPTION_STANZA) . '-restore'); # Set the lock path my $strLockPath = config_key_load(CONFIG_SECTION_RESTORE, CONFIG_KEY_PATH, true) . '/lock/' . - param_get(PARAM_STANZA) . '-' . operation_get() . '.lock'; + optionGet(OPTION_STANZA) . '-' . operationGet() . '.lock'; # Do the restore new BackRest::Restore ( config_key_load(CONFIG_SECTION_STANZA, CONFIG_KEY_PATH, true), - param_get(PARAM_SET), + optionGet(OPTION_SET), config_section_load(CONFIG_SECTION_TABLESPACE_MAP), $oFile, config_key_load(CONFIG_SECTION_RESTORE, CONFIG_KEY_THREAD_MAX, true), - param_get(PARAM_DELTA), - param_get(PARAM_FORCE), - param_get(PARAM_TYPE), - param_get(PARAM_TARGET), - param_get(PARAM_TARGET_EXCLUSIVE), - param_get(PARAM_TARGET_RESUME), - param_get(PARAM_TARGET_TIMELINE), + optionGet(OPTION_DELTA), + optionGet(OPTION_FORCE), + optionGet(OPTION_TYPE), + optionGet(OPTION_TARGET, false), + optionGet(OPTION_TARGET_EXCLUSIVE, false), + optionGet(OPTION_TARGET_RESUME, false), + optionGet(OPTION_TARGET_TIMELINE, false), config_section_load(CONFIG_SECTION_RECOVERY_OPTION), - param_get(PARAM_STANZA), + optionGet(OPTION_STANZA), $0, - param_get(PARAM_CONFIG) + optionGet(OPTION_CONFIG) )->restore; remote_exit(0); @@ -499,7 +500,7 @@ if (operation_get() eq OP_RESTORE) # GET MORE CONFIG INFO #################################################################################################################################### # Open the log file -log_file_set(config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true) . '/log/' . param_get(PARAM_STANZA)); +log_file_set(config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true) . '/log/' . optionGet(OPTION_STANZA)); # Make sure backup and expire operations happen on the backup side if ($strRemote eq BACKUP) @@ -512,18 +513,18 @@ my $bCompress = config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_COMPRESS, true # Set the lock path my $strLockPath = config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_PATH, true) . '/lock/' . - param_get(PARAM_STANZA) . '-' . operation_get() . '.lock'; + optionGet(OPTION_STANZA) . '-' . operationGet() . '.lock'; if (!lock_file_create($strLockPath)) { - &log(ERROR, 'backup process is already running for stanza ' . param_get(PARAM_STANZA) . ' - exiting'); + &log(ERROR, 'backup process is already running for stanza ' . optionGet(OPTION_STANZA) . ' - exiting'); remote_exit(0); } # Initialize the db object my $oDb; -if (!param_get(PARAM_NO_START_STOP)) +if (!optionGet(OPTION_NO_START_STOP)) { $oDb = new BackRest::Db ( @@ -538,31 +539,31 @@ backup_init ( $oDb, $oFile, - param_get(PARAM_TYPE), + optionGet(OPTION_TYPE), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_COMPRESS, true, 'y') eq 'y' ? true : false, config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_HARDLINK, true, 'y') eq 'y' ? true : false, config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_MAX), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_ARCHIVE_REQUIRED, true, 'y') eq 'y' ? true : false, config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_THREAD_TIMEOUT), - param_get(PARAM_NO_START_STOP), - param_get(PARAM_FORCE) + optionGet(OPTION_NO_START_STOP), + optionTest(OPTION_FORCE) ); #################################################################################################################################### # BACKUP #################################################################################################################################### -if (operation_get() eq OP_BACKUP) +if (operationTest(OP_BACKUP)) { backup(config_key_load(CONFIG_SECTION_STANZA, CONFIG_KEY_PATH), config_key_load(CONFIG_SECTION_BACKUP, CONFIG_KEY_START_FAST, true, 'n') eq 'y' ? true : false); - operation_set(OP_EXPIRE); + operationSet(OP_EXPIRE); } #################################################################################################################################### # EXPIRE #################################################################################################################################### -if (operation_get() eq OP_EXPIRE) +if (operationTest(OP_EXPIRE)) { backup_expire ( diff --git a/lib/BackRest/Backup.pm b/lib/BackRest/Backup.pm index 5e3d616a2..f7514e22a 100644 --- a/lib/BackRest/Backup.pm +++ b/lib/BackRest/Backup.pm @@ -16,6 +16,7 @@ use Thread::Queue; use lib dirname($0); use BackRest::Utility; use BackRest::Exception; +use BackRest::Param; use BackRest::Config; use BackRest::Manifest; use BackRest::File; diff --git a/lib/BackRest/Config.pm b/lib/BackRest/Config.pm index d56856ce4..8fdb38fc3 100644 --- a/lib/BackRest/Config.pm +++ b/lib/BackRest/Config.pm @@ -7,33 +7,21 @@ use strict; use warnings FATAL => qw(all); use Carp qw(confess); -use Pod::Usage; use File::Basename; use Getopt::Long; use lib dirname($0) . '/../lib'; use BackRest::Exception; use BackRest::Utility; +use BackRest::Param; use Exporter qw(import); -our @EXPORT = qw(config_load config_key_load config_section_load operation_get operation_set param_get +our @EXPORT = qw(config_load config_key_load config_section_load FILE_MANIFEST FILE_VERSION FILE_POSTMASTER_PID FILE_RECOVERY_CONF PATH_LATEST - OP_ARCHIVE_GET OP_ARCHIVE_PUSH OP_BACKUP OP_RESTORE OP_EXPIRE - - BACKUP_TYPE_FULL BACKUP_TYPE_DIFF BACKUP_TYPE_INCR - - RECOVERY_TYPE_NAME RECOVERY_TYPE_TIME RECOVERY_TYPE_XID RECOVERY_TYPE_PRESERVE RECOVERY_TYPE_NONE - RECOVERY_TYPE_DEFAULT - - PARAM_CONFIG PARAM_STANZA PARAM_TYPE PARAM_DELTA PARAM_SET PARAM_NO_START_STOP PARAM_FORCE PARAM_TARGET - PARAM_TARGET_EXCLUSIVE PARAM_TARGET_RESUME PARAM_TARGET_TIMELINE CONFIG_SECTION_RECOVERY - - PARAM_VERSION PARAM_HELP PARAM_TEST PARAM_TEST_DELAY PARAM_TEST_NO_FORK - CONFIG_SECTION_COMMAND CONFIG_SECTION_GENERAL CONFIG_SECTION_COMMAND_OPTION CONFIG_SECTION_LOG CONFIG_SECTION_BACKUP CONFIG_SECTION_RESTORE CONFIG_SECTION_RECOVERY CONFIG_SECTION_RECOVERY_OPTION CONFIG_SECTION_TABLESPACE_MAP CONFIG_SECTION_ARCHIVE CONFIG_SECTION_RETENTION CONFIG_SECTION_STANZA @@ -65,68 +53,6 @@ use constant FILE_VERSION => 'version', FILE_POSTMASTER_PID => 'postmaster.pid', FILE_RECOVERY_CONF => 'recovery.conf', - - PATH_LATEST => 'latest' -}; - -#################################################################################################################################### -# Operation constants - basic operations that are allowed in backrest -#################################################################################################################################### -use constant -{ - OP_ARCHIVE_GET => 'archive-get', - OP_ARCHIVE_PUSH => 'archive-push', - OP_BACKUP => 'backup', - OP_RESTORE => 'restore', - OP_EXPIRE => 'expire' -}; - -#################################################################################################################################### -# BACKUP Type Constants -#################################################################################################################################### -use constant -{ - BACKUP_TYPE_FULL => 'full', - BACKUP_TYPE_DIFF => 'diff', - BACKUP_TYPE_INCR => 'incr' -}; - -#################################################################################################################################### -# RECOVERY Type Constants -#################################################################################################################################### -use constant -{ - RECOVERY_TYPE_NAME => 'name', - RECOVERY_TYPE_TIME => 'time', - RECOVERY_TYPE_XID => 'xid', - RECOVERY_TYPE_PRESERVE => 'preserve', - RECOVERY_TYPE_NONE => 'none', - RECOVERY_TYPE_DEFAULT => 'default' -}; - -#################################################################################################################################### -# Parameter constants -#################################################################################################################################### -use constant -{ - PARAM_CONFIG => 'config', - PARAM_STANZA => 'stanza', - PARAM_TYPE => 'type', - PARAM_NO_START_STOP => 'no-start-stop', - PARAM_DELTA => 'delta', - PARAM_SET => 'set', - PARAM_FORCE => 'force', - PARAM_VERSION => 'version', - PARAM_HELP => 'help', - - PARAM_TARGET => 'target', - PARAM_TARGET_EXCLUSIVE => 'target-exclusive', - PARAM_TARGET_RESUME => 'target-resume', - PARAM_TARGET_TIMELINE => 'target-timeline', - - PARAM_TEST => 'test', - PARAM_TEST_DELAY => 'test-delay', - PARAM_TEST_NO_FORK => 'no-fork' }; #################################################################################################################################### @@ -216,8 +142,6 @@ use constant # Global variables #################################################################################################################################### my %oConfig; # Configuration hash -my %oParam = (); # Parameter hash -my $strOperation; # Operation (backup, archive-get, ...) #################################################################################################################################### # CONFIG_LOAD @@ -228,61 +152,9 @@ sub config_load { my $strFile = shift; # Full path to ini file to load from - # Default for general parameters - param_set(PARAM_NO_START_STOP, false); # Do not perform start/stop backup (and archive-required gets set to false) - param_set(PARAM_FORCE, false); # Force an action that would not normally be allowed (varies by action) - param_set(PARAM_VERSION, false); # Display version and exit - param_set(PARAM_HELP, false); # Display help and exit - - # Defaults for test parameters - not for general use - param_set(PARAM_TEST_NO_FORK, false); # Prevents the archive process from forking when local archiving is enabled - param_set(PARAM_TEST, false); # Enters test mode - not harmful, but adds special logging and pauses for unit testing - param_set(PARAM_TEST_DELAY, 5); # Seconds to delay after a test point (default is not enough for manual tests) - - # Get command line parameters - GetOptions (\%oParam, PARAM_CONFIG . '=s', PARAM_STANZA . '=s', PARAM_TYPE . '=s', PARAM_DELTA, PARAM_SET . '=s', - PARAM_NO_START_STOP, PARAM_FORCE, PARAM_TARGET . '=s', PARAM_TARGET_EXCLUSIVE, PARAM_TARGET_RESUME, - PARAM_TARGET_TIMELINE . '=s', PARAM_VERSION, PARAM_HELP, - PARAM_TEST, PARAM_TEST_DELAY . '=s', PARAM_TEST_NO_FORK) - or pod2usage(2); - - # Display version and exit if requested - if (param_get(PARAM_VERSION) || param_get(PARAM_HELP)) - { - print 'pg_backrest ' . version_get() . "\n"; - - if (!param_get(PARAM_HELP)) - { - exit 0; - } - } - - # Display help and exit if requested - if (param_get(PARAM_HELP)) - { - print "\n"; - pod2usage(); - } - - # Get and validate the operation - $strOperation = $ARGV[0]; - - # Validate params - param_valid(); - - # # Validate thread parameter - # if (defined(param_get(PARAM_THREAD)) && !(param_get(PARAM_THREAD) >= 1)) - # { - # confess &log(ERROR, 'thread parameter should be >= 1'); - # } - - # Get configuration parameter and load it - if (!defined(param_get(PARAM_CONFIG))) - { - param_set(PARAM_CONFIG, '/etc/pg_backrest.conf'); - } - - ini_load(param_get(PARAM_CONFIG), \%oConfig); + # Load parameters + configLoad(); + ini_load(optionGet(OPTION_CONFIG), \%oConfig); # If this is a restore, then try to default config if (!defined(config_key_load(CONFIG_SECTION_RESTORE, CONFIG_KEY_PATH))) @@ -304,9 +176,6 @@ sub config_load # Validate config config_valid(); - - # Set test parameters - test_set(param_get(PARAM_TEST), param_get(PARAM_TEST_DELAY)); } #################################################################################################################################### @@ -316,7 +185,7 @@ sub config_section_load { my $strSection = shift; - $strSection = param_get(PARAM_STANZA) . ':' . $strSection; + $strSection = optionGet(OPTION_STANZA) . ':' . $strSection; return $oConfig{$strSection}; } @@ -342,13 +211,13 @@ sub config_key_load # Look in the default stanza section if ($strSection eq CONFIG_SECTION_STANZA) { - $strValue = $oConfig{param_get(PARAM_STANZA)}{"${strKey}"}; + $strValue = $oConfig{optionGet(OPTION_STANZA)}{"${strKey}"}; } # Else look in the supplied section else { # First check the stanza section - $strValue = $oConfig{param_get(PARAM_STANZA) . ":${strSection}"}{"${strKey}"}; + $strValue = $oConfig{optionGet(OPTION_STANZA) . ":${strSection}"}{"${strKey}"}; # If the stanza section value is undefined then check global if (!defined($strValue)) @@ -396,7 +265,7 @@ sub config_key_set } # Set the value - $strSection = param_get(PARAM_STANZA) . ':' . $strSection; + $strSection = optionGet(OPTION_STANZA) . ':' . $strSection; $oConfig{$strSection}{$strKey} = $strValue; } @@ -413,7 +282,7 @@ sub config_valid my $oSectionHashRef; # Check [stanza]:recovery:option section - $strSection = param_get(PARAM_STANZA) . ':' . CONFIG_SECTION_RECOVERY_OPTION; + $strSection = optionGet(OPTION_STANZA) . ':' . CONFIG_SECTION_RECOVERY_OPTION; $oSectionHashRef = $oConfig{$strSection}; if (defined($oSectionHashRef) && keys($oSectionHashRef) != 0) @@ -511,7 +380,7 @@ sub config_key_valid $oConfig{$strSection}{$strKey} = $strValue; # Also do validation for the stanza section - my $strStanza = param_get(PARAM_STANZA); + my $strStanza = optionGet(OPTION_STANZA); if (substr($strSection, 0, length($strStanza) + 1) ne "${strStanza}:") { @@ -519,177 +388,4 @@ sub config_key_valid } } -#################################################################################################################################### -# PARAM_VALID -# -# Make sure the command-line parameters are valid. -#################################################################################################################################### -sub param_valid -{ - # Check the stanza - if (!defined(param_get(PARAM_STANZA))) - { - confess 'a backup stanza must be specified'; - } - - # Check that the operation is present and valid - if (!defined($strOperation)) - { - confess &log(ERROR, "operation must be specified", ERROR_PARAM); - } - - if ($strOperation ne OP_ARCHIVE_GET && - $strOperation ne OP_ARCHIVE_PUSH && - $strOperation ne OP_BACKUP && - $strOperation ne OP_RESTORE && - $strOperation ne OP_EXPIRE) - { - confess &log(ERROR, "invalid operation ${strOperation}"); - } - - # Check type param - my $strParam = PARAM_TYPE; - my $strType = param_get($strParam); - - # Type is only valid for backup and restore operations - if (operation_test(OP_BACKUP) || operation_test(OP_RESTORE)) - { - # Check types for backup - if (operation_test(OP_BACKUP)) - { - # If type is not defined set to BACKUP_TYPE_INCR - if (!defined($strType)) - { - $strType = BACKUP_TYPE_INCR; - param_set($strParam, $strType); - } - - # Check that type is in valid list - if (!($strType eq BACKUP_TYPE_FULL || $strType eq BACKUP_TYPE_DIFF || $strType eq BACKUP_TYPE_INCR)) - { - confess &log(ERROR, "invalid type '${strType}' for ${strOperation}, must be: '" . BACKUP_TYPE_FULL . "', '" . - BACKUP_TYPE_DIFF . "', '" . BACKUP_TYPE_INCR . "'", ERROR_PARAM); - } - } - - # Check types for restore - elsif (operation_test(OP_RESTORE)) - { - # If type is not defined set to RECOVERY_TYPE_DEFAULT - if (!defined($strType)) - { - $strType = RECOVERY_TYPE_DEFAULT; - param_set($strParam, $strType); - } - - if (!($strType eq RECOVERY_TYPE_NAME || $strType eq RECOVERY_TYPE_TIME || $strType eq RECOVERY_TYPE_XID || - $strType eq RECOVERY_TYPE_PRESERVE || $strType eq RECOVERY_TYPE_NONE || $strType eq RECOVERY_TYPE_DEFAULT)) - { - confess &log(ERROR, "invalid type '${strType}' for ${strOperation}, must be: '" . RECOVERY_TYPE_NAME . - "', '" . RECOVERY_TYPE_TIME . "', '" . RECOVERY_TYPE_XID . "', '" . RECOVERY_TYPE_PRESERVE . - "', '" . RECOVERY_TYPE_NONE . "', '" . RECOVERY_TYPE_DEFAULT . "'", ERROR_PARAM); - } - } - } - else - { - if (defined($strType)) - { - confess &log(ERROR, PARAM_TYPE . ' is only valid for '. OP_BACKUP . ' and ' . OP_RESTORE . ' operations', ERROR_PARAM); - } - } - - # Check target param - $strParam = PARAM_TARGET; - my $strTarget = param_get($strParam); - my $strTargetMessage = 'for ' . OP_RESTORE . " operations where type is '" . RECOVERY_TYPE_NAME . - "', '" . RECOVERY_TYPE_TIME . "', or '" . RECOVERY_TYPE_XID . "'"; - - if (operation_test(OP_RESTORE) && - ($strType eq RECOVERY_TYPE_NAME || $strType eq RECOVERY_TYPE_TIME || $strType eq RECOVERY_TYPE_XID)) - { - if (!defined($strTarget)) - { - confess &log(ERROR, PARAM_TARGET . ' is required ' . $strTargetMessage, ERROR_PARAM); - } - } - elsif (defined($strTarget)) - { - confess &log(ERROR, PARAM_TARGET . ' is only required ' . $strTargetMessage, ERROR_PARAM); - } - - # Check target-resume - can only be used when target is specified - if (defined(param_get(PARAM_TARGET_RESUME)) && !defined($strTarget)) - { - confess &log(ERROR, PARAM_TARGET_RESUME . ' and ' . PARAM_TARGET_TIMELINE . - ' are only valid when target is specified', ERROR_PARAM); - } - - # Check target-exclusive - can only be used when target is time or xid - if (defined(param_get(PARAM_TARGET_EXCLUSIVE)) && !($strType eq RECOVERY_TYPE_TIME || $strType eq RECOVERY_TYPE_XID)) - { - confess &log(ERROR, PARAM_TARGET_EXCLUSIVE . ' is only valid when target is specified and recovery type is ' . - RECOVERY_TYPE_TIME . ' or ' . RECOVERY_TYPE_XID, ERROR_PARAM); - } -} - -#################################################################################################################################### -# OPERATION_GET -# -# Get the current operation. -#################################################################################################################################### -sub operation_get -{ - return $strOperation; -} - -#################################################################################################################################### -# OPERATION_TEST -# -# Test the current operation. -#################################################################################################################################### -sub operation_test -{ - my $strOperationTest = shift; - - return $strOperationTest eq $strOperation; -} - -#################################################################################################################################### -# OPERATION_SET -# -# Set current operation (usually for triggering follow-on operations). -#################################################################################################################################### -sub operation_set -{ - my $strValue = shift; - - $strOperation = $strValue; -} - -#################################################################################################################################### -# PARAM_GET -# -# Get param value. -#################################################################################################################################### -sub param_get -{ - my $strParam = shift; - - return $oParam{$strParam}; -} - -#################################################################################################################################### -# PARAM_SET -# -# Set param value. -#################################################################################################################################### -sub param_set -{ - my $strParam = shift; - my $strValue = shift; - - $oParam{$strParam} = $strValue; -} - 1; diff --git a/lib/BackRest/Exception.pm b/lib/BackRest/Exception.pm index 1d32a7b84..f53a351ce 100644 --- a/lib/BackRest/Exception.pm +++ b/lib/BackRest/Exception.pm @@ -11,21 +11,25 @@ use Carp qw(confess); # Exports #################################################################################################################################### use Exporter qw(import); -our @EXPORT = qw(ERROR_CHECKSUM ERROR_CONFIG ERROR_PARAM ERROR_POSTMASTER_RUNNING ERROR_PROTOCOL ERROR_RESTORE_PATH_NOT_EMPTY - ERROR_FORMAT); +our @EXPORT = qw(ERROR_ASSERT ERROR_CHECKSUM ERROR_CONFIG ERROR_OPERATION_REQUIRED ERROR_OPTION_REQUIRED ERROR_OPTION_INVALID + ERROR_OPTION_INVALID_VALUE ERROR_POSTMASTER_RUNNING ERROR_PROTOCOL ERROR_RESTORE_PATH_NOT_EMPTY ERROR_FORMAT); #################################################################################################################################### # Exception Codes #################################################################################################################################### use constant { - ERROR_CHECKSUM => 100, - ERROR_CONFIG => 101, - ERROR_PARAM => 102, - ERROR_RESTORE_PATH_NOT_EMPTY => 103, - ERROR_POSTMASTER_RUNNING => 104, - ERROR_PROTOCOL => 105, - ERROR_FORMAT => 106 + ERROR_ASSERT => 100, + ERROR_CHECKSUM => 101, + ERROR_CONFIG => 102, + ERROR_FORMAT => 103, + ERROR_OPERATION_REQUIRED => 104, + ERROR_OPTION_REQUIRED => 105, + ERROR_OPTION_INVALID => 106, + ERROR_OPTION_INVALID_VALUE => 107, + ERROR_POSTMASTER_RUNNING => 108, + ERROR_PROTOCOL => 109, + ERROR_RESTORE_PATH_NOT_EMPTY => 110 }; #################################################################################################################################### diff --git a/lib/BackRest/Param.pm b/lib/BackRest/Param.pm new file mode 100644 index 000000000..8026f23e5 --- /dev/null +++ b/lib/BackRest/Param.pm @@ -0,0 +1,785 @@ +#################################################################################################################################### +# PARAM MODULE +#################################################################################################################################### +package BackRest::Param; + +use strict; +use warnings FATAL => qw(all); +use Carp qw(confess); + +use Pod::Usage; +use File::Basename; +use Getopt::Long qw(GetOptions); +use Storable qw(dclone); + +use lib dirname($0) . '/../lib'; +use BackRest::Exception; +use BackRest::Utility; + +use Exporter qw(import); + +our @EXPORT = qw(configLoad optionGet optionTest optionRuleGet operationGet operationTest operationSet + + OP_ARCHIVE_GET OP_ARCHIVE_PUSH OP_BACKUP OP_RESTORE OP_EXPIRE + + BACKUP_TYPE_FULL BACKUP_TYPE_DIFF BACKUP_TYPE_INCR + + RECOVERY_TYPE_NAME RECOVERY_TYPE_TIME RECOVERY_TYPE_XID RECOVERY_TYPE_PRESERVE RECOVERY_TYPE_NONE + RECOVERY_TYPE_DEFAULT + + OPTION_CONFIG OPTION_STANZA OPTION_TYPE OPTION_DELTA OPTION_SET OPTION_NO_START_STOP OPTION_FORCE OPTION_TARGET + OPTION_TARGET_EXCLUSIVE OPTION_TARGET_RESUME OPTION_TARGET_TIMELINE OPTION_THREAD_MAX + + OPTION_VERSION OPTION_HELP OPTION_TEST OPTION_TEST_DELAY OPTION_TEST_NO_FORK + + OPTION_DEFAULT_RESTORE_SET); + +#################################################################################################################################### +# Operation constants - basic operations that are allowed in backrest +#################################################################################################################################### +use constant +{ + OP_ARCHIVE_GET => 'archive-get', + OP_ARCHIVE_PUSH => 'archive-push', + OP_BACKUP => 'backup', + OP_RESTORE => 'restore', + OP_EXPIRE => 'expire' +}; + +#################################################################################################################################### +# BACKUP Type Constants +#################################################################################################################################### +use constant +{ + BACKUP_TYPE_FULL => 'full', + BACKUP_TYPE_DIFF => 'diff', + BACKUP_TYPE_INCR => 'incr' +}; + +#################################################################################################################################### +# RECOVERY Type Constants +#################################################################################################################################### +use constant +{ + RECOVERY_TYPE_NAME => 'name', + RECOVERY_TYPE_TIME => 'time', + RECOVERY_TYPE_XID => 'xid', + RECOVERY_TYPE_PRESERVE => 'preserve', + RECOVERY_TYPE_NONE => 'none', + RECOVERY_TYPE_DEFAULT => 'default' +}; + +#################################################################################################################################### +# Option constants +#################################################################################################################################### +use constant +{ + # Command-line-only options + OPTION_CONFIG => 'config', + OPTION_DELTA => 'delta', + OPTION_FORCE => 'force', + OPTION_NO_START_STOP => 'no-start-stop', + OPTION_SET => 'set', + OPTION_STANZA => 'stanza', + OPTION_TARGET => 'target', + OPTION_TARGET_EXCLUSIVE => 'target-exclusive', + OPTION_TARGET_RESUME => 'target-resume', + OPTION_TARGET_TIMELINE => 'target-timeline', + OPTION_TYPE => 'type', + + # Command-line-only/conf file options + OPTION_THREAD_MAX => 'thread-max', + + # Command-line-only help/version options + OPTION_HELP => 'help', + OPTION_VERSION => 'version', + + # Command-line-only test options + OPTION_TEST => 'test', + OPTION_TEST_DELAY => 'test-delay', + OPTION_TEST_NO_FORK => 'no-fork' +}; + +#################################################################################################################################### +# Option Defaults +#################################################################################################################################### +use constant +{ + OPTION_DEFAULT_CONFIG => '/etc/pg_backrest.conf', + OPTION_DEFAULT_THREAD_MAX => 1, + + OPTION_DEFAULT_BACKUP_FORCE => false, + OPTION_DEFAULT_BACKUP_NO_START_STOP => false, + OPTION_DEFAULT_BACKUP_TYPE => BACKUP_TYPE_INCR, + + OPTION_DEFAULT_RESTORE_DELTA => false, + OPTION_DEFAULT_RESTORE_FORCE => false, + OPTION_DEFAULT_RESTORE_SET => 'latest', + OPTION_DEFAULT_RESTORE_TYPE => RECOVERY_TYPE_DEFAULT, + OPTION_DEFAULT_RESTORE_TARGET_EXCLUSIVE => false, + OPTION_DEFAULT_RESTORE_TARGET_RESUME => false, + + OPTION_DEFAULT_HELP => false, + OPTION_DEFAULT_VERSION => false, + + OPTION_DEFAULT_TEST => false, + OPTION_DEFAULT_TEST_DELAY => 5, + OPTION_DEFAULT_TEST_NO_FORK => false +}; + +#################################################################################################################################### +# Option Rules +#################################################################################################################################### +use constant +{ + OPTION_RULE_ALLOW_LIST => 'allow-list', + OPTION_RULE_DEFAULT => 'default', + OPTION_RULE_DEPEND => 'depend', + OPTION_RULE_DEPEND_OPTION => 'depend-option', + OPTION_RULE_DEPEND_LIST => 'depend-list', + OPTION_RULE_DEPEND_VALUE => 'depend-value', + OPTION_RULE_REQUIRED => 'required', + OPTION_RULE_OPERATION => 'operation', + OPTION_RULE_TYPE => 'type' +}; + +#################################################################################################################################### +# Option Types +#################################################################################################################################### +use constant +{ + OPTION_TYPE_STRING => 'string', + OPTION_TYPE_BOOLEAN => 'boolean', + OPTION_TYPE_INTEGER => 'integer', + OPTION_TYPE_FLOAT => 'float' +}; + +#################################################################################################################################### +# Option Rule Hash +#################################################################################################################################### +my %oOptionRule = +( + &OPTION_CONFIG => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_CONFIG + }, + + &OPTION_DELTA => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_DELTA, + } + } + }, + + &OPTION_FORCE => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_FORCE, + }, + + &OP_BACKUP => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_BACKUP_FORCE, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_NO_START_STOP, + &OPTION_RULE_DEPEND_VALUE => true + } + } + } + }, + + &OPTION_NO_START_STOP => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_OPERATION => + { + &OP_BACKUP => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_BACKUP_NO_START_STOP + } + } + }, + + &OPTION_SET => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_TYPE, + } + } + }, + + &OPTION_STANZA => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING + }, + + &OPTION_TARGET => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TYPE, + &OPTION_RULE_DEPEND_LIST => + { + &RECOVERY_TYPE_NAME => true, + &RECOVERY_TYPE_TIME => true, + &RECOVERY_TYPE_XID => true + } + } + } + } + }, + + &OPTION_TARGET_EXCLUSIVE => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_TARGET_EXCLUSIVE, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TYPE, + &OPTION_RULE_DEPEND_LIST => + { + &RECOVERY_TYPE_TIME => true, + &RECOVERY_TYPE_XID => true + } + } + } + } + }, + + &OPTION_TARGET_RESUME => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_TARGET_RESUME, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TYPE, + &OPTION_RULE_DEPEND_LIST => + { + &RECOVERY_TYPE_NAME => true, + &RECOVERY_TYPE_TIME => true, + &RECOVERY_TYPE_XID => true + } + } + } + } + }, + + &OPTION_TARGET_TIMELINE => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING, + &OPTION_RULE_OPERATION => + { + &OP_RESTORE => + { + &OPTION_RULE_REQUIRED => false, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TYPE, + &OPTION_RULE_DEPEND_LIST => + { + &RECOVERY_TYPE_DEFAULT => true, + &RECOVERY_TYPE_NAME => true, + &RECOVERY_TYPE_TIME => true, + &RECOVERY_TYPE_XID => true + } + } + } + } + }, + + &OPTION_TYPE => + { + &OPTION_RULE_TYPE => OPTION_TYPE_STRING, + &OPTION_RULE_OPERATION => + { + &OP_BACKUP => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_BACKUP_TYPE, + &OPTION_RULE_ALLOW_LIST => + { + &BACKUP_TYPE_FULL => true, + &BACKUP_TYPE_DIFF => true, + &BACKUP_TYPE_INCR => true, + } + }, + + &OP_RESTORE => + { + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_RESTORE_TYPE, + &OPTION_RULE_ALLOW_LIST => + { + &RECOVERY_TYPE_NAME => true, + &RECOVERY_TYPE_TIME => true, + &RECOVERY_TYPE_XID => true, + &RECOVERY_TYPE_PRESERVE => true, + &RECOVERY_TYPE_NONE => true, + &RECOVERY_TYPE_DEFAULT => true + } + } + } + }, + + &OPTION_THREAD_MAX => + { + &OPTION_RULE_TYPE => OPTION_TYPE_INTEGER, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_THREAD_MAX, + &OPTION_RULE_OPERATION => + { + &OP_BACKUP => true, + &OP_RESTORE => true + } + }, + + &OPTION_HELP => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_HELP + }, + + &OPTION_VERSION => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_VERSION + }, + + &OPTION_TEST => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_TEST + }, + + &OPTION_TEST_DELAY => + { + &OPTION_RULE_TYPE => OPTION_TYPE_FLOAT, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_TEST_DELAY, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TEST, + &OPTION_RULE_DEPEND_VALUE => true + } + }, + + &OPTION_TEST_NO_FORK => + { + &OPTION_RULE_TYPE => OPTION_TYPE_BOOLEAN, + &OPTION_RULE_DEFAULT => OPTION_DEFAULT_TEST_NO_FORK, + &OPTION_RULE_DEPEND => + { + &OPTION_RULE_DEPEND_OPTION => OPTION_TEST + } + } +); + +#################################################################################################################################### +# Global variables +#################################################################################################################################### +my %oOption; # Option hash +my $strOperation; # Operation (backup, archive-get, ...) + +#################################################################################################################################### +# configLoad +# +# Load configuration. +#################################################################################################################################### +sub configLoad +{ + # Clear option in case it was loaded before + %oOption = (); + + # Build hash with all valid command-line options + my %oOption; + + foreach my $strKey (keys(%oOptionRule)) + { + my $strOption = $strKey; + + if (!defined($oOptionRule{$strKey}{&OPTION_RULE_TYPE})) + { + confess &log(ASSERT, "Option ${strKey} does not have a defined type", ERROR_ASSERT); + } + elsif ($oOptionRule{$strKey}{&OPTION_RULE_TYPE} ne OPTION_TYPE_BOOLEAN) + { + $strOption .= '=s'; + } + + $oOption{$strOption} = $strOption; + } + + # Get command-line options + my %oOptionTest; + + GetOptions(\%oOptionTest, %oOption) + or pod2usage(2); + + # Validate and store options + optionValid(\%oOptionTest); + + # Display version and exit if requested + if (optionGet(OPTION_VERSION) || optionGet(OPTION_HELP)) + { + print 'pg_backrest ' . version_get() . "\n"; + + if (!OPTION_get(OPTION_HELP)) + { + exit 0; + } + } + + # Display help and exit if requested + if (optionGet(OPTION_HELP)) + { + print "\n"; + pod2usage(); + } + + # Set test options + !optionGet(OPTION_TEST) or test_set(optionGet(OPTION_TEST), optionGet(OPTION_TEST_DELAY)); +} + +#################################################################################################################################### +# optionValid +# +# Make sure the command-line options are valid based on the operation. +#################################################################################################################################### +sub optionValid +{ + my $oOptionTest = shift; + + # Check that the operation is present and valid + $strOperation = $ARGV[0]; + + if (!defined($strOperation)) + { + confess &log(ERROR, "operation must be specified", ERROR_OPERATION_REQUIRED); + } + + if ($strOperation ne OP_ARCHIVE_GET && + $strOperation ne OP_ARCHIVE_PUSH && + $strOperation ne OP_BACKUP && + $strOperation ne OP_RESTORE && + $strOperation ne OP_EXPIRE) + { + confess &log(ERROR, "invalid operation ${strOperation}"); + } + + # Keep track of unresolved dependencies + my $bDependUnresolved = true; + my %oOptionResolved; + + # Loop through all possible options + while ($bDependUnresolved) + { + # Assume that all dependencies will be resolved in this loop + $bDependUnresolved = false; + + foreach my $strOption (sort(keys(%oOptionRule))) + { + # Skip the option if it has been resolved in a prior loop + if (defined($oOptionResolved{$strOption})) + { + next; + } + + # Check dependency for the operation then for the option + my $oDepend; + my $bDependResolved = true; + + if (defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}) && + defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) && + ref($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) eq 'HASH') + { + $oDepend = $oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}{&OPTION_RULE_DEPEND}; + } + + if (!defined($oDepend)) + { + $oDepend = $oOptionRule{$strOption}{&OPTION_RULE_DEPEND}; + } + + if (defined($oDepend)) + { + # Make sure the depend option has been resolved, otherwise skip this option for now + my $strDependOption = $$oDepend{&OPTION_RULE_DEPEND_OPTION}; + + if (!defined($oOptionResolved{$strDependOption})) + { + $bDependUnresolved = true; + next; + } + + # Check if the depend option has a value + my $strDependValue = $oOption{$strDependOption}; + my $strError = "option '${strOption}' not valid without option '${strDependOption}'"; + + $bDependResolved = defined($strDependValue) ? true : false; + + if (!$bDependResolved && defined($$oOptionTest{$strOption})) + { + confess &log(ERROR, $strError, ERROR_OPTION_INVALID); + } + + # If a depend value exists, make sure the option value matches + if ($bDependResolved && defined($$oDepend{&OPTION_RULE_DEPEND_VALUE}) && + $$oDepend{&OPTION_RULE_DEPEND_VALUE} ne $strDependValue) + { + $bDependResolved = false; + + if (defined($$oOptionTest{$strOption})) + { + if ($oOptionRule{$strDependOption}{&OPTION_RULE_TYPE} eq OPTION_TYPE_BOOLEAN) + { + if (!$$oDepend{&OPTION_RULE_DEPEND_VALUE}) + { + confess &log(ASSERT, "no error has been created for unused case where depend value = false"); + } + } + else + { + $strError .= " = '$$oDepend{&OPTION_RULE_DEPEND_VALUE}'"; + } + + confess &log(ERROR, $strError, ERROR_OPTION_INVALID); + } + } + + # If a depend list exists, make sure the value is in the list + if ($bDependResolved && defined($$oDepend{&OPTION_RULE_DEPEND_LIST}) && + !defined($$oDepend{&OPTION_RULE_DEPEND_LIST}{$strDependValue})) + { + $bDependResolved = false; + + if (defined($$oOptionTest{$strOption})) + { + my @oyValue; + + foreach my $strValue (sort(keys($$oDepend{&OPTION_RULE_DEPEND_LIST}))) + { + push(@oyValue, "'${strValue}'"); + } + + $strError .= @oyValue == 1 ? " = $oyValue[0]" : " in (" . join(", ", @oyValue) . ")"; + confess &log(ERROR, $strError, ERROR_OPTION_INVALID); + } + } + } + + # Is the option defined? + if (defined($$oOptionTest{$strOption})) + { + my $strValue = $$oOptionTest{$strOption}; + + # Test option type + if ($oOptionRule{$strOption}{&OPTION_RULE_TYPE} eq OPTION_TYPE_INTEGER || + $oOptionRule{$strOption}{&OPTION_RULE_TYPE} eq OPTION_TYPE_FLOAT) + { + # Test that the string is a valid float or integer by adding 1 to it. It's pretty hokey but it works and it + # beats requiring Scalar::Util::Numeric to do it properly. + eval + { + my $strTest = $strValue + 1; + }; + + my $bError = $@ ? true : false; + + # Check that integers are really integers + if (!$bError && $oOptionRule{$strOption}{&OPTION_RULE_TYPE} eq OPTION_TYPE_INTEGER && + (int($strValue) . 'S') ne ($strValue . 'S')) + { + $bError = true; + } + + # Error if the value did not pass tests + !$bError + or confess &log(ERROR, "'${strValue}' is not valid for '${strOption}' option", ERROR_OPTION_INVALID_VALUE); + } + + # Process an allow list for the operation then for the option + my $oAllow; + + if (defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}) && + defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) && + ref($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) eq 'HASH') + { + $oAllow = $oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}{&OPTION_RULE_ALLOW_LIST}; + } + + if (!defined($oAllow)) + { + $oAllow = $oOptionRule{$strOption}{&OPTION_RULE_ALLOW_LIST}; + } + + if (defined($oAllow) && !defined($$oAllow{$$oOptionTest{$strOption}})) + { + confess &log(ERROR, "'${strValue}' is not valid for '${strOption}' option", ERROR_OPTION_INVALID_VALUE); + } + + # Set option value + $oOption{$strOption} = $strValue; + } + # Else set the default if required + elsif (!defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}) || + defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation})) + { + # Check for default in operation then option + my $strDefault; + + if (defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}) && + defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) && + ref($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) eq 'HASH') + { + $strDefault = $oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}{&OPTION_RULE_DEFAULT} + } + + if (!defined($strDefault)) + { + $strDefault = $oOptionRule{$strOption}{&OPTION_RULE_DEFAULT}; + } + + # If default is defined + if (defined($strDefault)) + { + # Only set default if dependency is resolved + $oOption{$strOption} = $strDefault if $bDependResolved; + } + # Else error + else + { + # Check for required in operation then option + my $bRequired; + + if (defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}) && + defined($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) && + ref($oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}) eq 'HASH') + { + $bRequired = $oOptionRule{$strOption}{&OPTION_RULE_OPERATION}{$strOperation}{&OPTION_RULE_REQUIRED}; + } + + if (!defined($bRequired)) + { + $bRequired = $oOptionRule{$strOption}{&OPTION_RULE_REQUIRED}; + } + + if (!defined($bRequired) || $bRequired) + { + if ($bDependResolved) + { + confess &log(ERROR, "${strOperation} operation requires option: ${strOption}", ERROR_OPTION_REQUIRED); + } + } + } + } + + $oOptionResolved{$strOption} = true; + } + } +} + +#################################################################################################################################### +# operationGet +# +# Get the current operation. +#################################################################################################################################### +sub operationGet +{ + return $strOperation; +} + +#################################################################################################################################### +# operationTest +# +# Test the current operation. +#################################################################################################################################### +sub operationTest +{ + my $strOperationTest = shift; + + return $strOperationTest eq $strOperation; +} + +#################################################################################################################################### +# operationSet +# +# Set current operation (usually for triggering follow-on operations). +#################################################################################################################################### +sub operationSet +{ + my $strValue = shift; + + $strOperation = $strValue; +} + +#################################################################################################################################### +# optionGet +# +# Get option value. +#################################################################################################################################### +sub optionGet +{ + my $strOption = shift; + my $bRequired = shift; + + if (!defined($oOption{$strOption}) && (!defined($bRequired) || $bRequired)) + { + confess &log(ASSERT, "option ${strOption} is required"); + } + + return $oOption{$strOption}; +} + +#################################################################################################################################### +# optionTest +# +# Test a option value. +#################################################################################################################################### +sub optionTest +{ + my $strOption = shift; + my $strValue = shift; + + if (defined($strValue)) + { + return optionGet($strOption) eq $strValue; + } + + return defined($oOption{$strOption}); +} + +#################################################################################################################################### +# optionRuleGet +# +# Get the option rules. +#################################################################################################################################### +sub optionRuleGet +{ + return dclone(\%oOptionRule); +} + +1; diff --git a/lib/BackRest/Restore.pm b/lib/BackRest/Restore.pm index 3ebfe6605..135588978 100644 --- a/lib/BackRest/Restore.pm +++ b/lib/BackRest/Restore.pm @@ -17,6 +17,7 @@ use lib dirname($0); use BackRest::Exception; use BackRest::Utility; use BackRest::ThreadGroup; +use BackRest::Param; use BackRest::Config; use BackRest::Manifest; use BackRest::File; @@ -50,6 +51,7 @@ sub new # Initialize variables $self->{strDbClusterPath} = $strDbClusterPath; + $self->{strBackupPath} = $strBackupPath; $self->{oRemapRef} = $oRemapRef; $self->{oFile} = $oFile; $self->{iThreadTotal} = defined($iThreadTotal) ? $iThreadTotal : 1; @@ -65,16 +67,6 @@ sub new $self->{strBackRestBin} = $strBackRestBin; $self->{strConfigFile} = $strConfigFile; - # If backup path is not specified then default to latest - if (defined($strBackupPath)) - { - $self->{strBackupPath} = $strBackupPath; - } - else - { - $self->{strBackupPath} = PATH_LATEST; - } - return $self; } @@ -196,7 +188,7 @@ sub manifest_load # If backup is latest then set it equal to backup label, else verify that requested backup and label match my $strBackupLabel = $oManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL); - if ($self->{strBackupPath} eq PATH_LATEST) + if ($self->{strBackupPath} eq OPTION_DEFAULT_RESTORE_SET) { $self->{strBackupPath} = $strBackupLabel; } diff --git a/test/lib/BackRestTest/BackupTest.pm b/test/lib/BackRestTest/BackupTest.pm index f43ec57cf..aca69c088 100755 --- a/test/lib/BackRestTest/BackupTest.pm +++ b/test/lib/BackRestTest/BackupTest.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl #################################################################################################################################### -# BackupTest.pl - Unit Tests for BackRest::File +# BackupTest.pl - Unit Tests for BackRest::Backup and BackRest::Restore #################################################################################################################################### package BackRestTest::BackupTest; @@ -21,6 +21,7 @@ use DBI; use lib dirname($0) . '/../lib'; use BackRest::Exception; use BackRest::Utility; +use BackRest::Param; use BackRest::Config; use BackRest::Manifest; use BackRest::File; @@ -1963,7 +1964,7 @@ sub BackRestTestBackup_Test # Static backup parameters my $bSynthetic = false; - my $fTestDelay = .25; + my $fTestDelay = 1; # Variable backup parameters my $bDelta = true; diff --git a/test/lib/BackRestTest/ConfigTest.pm b/test/lib/BackRestTest/ConfigTest.pm new file mode 100755 index 000000000..9cd5d1620 --- /dev/null +++ b/test/lib/BackRestTest/ConfigTest.pm @@ -0,0 +1,392 @@ +#!/usr/bin/perl +#################################################################################################################################### +# ConfigTest.pl - Unit Tests for BackRest::Param and BackRest::Config +#################################################################################################################################### +package BackRestTest::ConfigTest; + +#################################################################################################################################### +# Perl includes +#################################################################################################################################### +use strict; +use warnings FATAL => qw(all); +use Carp qw(confess); + +use File::Basename qw(dirname); +use Scalar::Util 'blessed'; +#use Data::Dumper qw(Dumper); +#use Scalar::Util qw(blessed); +# use Test::More qw(no_plan); +# use Test::Deep; + +use lib dirname($0) . '/../lib'; +use BackRest::Exception; +use BackRest::Utility; +use BackRest::Param; + +use BackRestTest::CommonTest; + +use Exporter qw(import); +our @EXPORT = qw(BackRestTestConfig_Test); + +sub optionSetTest +{ + my $oOption = shift; + my $strKey = shift; + my $strValue = shift; + + $$oOption{option}{$strKey} = $strValue; +} + +sub optionSetBoolTest +{ + my $oOption = shift; + my $strKey = shift; + + $$oOption{boolean}{$strKey} = true; +} + +sub operationSetTest +{ + my $oOption = shift; + my $strOperation = shift; + + $$oOption{operation} = $strOperation; +} + +sub optionRemoveTest +{ + my $oOption = shift; + my $strKey = shift; + + delete($$oOption{option}{$strKey}); + delete($$oOption{boolean}{$strKey}); +} + +sub argvWriteTest +{ + my $oOption = shift; + + @ARGV = (); + + if (defined($$oOption{boolean})) + { + foreach my $strKey (keys $$oOption{boolean}) + { + if ($$oOption{boolean}{$strKey}) + { + $ARGV[@ARGV] = "--${strKey}"; + } + else + { + $ARGV[@ARGV] = "--no-${strKey}"; + } + } + } + + if (defined($$oOption{option})) + { + foreach my $strKey (keys $$oOption{option}) + { + $ARGV[@ARGV] = "--${strKey}="; + + if (defined($$oOption{option}{$strKey})) + { + $ARGV[@ARGV - 1] .= $$oOption{option}{$strKey}; + } + } + } + + $ARGV[@ARGV] = $$oOption{operation}; + + &log(INFO, " command line: " . join(" ", @ARGV)); + + %$oOption = (); +} + +sub configLoadExpectError +{ + my $oOption = shift; + my $strOperation = shift; + my $iExpectedError = shift; + my $strErrorParam1 = shift; + my $strErrorParam2 = shift; + my $strErrorParam3 = shift; + + my $oOptionRuleExpected = optionRuleGet(); + + operationSetTest($oOption, $strOperation); + argvWriteTest($oOption); + + eval + { + configLoad(); + }; + + if ($@) + { + if (!defined($iExpectedError)) + { + confess $@; + } + + my $oMessage = $@; + + if (blessed($oMessage) && $oMessage->isa('BackRest::Exception')) + { + if ($oMessage->code() != $iExpectedError) + { + confess "expected error ${iExpectedError} from configLoad but got " . $oMessage->code(); + } + + my $strError; + + if ($iExpectedError == ERROR_OPTION_REQUIRED) + { + $strError = "backup operation requires option: ${strErrorParam1}"; + } + elsif ($iExpectedError == ERROR_OPERATION_REQUIRED) + { + $strError = "operation must be specified"; + } + elsif ($iExpectedError == ERROR_OPTION_INVALID) + { + $strError = "option '${strErrorParam1}' not valid without option '${strErrorParam2}'"; + + if (defined($strErrorParam3)) + { + $strError .= @{$strErrorParam3} == 1 ? " = '$$strErrorParam3[0]'" : + " in ('" . join("', '",@{ $strErrorParam3}) . "')"; + } + } + elsif ($iExpectedError == ERROR_OPTION_INVALID_VALUE) + { + $strError = "'${strErrorParam1}' is not valid for '${strErrorParam2}' option"; + } + else + { + confess "must construct message for error ${iExpectedError}, use this as an example: '" . $oMessage->message() . "'"; + } + + if ($oMessage->message() ne $strError) + { + confess "expected error message \"${strError}\" from configLoad but got \"" . $oMessage->message() . "\""; + } + } + else + { + confess "configLoad should throw BackRest::Exception:\n$oMessage"; + } + } + else + { + if (defined($iExpectedError)) + { + confess "expected error ${iExpectedError} from configLoad but got success"; + } + } + + # cmp_deeply(OPTION_rule_get(), $oOptionRuleExpected, 'compare original and new rule hashes') + # or die 'comparison failed'; +} + +sub optionTestExpect +{ + my $strOption = shift; + my $strExpectedValue = shift; + + if (defined($strExpectedValue)) + { + my $strActualValue = optionGet($strOption); + + $strActualValue eq $strExpectedValue + or confess "expected option ${strOption} to have value ${strExpectedValue}, but ${strActualValue} found instead"; + } + elsif (optionTest($strOption)) + { + confess "expected option ${strOption} to be [undef], but " . optionGet($strOption) . ' found instead'; + } +} + +#################################################################################################################################### +# BackRestTestConfig_Test +#################################################################################################################################### +sub BackRestTestConfig_Test +{ + my $strTest = shift; + + # Setup test variables + my $iRun; + my $bCreate; + my $strStanza = 'main'; + my $oOption = {}; + my @oyArray; + use constant BOGUS => 'bogus'; + + # Print test banner + &log(INFO, 'CONFIG MODULE ******************************************************************'); + + #------------------------------------------------------------------------------------------------------------------------------- + # Test config + #------------------------------------------------------------------------------------------------------------------------------- + if ($strTest eq 'all' || $strTest eq 'option') + { + &log(INFO, "Option module\n"); + + if (BackRestTestCommon_Run(++$iRun, 'backup with no stanza')) + { + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_REQUIRED, OPTION_STANZA); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup with boolean stanza')) + { + optionSetBoolTest($oOption, OPTION_STANZA); + + configLoadExpectError($oOption, OP_BACKUP, , ERROR_OPERATION_REQUIRED); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup type defaults to ' . BACKUP_TYPE_INCR)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + + configLoadExpectError($oOption, OP_BACKUP); + optionTestExpect(OPTION_TYPE, BACKUP_TYPE_INCR); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup type set to ' . BACKUP_TYPE_FULL)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TYPE, BACKUP_TYPE_FULL); + + configLoadExpectError($oOption, OP_BACKUP); + optionTestExpect(OPTION_TYPE, BACKUP_TYPE_FULL); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup type invalid')) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TYPE, BOGUS); + + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_INVALID_VALUE, BOGUS, OPTION_TYPE); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup invalid force')) + { +# $oOption = {}; + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetBoolTest($oOption, OPTION_FORCE); + + configLoadExpectError($oOption, OP_BACKUP, ERROR_OPTION_INVALID, OPTION_FORCE, OPTION_NO_START_STOP); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup valid force')) + { + # $oOption = {}; + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetBoolTest($oOption, OPTION_NO_START_STOP); + optionSetBoolTest($oOption, OPTION_FORCE); + + configLoadExpectError($oOption, OP_BACKUP); + optionTestExpect(OPTION_NO_START_STOP, true); + optionTestExpect(OPTION_FORCE, true); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup invalid value for ' . OPTION_TEST_DELAY)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetBoolTest($oOption, OPTION_TEST); + optionSetTest($oOption, OPTION_TEST_DELAY, BOGUS); + + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_INVALID_VALUE, BOGUS, OPTION_TEST_DELAY); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup invalid ' . OPTION_TEST_DELAY)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TEST_DELAY, 5); + + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_INVALID, OPTION_TEST_DELAY, OPTION_TEST); + } + + if (BackRestTestCommon_Run(++$iRun, 'backup check ' . OPTION_TEST_DELAY . ' undef')) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + + configLoadExpectError($oOption, OP_BACKUP); + optionTestExpect(OPTION_TEST_DELAY); + } + + if (BackRestTestCommon_Run(++$iRun, 'restore invalid ' . OPTION_TARGET)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TYPE, RECOVERY_TYPE_DEFAULT); + optionSetTest($oOption, OPTION_TARGET, BOGUS); + + @oyArray = (RECOVERY_TYPE_NAME, RECOVERY_TYPE_TIME, RECOVERY_TYPE_XID); + configLoadExpectError($oOption, OP_RESTORE , ERROR_OPTION_INVALID, OPTION_TARGET, OPTION_TYPE, \@oyArray); + } + + if (BackRestTestCommon_Run(++$iRun, 'restore ' . OPTION_TARGET)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TYPE, RECOVERY_TYPE_NAME); + optionSetTest($oOption, OPTION_TARGET, BOGUS); + + configLoadExpectError($oOption, OP_RESTORE); + optionTestExpect(OPTION_TYPE, RECOVERY_TYPE_NAME); + optionTestExpect(OPTION_TARGET, BOGUS); + optionTestExpect(OPTION_TARGET_TIMELINE); + } + + if (BackRestTestCommon_Run(++$iRun, 'invalid string ' . OPTION_THREAD_MAX)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_THREAD_MAX, BOGUS); + + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_INVALID_VALUE, BOGUS, OPTION_THREAD_MAX); + } + + if (BackRestTestCommon_Run(++$iRun, 'invalid float ' . OPTION_THREAD_MAX)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_THREAD_MAX, '0.0'); + + configLoadExpectError($oOption, OP_BACKUP , ERROR_OPTION_INVALID_VALUE, '0.0', OPTION_THREAD_MAX); + } + + if (BackRestTestCommon_Run(++$iRun, 'valid ' . OPTION_THREAD_MAX)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_THREAD_MAX, '2'); + + configLoadExpectError($oOption, OP_BACKUP); + } + + if (BackRestTestCommon_Run(++$iRun, 'valid float ' . OPTION_TEST_DELAY)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetBoolTest($oOption, OPTION_TEST); + optionSetTest($oOption, OPTION_TEST_DELAY, '0.25'); + + configLoadExpectError($oOption, OP_BACKUP); + } + + if (BackRestTestCommon_Run(++$iRun, 'valid int ' . OPTION_TEST_DELAY)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetBoolTest($oOption, OPTION_TEST); + optionSetTest($oOption, OPTION_TEST_DELAY, 3); + + configLoadExpectError($oOption, OP_BACKUP); + } + + if (BackRestTestCommon_Run(++$iRun, 'restore valid ' . OPTION_TARGET_TIMELINE)) + { + optionSetTest($oOption, OPTION_STANZA, $strStanza); + optionSetTest($oOption, OPTION_TARGET_TIMELINE, 2); + + configLoadExpectError($oOption, OP_RESTORE); + } + } +} + +1; diff --git a/test/lib/BackRestTest/UtilityTest.pm b/test/lib/BackRestTest/UtilityTest.pm index 80312abf5..73e22e020 100755 --- a/test/lib/BackRestTest/UtilityTest.pm +++ b/test/lib/BackRestTest/UtilityTest.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl #################################################################################################################################### -# BackupTest.pl - Unit Tests for BackRest::File +# UtilityTest.pl - Unit Tests for BackRest::Utility #################################################################################################################################### package BackRestTest::UtilityTest; diff --git a/test/test.pl b/test/test.pl index fe66393d9..b352dcd25 100755 --- a/test/test.pl +++ b/test/test.pl @@ -14,6 +14,7 @@ use File::Basename; use Getopt::Long; use Cwd 'abs_path'; use Pod::Usage; +#use Test::More; use lib dirname($0) . '/../lib'; use BackRest::Utility; @@ -21,6 +22,7 @@ use BackRest::Utility; use lib dirname($0) . '/lib'; use BackRestTest::CommonTest; use BackRestTest::UtilityTest; +use BackRestTest::ConfigTest; use BackRestTest::FileTest; use BackRestTest::BackupTest; @@ -102,6 +104,8 @@ if ($bVersion || $bHelp) exit 0; } +# Test::More->builder->output('/dev/null'); + #################################################################################################################################### # Setup #################################################################################################################################### @@ -229,6 +233,11 @@ do BackRestTestUtility_Test($strModuleTest); } + if ($strModule eq 'all' || $strModule eq 'config') + { + BackRestTestConfig_Test($strModuleTest); + } + if ($strModule eq 'all' || $strModule eq 'file') { BackRestTestFile_Test($strModuleTest);