1#################################################################################################################################### 2# FullCommonTest.pm - Common code for backup tests 3#################################################################################################################################### 4package pgBackRestTest::Env::HostEnvTest; 5use parent 'pgBackRestTest::Common::RunTest'; 6 7#################################################################################################################################### 8# Perl includes 9#################################################################################################################################### 10use strict; 11use warnings FATAL => qw(all); 12use Carp qw(confess); 13 14use Digest::SHA qw(sha1_hex); 15use Exporter qw(import); 16 our @EXPORT = qw(); 17use Storable qw(dclone); 18 19use pgBackRestDoc::Common::Log; 20 21use pgBackRestTest::Common::ContainerTest; 22use pgBackRestTest::Common::DbVersion; 23use pgBackRestTest::Common::ExecuteTest; 24use pgBackRestTest::Common::HostGroupTest; 25use pgBackRestTest::Common::RunTest; 26use pgBackRestTest::Common::StorageBase; 27use pgBackRestTest::Common::StorageRepo; 28use pgBackRestTest::Env::ArchiveInfo; 29use pgBackRestTest::Env::Host::HostAzureTest; 30use pgBackRestTest::Env::Host::HostBackupTest; 31use pgBackRestTest::Env::Host::HostBaseTest; 32use pgBackRestTest::Env::Host::HostDbCommonTest; 33use pgBackRestTest::Env::Host::HostDbTest; 34use pgBackRestTest::Env::Host::HostDbSyntheticTest; 35use pgBackRestTest::Env::Host::HostGcsTest; 36use pgBackRestTest::Env::Host::HostS3Test; 37 38#################################################################################################################################### 39# Constants 40#################################################################################################################################### 41use constant ENCRYPTION_KEY_ARCHIVE => 'archive'; 42 push @EXPORT, qw(ENCRYPTION_KEY_ARCHIVE); 43use constant ENCRYPTION_KEY_MANIFEST => 'manifest'; 44 push @EXPORT, qw(ENCRYPTION_KEY_MANIFEST); 45use constant ENCRYPTION_KEY_BACKUPSET => 'backupset'; 46 push @EXPORT, qw(ENCRYPTION_KEY_BACKUPSET); 47 48#################################################################################################################################### 49# setup 50#################################################################################################################################### 51sub setup 52{ 53 my $self = shift; 54 my $bSynthetic = shift; 55 my $oLogTest = shift; 56 my $oConfigParam = shift; 57 58 # Start object server first since it takes the longest 59 #------------------------------------------------------------------------------------------------------------------------------- 60 my $oHostObject; 61 62 if ($oConfigParam->{strStorage} eq S3) 63 { 64 $oHostObject = new pgBackRestTest::Env::Host::HostS3Test(); 65 } 66 elsif ($oConfigParam->{strStorage} eq AZURE) 67 { 68 $oHostObject = new pgBackRestTest::Env::Host::HostAzureTest(); 69 } 70 elsif ($oConfigParam->{strStorage} eq GCS) 71 { 72 $oHostObject = new pgBackRestTest::Env::Host::HostGcsTest(); 73 } 74 75 # Get host group 76 my $oHostGroup = hostGroupGet(); 77 78 # Create the backup host 79 my $strBackupDestination; 80 my $bHostBackup = defined($$oConfigParam{bHostBackup}) ? $$oConfigParam{bHostBackup} : false; 81 my $oHostBackup = undef; 82 83 my $bRepoEncrypt = defined($$oConfigParam{bRepoEncrypt}) ? $$oConfigParam{bRepoEncrypt} : false; 84 85 if ($bHostBackup) 86 { 87 $strBackupDestination = defined($$oConfigParam{strBackupDestination}) ? $$oConfigParam{strBackupDestination} : HOST_BACKUP; 88 89 $oHostBackup = new pgBackRestTest::Env::Host::HostBackupTest( 90 {strBackupDestination => $strBackupDestination, bSynthetic => $bSynthetic, oLogTest => $oLogTest, 91 bRepoLocal => $oConfigParam->{strStorage} eq POSIX, bRepoEncrypt => $bRepoEncrypt}); 92 $oHostGroup->hostAdd($oHostBackup); 93 } 94 else 95 { 96 $strBackupDestination = 97 defined($$oConfigParam{strBackupDestination}) ? $$oConfigParam{strBackupDestination} : HOST_DB_PRIMARY; 98 } 99 100 # Create the db-primary host 101 my $oHostDbPrimary = undef; 102 103 if ($bSynthetic) 104 { 105 $oHostDbPrimary = new pgBackRestTest::Env::Host::HostDbSyntheticTest( 106 {strBackupDestination => $strBackupDestination, oLogTest => $oLogTest, 107 bRepoLocal => $oConfigParam->{strStorage} eq POSIX, bRepoEncrypt => $bRepoEncrypt}); 108 } 109 else 110 { 111 $oHostDbPrimary = new pgBackRestTest::Env::Host::HostDbTest( 112 {strBackupDestination => $strBackupDestination, oLogTest => $oLogTest, bRepoLocal => 113 $oConfigParam->{strStorage} eq POSIX, bRepoEncrypt => $bRepoEncrypt}); 114 } 115 116 $oHostGroup->hostAdd($oHostDbPrimary); 117 118 # Create the db-standby host 119 my $oHostDbStandby = undef; 120 121 if (defined($$oConfigParam{bStandby}) && $$oConfigParam{bStandby}) 122 { 123 $oHostDbStandby = new pgBackRestTest::Env::Host::HostDbTest( 124 {strBackupDestination => $strBackupDestination, bStandby => true, oLogTest => $oLogTest, 125 bRepoLocal => $oConfigParam->{strStorage} eq POSIX}); 126 127 $oHostGroup->hostAdd($oHostDbStandby); 128 } 129 130 # Finalize object server 131 #------------------------------------------------------------------------------------------------------------------------------- 132 if ($oConfigParam->{strStorage} eq S3) 133 { 134 $oHostGroup->hostAdd($oHostObject, {rstryHostName => ['pgbackrest-dev.s3.amazonaws.com', 's3.amazonaws.com']}); 135 } 136 elsif ($oConfigParam->{strStorage} eq AZURE || $oConfigParam->{strStorage} eq GCS) 137 { 138 $oHostGroup->hostAdd($oHostObject); 139 } 140 141 # Create db-primary config 142 $oHostDbPrimary->configCreate({ 143 strBackupSource => $$oConfigParam{strBackupSource}, 144 strCompressType => $$oConfigParam{strCompressType}, 145 bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink}, 146 bArchiveAsync => $$oConfigParam{bArchiveAsync}, 147 strStorage => $oConfigParam->{strStorage}, 148 iRepoTotal => $oConfigParam->{iRepoTotal}}); 149 150 # Create backup config if backup host exists 151 if (defined($oHostBackup)) 152 { 153 $oHostBackup->configCreate({ 154 strCompressType => $$oConfigParam{strCompressType}, 155 bHardlink => $$oConfigParam{bHardLink}, 156 strStorage => $oConfigParam->{strStorage}, 157 iRepoTotal => $oConfigParam->{iRepoTotal}}); 158 } 159 # If backup host is not defined set it to db-primary 160 else 161 { 162 $oHostBackup = $strBackupDestination eq HOST_DB_PRIMARY ? $oHostDbPrimary : $oHostDbStandby; 163 } 164 165 storageRepoCommandSet( 166 $self->backrestExeHelper() . 167 ' --config=' . $oHostBackup->backrestConfig() . ' --stanza=' . $self->stanza() . ' --log-level-console=off' . 168 ' --log-level-stderr=error' . 169 ($oConfigParam->{strStorage} ne POSIX ? 170 " --no-repo1-storage-verify-tls --repo1-$oConfigParam->{strStorage}-" . 171 ($oConfigParam->{strStorage} eq GCS ? 'endpoint' : 'host') . "=" . $oHostObject->ipGet() : '') . 172 ($oConfigParam->{strStorage} eq GCS ? ':' . HOST_GCS_PORT : ''), 173 $oConfigParam->{strStorage} eq POSIX ? STORAGE_POSIX : STORAGE_OBJECT); 174 175 # Create db-standby config 176 if (defined($oHostDbStandby)) 177 { 178 $oHostDbStandby->configCreate({ 179 strBackupSource => $$oConfigParam{strBackupSource}, 180 strCompressType => $$oConfigParam{strCompressType}, 181 bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink}, 182 bArchiveAsync => $$oConfigParam{bArchiveAsync}, 183 strStorage => $oConfigParam->{strStorage}, 184 iRepoTotal => $oConfigParam->{iRepoTotal}}); 185 } 186 187 # Create object storage 188 if (defined($oHostObject)) 189 { 190 storageRepo()->create(); 191 } 192 193 return $oHostDbPrimary, $oHostDbStandby, $oHostBackup; 194} 195 196#################################################################################################################################### 197# Generate database system id for the db version 198#################################################################################################################################### 199sub dbSysId 200{ 201 my $self = shift; 202 203 # Assign function parameters, defaults, and log debug info 204 my 205 ( 206 $strOperation, 207 $strPgVersion, 208 ) = 209 logDebugParam 210 ( 211 __PACKAGE__ . '->dbSysId', \@_, 212 {name => 'strPgVersion', trace => true}, 213 ); 214 215 return (1000000000000000000 + ($strPgVersion * 10)); 216} 217 218#################################################################################################################################### 219# Get database catalog version for the db version 220#################################################################################################################################### 221sub dbCatalogVersion 222{ 223 my $self = shift; 224 225 # Assign function parameters, defaults, and log debug info 226 my 227 ( 228 $strOperation, 229 $strPgVersion, 230 ) = 231 logDebugParam 232 ( 233 __PACKAGE__ . '->sysId', \@_, 234 {name => 'strPgVersion', trace => true}, 235 ); 236 237 my $hCatalogVersion = 238 { 239 &PG_VERSION_83 => 200711281, 240 &PG_VERSION_84 => 200904091, 241 &PG_VERSION_90 => 201008051, 242 &PG_VERSION_91 => 201105231, 243 &PG_VERSION_92 => 201204301, 244 &PG_VERSION_93 => 201306121, 245 &PG_VERSION_94 => 201409291, 246 &PG_VERSION_95 => 201510051, 247 &PG_VERSION_96 => 201608131, 248 &PG_VERSION_10 => 201707211, 249 &PG_VERSION_11 => 201806231, 250 &PG_VERSION_12 => 201909212, 251 &PG_VERSION_13 => 202007201, 252 &PG_VERSION_14 => 202105121, 253 }; 254 255 if (!defined($hCatalogVersion->{$strPgVersion})) 256 { 257 confess &log(ASSERT, "no catalog version defined for pg version ${strPgVersion}"); 258 } 259 260 return $hCatalogVersion->{$strPgVersion}; 261} 262 263#################################################################################################################################### 264# Get database control version for the db version 265#################################################################################################################################### 266sub dbControlVersion 267{ 268 my $self = shift; 269 270 # Assign function parameters, defaults, and log debug info 271 my 272 ( 273 $strOperation, 274 $strPgVersion, 275 ) = 276 logDebugParam 277 ( 278 __PACKAGE__ . '->dbControlVersion', \@_, 279 {name => 'strPgVersion', trace => true}, 280 ); 281 282 my $hControlVersion = 283 { 284 &PG_VERSION_83 => 833, 285 &PG_VERSION_84 => 843, 286 &PG_VERSION_90 => 903, 287 &PG_VERSION_91 => 903, 288 &PG_VERSION_92 => 922, 289 &PG_VERSION_93 => 937, 290 &PG_VERSION_94 => 942, 291 &PG_VERSION_95 => 942, 292 &PG_VERSION_96 => 960, 293 &PG_VERSION_10 => 1002, 294 &PG_VERSION_11 => 1100, 295 &PG_VERSION_12 => 1201, 296 &PG_VERSION_13 => 1300, 297 &PG_VERSION_14 => 1300, 298 }; 299 300 if (!defined($hControlVersion->{$strPgVersion})) 301 { 302 confess &log(ASSERT, "no control version defined for pg version ${strPgVersion}"); 303 } 304 305 return $hControlVersion->{$strPgVersion}; 306} 307 308#################################################################################################################################### 309# Generate control file content 310#################################################################################################################################### 311sub controlGenerateContent 312{ 313 my $self = shift; 314 315 # Assign function parameters, defaults, and log debug info 316 my 317 ( 318 $strOperation, 319 $strPgVersion, 320 ) = 321 logDebugParam 322 ( 323 __PACKAGE__ . '->controlGenerateContent', \@_, 324 {name => 'strPgVersion', trace => true}, 325 ); 326 327 my $tControlContent = pack('Q', $self->dbSysId($strPgVersion)); 328 $tControlContent .= pack('L', $self->dbControlVersion($strPgVersion)); 329 $tControlContent .= pack('L', $self->dbCatalogVersion($strPgVersion)); 330 331 # Offset to page size by architecture bits and version 332 my $rhOffsetToPageSize = 333 { 334 32 => 335 { 336 '8.3' => 96 - length($tControlContent), 337 '8.4' => 104 - length($tControlContent), 338 '9.0' => 140 - length($tControlContent), 339 '9.1' => 140 - length($tControlContent), 340 '9.2' => 156 - length($tControlContent), 341 '9.3' => 180 - length($tControlContent), 342 '9.4' => 188 - length($tControlContent), 343 '9.5' => 200 - length($tControlContent), 344 '9.6' => 200 - length($tControlContent), 345 '10' => 200 - length($tControlContent), 346 '11' => 192 - length($tControlContent), 347 '12' => 196 - length($tControlContent), 348 '13' => 196 - length($tControlContent), 349 }, 350 351 64 => 352 { 353 '8.3' => 112 - length($tControlContent), 354 '8.4' => 112 - length($tControlContent), 355 '9.0' => 152 - length($tControlContent), 356 '9.1' => 152 - length($tControlContent), 357 '9.2' => 168 - length($tControlContent), 358 '9.3' => 192 - length($tControlContent), 359 '9.4' => 200 - length($tControlContent), 360 '9.5' => 216 - length($tControlContent), 361 '9.6' => 216 - length($tControlContent), 362 '10' => 216 - length($tControlContent), 363 '11' => 208 - length($tControlContent), 364 '12' => 212 - length($tControlContent), 365 '13' => 212 - length($tControlContent), 366 }, 367 }; 368 369 # Fill up to page size and set page size 370 $tControlContent .= ('C' x $rhOffsetToPageSize->{$self->archBits()}{$strPgVersion}); 371 $tControlContent .= pack('L', 8192); 372 373 # Fill up to wal segment size and set wal segment size 374 $tControlContent .= ('C' x 8); 375 $tControlContent .= pack('L', 16 * 1024 * 1024); 376 377 # Pad bytes 378 $tControlContent .= ('C' x (8192 - length($tControlContent))); 379 380 return \$tControlContent; 381} 382 383#################################################################################################################################### 384# Generate control file and write to disk 385#################################################################################################################################### 386sub controlGenerate 387{ 388 my $self = shift; 389 390 # Assign function parameters, defaults, and log debug info 391 my 392 ( 393 $strOperation, 394 $strDbPath, 395 $strPgVersion, 396 ) = 397 logDebugParam 398 ( 399 __PACKAGE__ . '->controlGenerate', \@_, 400 {name => 'strDbPath', trace => true}, 401 {name => 'strPgVersion', trace => true}, 402 ); 403 404 storageTest()->put("${strDbPath}/global/pg_control", $self->controlGenerateContent($strPgVersion)); 405} 406 407#################################################################################################################################### 408# walSegment 409# 410# Generate name of WAL segment from component parts. 411#################################################################################################################################### 412sub walSegment 413{ 414 my $self = shift; 415 my $iTimeline = shift; 416 my $iMajor = shift; 417 my $iMinor = shift; 418 419 return uc(sprintf('%08x%08x%08x', $iTimeline, $iMajor, $iMinor)); 420} 421 422#################################################################################################################################### 423# Generate WAL file content 424#################################################################################################################################### 425sub walGenerateContent 426{ 427 my $self = shift; 428 429 # Assign function parameters, defaults, and log debug info 430 my 431 ( 432 $strOperation, 433 $strPgVersion, 434 $iSourceNo, 435 ) = 436 logDebugParam 437 ( 438 __PACKAGE__ . '->walGenerateContent', \@_, 439 {name => 'strPgVersion', trace => true}, 440 {name => 'iSourceNo', optional => true, default => 1, trace => true}, 441 ); 442 443 # Get WAL magic for the PG version 444 my $hWalMagic = 445 { 446 &PG_VERSION_83 => hex('0xD062'), 447 &PG_VERSION_84 => hex('0xD063'), 448 &PG_VERSION_90 => hex('0xD064'), 449 &PG_VERSION_91 => hex('0xD066'), 450 &PG_VERSION_92 => hex('0xD071'), 451 &PG_VERSION_93 => hex('0xD075'), 452 &PG_VERSION_94 => hex('0xD07E'), 453 &PG_VERSION_95 => hex('0xD087'), 454 &PG_VERSION_96 => hex('0xD093'), 455 &PG_VERSION_10 => hex('0xD097'), 456 &PG_VERSION_11 => hex('0xD098'), 457 &PG_VERSION_12 => hex('0xD101'), 458 &PG_VERSION_13 => hex('0xD106'), 459 }; 460 461 my $tWalContent = pack('S', $hWalMagic->{$strPgVersion}); 462 463 # Indicate that the long header is present 464 $tWalContent .= pack('S', 2); 465 466 # Add junk (H for header) for the bytes that won't be read by the tests 467 my $iOffset = 12 + ($strPgVersion >= PG_VERSION_93 ? testRunGet()->archBits() / 8 : 0); 468 $tWalContent .= ('H' x $iOffset); 469 470 # Add the system identifier 471 $tWalContent .= pack('Q', $self->dbSysId($strPgVersion)); 472 473 # Add segment size 474 $tWalContent .= pack('L', PG_WAL_SEGMENT_SIZE); 475 476 # Add the source number to produce WAL segments with different checksums 477 $tWalContent .= pack('S', $iSourceNo); 478 479 # Pad out to the required size (B for body) 480 $tWalContent .= ('B' x (PG_WAL_SEGMENT_SIZE - length($tWalContent))); 481 482 return \$tWalContent; 483} 484 485#################################################################################################################################### 486# Generate WAL file content checksum 487#################################################################################################################################### 488sub walGenerateContentChecksum 489{ 490 my $self = shift; 491 492 # Assign function parameters, defaults, and log debug info 493 my 494 ( 495 $strOperation, 496 $strPgVersion, 497 $hParam, 498 ) = 499 logDebugParam 500 ( 501 __PACKAGE__ . '->walGenerateContent', \@_, 502 {name => 'strPgVersion', trace => true}, 503 {name => 'hParam', required => false, trace => true}, 504 ); 505 506 return sha1_hex(${$self->walGenerateContent($strPgVersion, $hParam)}); 507} 508 509#################################################################################################################################### 510# walGenerate 511# 512# Generate a WAL segment and ready file for testing. 513#################################################################################################################################### 514sub walGenerate 515{ 516 my $self = shift; 517 my $strWalPath = shift; 518 my $strPgVersion = shift; 519 my $iSourceNo = shift; 520 my $strWalSegment = shift; 521 my $bPartial = shift; 522 my $bChecksum = shift; 523 my $bReady = shift; 524 525 my $rtWalContent = $self->walGenerateContent($strPgVersion, {iSourceNo => $iSourceNo}); 526 my $strWalFile = 527 "${strWalPath}/${strWalSegment}" . ($bChecksum ? '-' . sha1_hex($rtWalContent) : '') . 528 (defined($bPartial) && $bPartial ? '.partial' : ''); 529 530 # Put the WAL segment and the ready file 531 storageTest()->put($strWalFile, $rtWalContent); 532 533 if (!defined($bReady) || $bReady) 534 { 535 storageTest()->put("${strWalPath}/archive_status/${strWalSegment}.ready"); 536 } 537 538 return $strWalFile; 539} 540 541#################################################################################################################################### 542# walRemove 543# 544# Remove WAL file and ready file. 545#################################################################################################################################### 546sub walRemove 547{ 548 my $self = shift; 549 my $strWalPath = shift; 550 my $strWalFile = shift; 551 552 storageTest()->remove("$self->{strWalPath}/${strWalFile}"); 553 storageTest()->remove("$self->{strWalPath}/archive_status/${strWalFile}.ready"); 554} 555 5561; 557