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