Skip to content

Commit 9d3c513

Browse files
mnenciaarmru
andauthored
fix(upgrades): consider previous controldata when creating the new datadir (cloudnative-pg#7274)
Closes cloudnative-pg#7273 cloudnative-pg#7276 --------- Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com> Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com> Co-authored-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
1 parent 852fdf6 commit 9d3c513

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-8
lines changed

internal/cmd/manager/instance/upgrade/execute/cmd.go

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"os/exec"
2828
"path"
2929
"path/filepath"
30+
"strconv"
3031
"strings"
3132
"time"
3233

@@ -36,6 +37,7 @@ import (
3637
"github.com/cloudnative-pg/machinery/pkg/fileutils"
3738
"github.com/cloudnative-pg/machinery/pkg/fileutils/compatibility"
3839
"github.com/cloudnative-pg/machinery/pkg/log"
40+
"github.com/cloudnative-pg/machinery/pkg/postgres/version"
3941
"github.com/spf13/cobra"
4042
"k8s.io/utils/ptr"
4143
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
@@ -48,9 +50,10 @@ import (
4850
"github.com/cloudnative-pg/cloudnative-pg/pkg/management"
4951
"github.com/cloudnative-pg/cloudnative-pg/pkg/management/postgres"
5052
"github.com/cloudnative-pg/cloudnative-pg/pkg/management/postgres/constants"
51-
"github.com/cloudnative-pg/cloudnative-pg/pkg/management/postgres/utils"
53+
postgresutils "github.com/cloudnative-pg/cloudnative-pg/pkg/management/postgres/utils"
5254
"github.com/cloudnative-pg/cloudnative-pg/pkg/management/postgres/webserver/metricserver"
5355
"github.com/cloudnative-pg/cloudnative-pg/pkg/specs"
56+
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
5457
)
5558

5659
// NewCmd creates the cobra command
@@ -187,8 +190,19 @@ func upgradeSubCommand(
187190
}
188191
}
189192

193+
// Extract controldata information from the old data directory
194+
controlData, err := getControlData(oldBinDir, pgData)
195+
if err != nil {
196+
return fmt.Errorf("error while getting old data directory control data: %w", err)
197+
}
198+
199+
targetVersion, err := cluster.GetPostgresqlVersion()
200+
if err != nil {
201+
return fmt.Errorf("error while getting the target version from the cluster object: %w", err)
202+
}
203+
190204
contextLogger.Info("Creating data directory", "directory", newDataDir)
191-
if err := runInitDB(newDataDir, newWalDir); err != nil {
205+
if err := runInitDB(newDataDir, newWalDir, controlData, targetVersion); err != nil {
192206
return fmt.Errorf("error while creating the data directory: %w", err)
193207
}
194208

@@ -199,12 +213,12 @@ func upgradeSubCommand(
199213

200214
contextLogger.Info("Checking if we have anything to update")
201215
// Read pg_version from both the old and new data directories
202-
oldVersion, err := utils.GetPgdataVersion(pgData)
216+
oldVersion, err := postgresutils.GetPgdataVersion(pgData)
203217
if err != nil {
204218
return fmt.Errorf("error while reading the old version: %w", err)
205219
}
206220

207-
newVersion, err := utils.GetPgdataVersion(newDataDir)
221+
newVersion, err := postgresutils.GetPgdataVersion(newDataDir)
208222
if err != nil {
209223
return fmt.Errorf("error while reading the new version: %w", err)
210224
}
@@ -262,7 +276,19 @@ func upgradeSubCommand(
262276
return nil
263277
}
264278

265-
func runInitDB(destDir string, walDir *string) error {
279+
func getControlData(binDir, pgData string) (map[string]string, error) {
280+
pgControlDataCmd := exec.Command(path.Join(binDir, "pg_controldata")) // #nosec
281+
pgControlDataCmd.Env = append(os.Environ(), "PGDATA="+pgData)
282+
283+
out, err := pgControlDataCmd.CombinedOutput()
284+
if err != nil {
285+
return nil, fmt.Errorf("while executing pg_controldata: %w", err)
286+
}
287+
288+
return utils.ParsePgControldataOutput(string(out)), nil
289+
}
290+
291+
func runInitDB(destDir string, walDir *string, pgControlData map[string]string, targetVersion version.Data) error {
266292
// Invoke initdb to generate a data directory
267293
options := []string{
268294
"--username",
@@ -275,6 +301,17 @@ func runInitDB(destDir string, walDir *string) error {
275301
options = append(options, "--waldir", *walDir)
276302
}
277303

304+
// Extract the WAL segment size from the pg_controldata output
305+
options, err := tryAddWalSegmentSize(pgControlData, options)
306+
if err != nil {
307+
return err
308+
}
309+
310+
options, err = tryAddDataChecksums(pgControlData, targetVersion, options)
311+
if err != nil {
312+
return err
313+
}
314+
278315
// Certain CSI drivers may add setgid permissions on newly created folders.
279316
// A default umask is set to attempt to avoid this, by revoking group/other
280317
// permission bits on the PGDATA
@@ -288,6 +325,46 @@ func runInitDB(destDir string, walDir *string) error {
288325
return nil
289326
}
290327

328+
// TODO: refactor it should be a method of pgControlData
329+
func tryAddDataChecksums(
330+
pgControlData map[string]string,
331+
targetVersion version.Data,
332+
options []string,
333+
) ([]string, error) {
334+
dataPageChecksumVersion, ok := pgControlData[utils.PgControlDataDataPageChecksumVersion]
335+
if !ok {
336+
return nil, fmt.Errorf("no '%s' section into pg_controldata output", utils.PgControlDataDataPageChecksumVersion)
337+
}
338+
339+
if dataPageChecksumVersion != "1" {
340+
// In postgres 18 we will have to set "--no-data-checksums" if checksums are disabled (they are enabled by default)
341+
if targetVersion.Major() >= 18 {
342+
return append(options, "--no-data-checksums"), nil
343+
}
344+
return options, nil
345+
}
346+
347+
return append(options, "--data-checksums"), nil
348+
}
349+
350+
// TODO: refactor it should be a method of pgControlData
351+
func tryAddWalSegmentSize(pgControlData map[string]string, options []string) ([]string, error) {
352+
walSegmentSizeString, ok := pgControlData[utils.PgControlDataBytesPerWALSegment]
353+
if !ok {
354+
return nil, fmt.Errorf("no '%s' section into pg_controldata output", utils.PgControlDataBytesPerWALSegment)
355+
}
356+
357+
walSegmentSize, err := strconv.Atoi(walSegmentSizeString)
358+
if err != nil {
359+
return nil, fmt.Errorf(
360+
"wrong '%s' pg_controldata value (not an integer): '%s' %w",
361+
utils.PgControlDataBytesPerWALSegment, walSegmentSizeString, err)
362+
}
363+
364+
param := "--wal-segsize=" + strconv.Itoa(walSegmentSize/(1024*1024))
365+
return append(options, param), nil
366+
}
367+
291368
func prepareConfigurationFiles(ctx context.Context, cluster apiv1.Cluster, destDir string) error {
292369
// Always read the custom and override configuration files created by the operator
293370
_, err := configfile.EnsureIncludes(path.Join(destDir, "postgresql.conf"),
@@ -298,8 +375,20 @@ func prepareConfigurationFiles(ctx context.Context, cluster apiv1.Cluster, destD
298375
return fmt.Errorf("appending inclusion directives to postgresql.conf file resulted in an error: %w", err)
299376
}
300377

378+
// Set `max_slot_wal_keep_size` to the default value because any other value it is not supported in pg_upgrade
379+
tmpCluster := cluster.DeepCopy()
380+
tmpCluster.Spec.PostgresConfiguration.Parameters["max_slot_wal_keep_size"] = "-1"
381+
382+
pgVersion, err := postgresutils.GetPgdataVersion(destDir)
383+
if err != nil {
384+
return fmt.Errorf("error while reading the new data directory version: %w", err)
385+
}
386+
if pgVersion.Major >= 18 {
387+
tmpCluster.Spec.PostgresConfiguration.Parameters["idle_replication_slot_timeout"] = "0"
388+
}
389+
301390
newInstance := postgres.Instance{PgData: destDir}
302-
if _, err := newInstance.RefreshConfigurationFilesFromCluster(ctx, &cluster, false); err != nil {
391+
if _, err := newInstance.RefreshConfigurationFilesFromCluster(ctx, tmpCluster, false); err != nil {
303392
return fmt.Errorf("error while creating the configuration files for new datadir %q: %w", destDir, err)
304393
}
305394

pkg/management/postgres/instance.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ func (instance *Instance) CheckHasDiskSpaceForWAL(ctx context.Context) (bool, er
271271
}
272272

273273
pgControlData := utils.ParsePgControldataOutput(pgControlDataString)
274-
walSegmentSizeString, ok := pgControlData["Bytes per WAL segment"]
274+
walSegmentSizeString, ok := pgControlData[utils.PgControlDataBytesPerWALSegment]
275275
if !ok {
276276
return false, fmt.Errorf("no 'Bytes per WAL segment' section into pg_controldata output")
277277
}

pkg/utils/parser.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ const (
5757
// PgControlDataDatabaseClusterStateKey is the status
5858
// of the latest primary that run on this data directory.
5959
PgControlDataDatabaseClusterStateKey pgControlDataKey = "Database cluster state"
60+
61+
// PgControlDataDataPageChecksumVersion reports whether the checksums are enabled in the cluster
62+
PgControlDataDataPageChecksumVersion pgControlDataKey = "Data page checksum version"
63+
64+
// PgControlDataBytesPerWALSegment reports the size of the WAL segments
65+
PgControlDataBytesPerWALSegment pgControlDataKey = "Bytes per WAL segment"
6066
)
6167

6268
// PgDataState represents the "Database cluster state" field of pg_controldata
@@ -82,7 +88,7 @@ func (state PgDataState) IsShutdown(ctx context.Context) bool {
8288
}
8389

8490
// ParsePgControldataOutput parses a pg_controldata output into a map of key-value pairs
85-
func ParsePgControldataOutput(data string) map[string]string {
91+
func ParsePgControldataOutput(data string) map[pgControlDataKey]string {
8692
pairs := make(map[string]string)
8793
lines := strings.Split(data, "\n")
8894
for _, line := range lines {

tests/e2e/cluster_major_upgrade_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3333
"k8s.io/apimachinery/pkg/types"
3434
"k8s.io/apimachinery/pkg/util/rand"
35+
"k8s.io/utils/ptr"
3536
"sigs.k8s.io/controller-runtime/pkg/client"
3637

3738
v1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
@@ -78,6 +79,12 @@ var _ = Describe("Postgres Major Upgrade", Label(tests.LabelPostgresMajorUpgrade
7879
},
7980
Spec: v1.ClusterSpec{
8081
Instances: 3,
82+
Bootstrap: &v1.BootstrapConfiguration{
83+
InitDB: &v1.BootstrapInitDB{
84+
DataChecksums: ptr.To(true),
85+
WalSegmentSize: 32,
86+
},
87+
},
8188
StorageConfiguration: v1.StorageConfiguration{
8289
StorageClass: &storageClass,
8390
Size: "1Gi",
@@ -95,6 +102,7 @@ var _ = Describe("Postgres Major Upgrade", Label(tests.LabelPostgresMajorUpgrade
95102
"log_temp_files": "1024",
96103
"log_autovacuum_min_duration": "1000",
97104
"log_replication_commands": "on",
105+
"max_slot_wal_keep_size": "1GB",
98106
},
99107
},
100108
},

0 commit comments

Comments
 (0)