1#################################################################################################################################### 2# ARCHIVE INFO MODULE 3# 4# The archive.info file is created when archiving begins. It is located under the stanza directory. The file contains information 5# regarding the stanza database version, database WAL segment system id and other information to ensure that archiving is being 6# performed on the proper database. 7#################################################################################################################################### 8package pgBackRestTest::Env::ArchiveInfo; 9use parent 'pgBackRestDoc::Common::Ini'; 10 11use strict; 12use warnings FATAL => qw(all); 13use Carp qw(confess); 14use English '-no_match_vars'; 15 16use Exporter qw(import); 17 our @EXPORT = qw(); 18use File::Basename qw(dirname basename); 19 20use pgBackRestDoc::Common::Exception; 21use pgBackRestDoc::Common::Ini; 22use pgBackRestDoc::Common::Log; 23 24use pgBackRestTest::Common::DbVersion; 25use pgBackRestTest::Common::StorageBase; 26use pgBackRestTest::Common::StorageRepo; 27use pgBackRestTest::Env::InfoCommon; 28use pgBackRestTest::Env::Manifest; 29 30#################################################################################################################################### 31# File/path constants 32#################################################################################################################################### 33use constant ARCHIVE_INFO_FILE => 'archive.info'; 34 push @EXPORT, qw(ARCHIVE_INFO_FILE); 35 36#################################################################################################################################### 37# RegEx constants 38#################################################################################################################################### 39use constant REGEX_ARCHIVE_DIR_DB_VERSION => '^[0-9]+(\.[0-9]+)*-[0-9]+$'; 40 push @EXPORT, qw(REGEX_ARCHIVE_DIR_DB_VERSION); 41use constant REGEX_ARCHIVE_DIR_WAL => '^[0-F]{16}$'; 42 push @EXPORT, qw(REGEX_ARCHIVE_DIR_WAL); 43 44#################################################################################################################################### 45# WAL segment size 46#################################################################################################################################### 47use constant PG_WAL_SEGMENT_SIZE => 16777216; 48 push @EXPORT, qw(PG_WAL_SEGMENT_SIZE); 49 50#################################################################################################################################### 51# Archive info constants 52#################################################################################################################################### 53use constant INFO_ARCHIVE_SECTION_DB => INFO_BACKUP_SECTION_DB; 54 push @EXPORT, qw(INFO_ARCHIVE_SECTION_DB); 55use constant INFO_ARCHIVE_SECTION_DB_HISTORY => INFO_BACKUP_SECTION_DB_HISTORY; 56 push @EXPORT, qw(INFO_ARCHIVE_SECTION_DB_HISTORY); 57 58use constant INFO_ARCHIVE_KEY_DB_VERSION => MANIFEST_KEY_DB_VERSION; 59 push @EXPORT, qw(INFO_ARCHIVE_KEY_DB_VERSION); 60use constant INFO_ARCHIVE_KEY_DB_ID => MANIFEST_KEY_DB_ID; 61 push @EXPORT, qw(INFO_ARCHIVE_KEY_DB_ID); 62use constant INFO_ARCHIVE_KEY_DB_SYSTEM_ID => MANIFEST_KEY_SYSTEM_ID; 63 push @EXPORT, qw(INFO_ARCHIVE_KEY_DB_SYSTEM_ID); 64 65#################################################################################################################################### 66# Global variables 67#################################################################################################################################### 68my $strArchiveInfoMissingMsg = 69 ARCHIVE_INFO_FILE . " does not exist but is required to push/get WAL segments\n" . 70 "HINT: is archive_command configured in postgresql.conf?\n" . 71 "HINT: has a stanza-create been performed?\n" . 72 "HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme."; 73 74#################################################################################################################################### 75# CONSTRUCTOR 76#################################################################################################################################### 77sub new 78{ 79 my $class = shift; # Class name 80 81 # Assign function parameters, defaults, and log debug info 82 my 83 ( 84 $strOperation, 85 $strArchiveClusterPath, # Archive cluster path 86 $bRequired, # Is archive info required? 87 $bLoad, # Should the file attempt to be loaded? 88 $bIgnoreMissing, # Don't error on missing files 89 $strCipherPassSub, # Passphrase to encrypt the subsequent archive files if repo is encrypted 90 ) = 91 logDebugParam 92 ( 93 __PACKAGE__ . '->new', \@_, 94 {name => 'strArchiveClusterPath'}, 95 {name => 'bRequired', default => true}, 96 {name => 'bLoad', optional => true, default => true}, 97 {name => 'bIgnoreMissing', optional => true, default => false}, 98 {name => 'strCipherPassSub', optional => true}, 99 ); 100 101 # Build the archive info path/file name 102 my $strArchiveInfoFile = "${strArchiveClusterPath}/" . ARCHIVE_INFO_FILE; 103 my $self = {}; 104 my $iResult = 0; 105 my $strResultMessage; 106 107 # Init object and store variables 108 eval 109 { 110 $self = $class->SUPER::new( 111 storageRepo(), $strArchiveInfoFile, 112 {bLoad => $bLoad, bIgnoreMissing => $bIgnoreMissing, strCipherPassSub => $strCipherPassSub}); 113 return true; 114 } 115 or do 116 { 117 # Capture error information 118 $iResult = exceptionCode($EVAL_ERROR); 119 $strResultMessage = exceptionMessage($EVAL_ERROR); 120 }; 121 122 if ($iResult != 0) 123 { 124 # If the file does not exist but is required to exist, then error 125 # The archive info is only allowed not to exist when running a stanza-create on a new install 126 if ($iResult == ERROR_FILE_MISSING) 127 { 128 if ($bRequired) 129 { 130 confess &log(ERROR, $strArchiveInfoMissingMsg, ERROR_FILE_MISSING); 131 } 132 } 133 elsif ($iResult == ERROR_CRYPTO && $strResultMessage =~ "^unable to flush") 134 { 135 confess &log(ERROR, "unable to parse '$strArchiveInfoFile'\nHINT: is or was the repo encrypted?", $iResult); 136 } 137 else 138 { 139 confess $EVAL_ERROR; 140 } 141 } 142 143 $self->{strArchiveClusterPath} = $strArchiveClusterPath; 144 145 # Return from function and log return values if any 146 return logDebugReturn 147 ( 148 $strOperation, 149 {name => 'self', value => $self} 150 ); 151} 152 153#################################################################################################################################### 154# check 155# 156# Check archive info file and make sure it is compatible with the current version of the database for the stanza. If the file does 157# not exist an error will occur. 158#################################################################################################################################### 159sub check 160{ 161 my $self = shift; 162 163 # Assign function parameters, defaults, and log debug info 164 my 165 ( 166 $strOperation, 167 $strDbVersion, 168 $ullDbSysId, 169 $bRequired, 170 ) = 171 logDebugParam 172 ( 173 __PACKAGE__ . '->check', \@_, 174 {name => 'strDbVersion'}, 175 {name => 'ullDbSysId'}, 176 {name => 'bRequired', default => true}, 177 ); 178 179 # ??? remove bRequired after stanza-upgrade 180 if ($bRequired) 181 { 182 # Confirm the info file exists with the DB section 183 $self->confirmExists(); 184 } 185 186 my $strError = undef; 187 188 if (!$self->test(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_VERSION, undef, $strDbVersion)) 189 { 190 $strError = "WAL segment version ${strDbVersion} does not match archive version " . 191 $self->get(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_VERSION); 192 } 193 194 if (!$self->test(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_SYSTEM_ID, undef, $ullDbSysId)) 195 { 196 $strError = (defined($strError) ? ($strError . "\n") : "") . 197 "WAL segment system-id ${ullDbSysId} does not match archive system-id " . 198 $self->get(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_SYSTEM_ID); 199 } 200 201 if (defined($strError)) 202 { 203 confess &log(ERROR, "${strError}\nHINT: are you archiving to the correct stanza?", ERROR_ARCHIVE_MISMATCH); 204 } 205 206 # Return from function and log return values if any 207 return logDebugReturn 208 ( 209 $strOperation, 210 {name => 'strArchiveId', value => $self->archiveId()} 211 ); 212} 213 214#################################################################################################################################### 215# archiveId 216# 217# Get the archive id which is a combination of the DB version and the db-id setting (e.g. 9.4-1) 218#################################################################################################################################### 219sub archiveId 220{ 221 my $self = shift; 222 223 # Assign function parameters, defaults, and log debug info 224 my 225 ( 226 $strOperation, 227 $strDbVersion, 228 $ullDbSysId, 229 ) = logDebugParam 230 ( 231 __PACKAGE__ . '->archiveId', \@_, 232 {name => 'strDbVersion', optional => true}, 233 {name => 'ullDbSysId', optional => true}, 234 ); 235 236 my $strArchiveId = undef; 237 238 # If neither optional version and system-id are passed then set the archive id to the current one 239 if (!defined($strDbVersion) && !defined($ullDbSysId)) 240 { 241 $strArchiveId = $self->get(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_VERSION) . "-" . 242 $self->get(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_ID); 243 } 244 # If both the optional version and system-id are passed 245 elsif (defined($strDbVersion) && defined($ullDbSysId)) 246 { 247 # Get the newest archiveId for the version/system-id passed 248 $strArchiveId = ($self->archiveIdList($strDbVersion, $ullDbSysId))[0]; 249 } 250 251 # Return from function and log return values if any 252 return logDebugReturn 253 ( 254 $strOperation, 255 {name => 'strArchiveId', value => $strArchiveId} 256 ); 257} 258 259#################################################################################################################################### 260# archiveIdList 261# 262# Get a sorted list of the archive ids for the db-version and db-system-id passed. 263#################################################################################################################################### 264sub archiveIdList 265{ 266 my $self = shift; 267 268 # Assign function parameters, defaults, and log debug info 269 my 270 ( 271 $strOperation, 272 $strDbVersion, 273 $ullDbSysId, 274 ) = logDebugParam 275 ( 276 __PACKAGE__ . '->archiveIdList', \@_, 277 {name => 'strDbVersion'}, 278 {name => 'ullDbSysId'}, 279 ); 280 281 my @stryArchiveId; 282 283 # Get the version and system-id for all known databases 284 my $hDbList = $self->dbHistoryList(); 285 286 foreach my $iDbHistoryId (sort {$a <=> $b} keys %$hDbList) 287 { 288 # If the version and system-id match then construct the archive id so that the constructed array has the newest match first 289 if (($hDbList->{$iDbHistoryId}{&INFO_DB_VERSION} eq $strDbVersion) && 290 ($hDbList->{$iDbHistoryId}{&INFO_SYSTEM_ID} eq $ullDbSysId)) 291 { 292 unshift(@stryArchiveId, $strDbVersion . "-" . $iDbHistoryId); 293 } 294 } 295 296 # If the archive id has still not been found, then error 297 if (@stryArchiveId == 0) 298 { 299 confess &log( 300 ERROR, "unable to retrieve the archive id for database version '$strDbVersion' and system-id '$ullDbSysId'", 301 ERROR_ARCHIVE_MISMATCH); 302 } 303 304 # Return from function and log return values if any 305 return logDebugReturn 306 ( 307 $strOperation, 308 {name => 'stryArchiveId', value => \@stryArchiveId} 309 ); 310} 311 312#################################################################################################################################### 313# create 314# 315# Creates the archive.info file. WARNING - this function should only be called from stanza-create or tests. 316#################################################################################################################################### 317sub create 318{ 319 my $self = shift; 320 321 # Assign function parameters, defaults, and log debug info 322 my 323 ( 324 $strOperation, 325 $strDbVersion, 326 $ullDbSysId, 327 $bSave, 328 ) = 329 logDebugParam 330 ( 331 __PACKAGE__ . '->create', \@_, 332 {name => 'strDbVersion'}, 333 {name => 'ullDbSysId'}, 334 {name => 'bSave', default => true}, 335 ); 336 337 # Fill db section and db history section 338 $self->dbSectionSet($strDbVersion, $ullDbSysId, $self->dbHistoryIdGet(false)); 339 340 if ($bSave) 341 { 342 $self->save(); 343 } 344 345 # Return from function and log return values if any 346 return logDebugReturn($strOperation); 347} 348 349#################################################################################################################################### 350# dbHistoryIdGet 351# 352# Get the db history ID 353#################################################################################################################################### 354sub dbHistoryIdGet 355{ 356 my $self = shift; 357 358 # Assign function parameters, defaults, and log debug info 359 my 360 ( 361 $strOperation, 362 $bFileRequired, 363 ) = 364 logDebugParam 365 ( 366 __PACKAGE__ . '->dbHistoryIdGet', \@_, 367 {name => 'bFileRequired', default => true}, 368 ); 369 370 # Confirm the info file exists if it is required 371 if ($bFileRequired) 372 { 373 $self->confirmExists(); 374 } 375 376 # If the DB section does not exist, initialize the history to one, else return the latest ID 377 my $iDbHistoryId = (!$self->test(INFO_ARCHIVE_SECTION_DB)) 378 ? 1 : $self->numericGet(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_ID); 379 380 # Return from function and log return values if any 381 return logDebugReturn 382 ( 383 $strOperation, 384 {name => 'iDbHistoryId', value => $iDbHistoryId} 385 ); 386} 387 388#################################################################################################################################### 389# dbHistoryList 390# 391# Get the data from the db history section. 392#################################################################################################################################### 393sub dbHistoryList 394{ 395 my $self = shift; 396 my 397 ( 398 $strOperation, 399 ) = logDebugParam 400 ( 401 __PACKAGE__ . '->dbHistoryList', 402 ); 403 404 my %hDbHash; 405 406 foreach my $iHistoryId ($self->keys(INFO_ARCHIVE_SECTION_DB_HISTORY)) 407 { 408 $hDbHash{$iHistoryId}{&INFO_DB_VERSION} = 409 $self->get(INFO_ARCHIVE_SECTION_DB_HISTORY, $iHistoryId, INFO_ARCHIVE_KEY_DB_VERSION); 410 $hDbHash{$iHistoryId}{&INFO_SYSTEM_ID} = 411 $self->get(INFO_ARCHIVE_SECTION_DB_HISTORY, $iHistoryId, INFO_ARCHIVE_KEY_DB_ID); 412 } 413 414 # Return from function and log return values if any 415 return logDebugReturn 416 ( 417 $strOperation, 418 {name => 'hDbHash', value => \%hDbHash} 419 ); 420} 421 422#################################################################################################################################### 423# dbSectionSet 424# 425# Set the db and db:history sections. 426#################################################################################################################################### 427sub dbSectionSet 428{ 429 my $self = shift; 430 431 # Assign function parameters, defaults, and log debug info 432 my 433 ( 434 $strOperation, 435 $strDbVersion, 436 $ullDbSysId, 437 $iDbHistoryId, 438 ) = 439 logDebugParam 440 ( 441 __PACKAGE__ . '->dbSectionSet', \@_, 442 {name => 'strDbVersion', trace => true}, 443 {name => 'ullDbSysId', trace => true}, 444 {name => 'iDbHistoryId', trace => true} 445 ); 446 447 # Fill db section 448 $self->numericSet(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_SYSTEM_ID, undef, $ullDbSysId); 449 # Force the version to a string since newer versions of JSON::PP lose track of the fact that it is one 450 $self->set(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_VERSION, undef, $strDbVersion . ''); 451 $self->numericSet(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_ID, undef, $iDbHistoryId); 452 453 # Fill db history 454 $self->numericSet(INFO_ARCHIVE_SECTION_DB_HISTORY, $iDbHistoryId, INFO_ARCHIVE_KEY_DB_ID, $ullDbSysId); 455 # Force the version to a string since newer versions of JSON::PP lose track of the fact that it is one 456 $self->set(INFO_ARCHIVE_SECTION_DB_HISTORY, $iDbHistoryId, INFO_ARCHIVE_KEY_DB_VERSION, $strDbVersion . ''); 457 458 # Return from function and log return values if any 459 return logDebugReturn($strOperation); 460} 461 462#################################################################################################################################### 463# confirmExists 464# 465# Ensure that the archive.info file and the db section exist. 466#################################################################################################################################### 467sub confirmExists 468{ 469 my $self = shift; 470 471 # Confirm the file exists and the DB section is filled out 472 if (!$self->test(INFO_ARCHIVE_SECTION_DB) || !$self->{bExists}) 473 { 474 confess &log(ERROR, $strArchiveInfoMissingMsg, ERROR_FILE_MISSING); 475 } 476} 477 4781; 479