diff --git a/.agola/config.jsonnet b/.agola/config.jsonnet index 8ec614639..76a3c1e90 100644 --- a/.agola/config.jsonnet +++ b/.agola/config.jsonnet @@ -13,7 +13,7 @@ local ci_runtime(pgversion, arch) = { arch: arch, containers: [ { - image: 'sorintlab/stolon-ci-image:v0.1.0-pg' + pgversion, + image: 'sorintlab/stolon-ci-image:v0.2.0-pg' + pgversion, volumes: [ { path: '/stolontemp', @@ -172,15 +172,15 @@ local task_build_push_images(name, pgversions, istag, push) = task_integration_tests(store, pgversion, 'amd64'), ] for store in ['etcdv2', 'consul'] - for pgversion in ['11' /*, '12' */] + for pgversion in ['12'] ]) + std.flattenArrays([ [ task_integration_tests(store, pgversion, 'amd64'), ] for store in ['etcdv3'] - for pgversion in ['9.5', '9.6', '10', '11' /*, '12' */] + for pgversion in ['9.5', '9.6', '10', '11', '12'] ]) + [ - task_build_push_images('test build docker "stolon" images', '9.4 9.5 9.6 10 11', false, false) + task_build_push_images('test build docker "stolon" images', '9.4 9.5 9.6 10 11 12', false, false) + { when: { branch: { @@ -190,13 +190,13 @@ local task_build_push_images(name, pgversions, istag, push) = ref: '#refs/pull/\\d+/head#', }, }, - task_build_push_images('build and push docker "stolon" master branch images', '9.4 9.5 9.6 10 11', false, true) + task_build_push_images('build and push docker "stolon" master branch images', '9.4 9.5 9.6 10 11 12', false, true) + { when: { branch: 'master', }, }, - task_build_push_images('build and push docker "stolon" tag images', '9.4 9.5 9.6 10 11', true, true) + task_build_push_images('build and push docker "stolon" tag images', '9.4 9.5 9.6 10 11 12', true, true) + { when: { tag: '#v.*#', diff --git a/cmd/keeper/cmd/keeper.go b/cmd/keeper/cmd/keeper.go index 2777c19d5..34253279c 100644 --- a/cmd/keeper/cmd/keeper.go +++ b/cmd/keeper/cmd/keeper.go @@ -161,6 +161,20 @@ var managedPGParameters = []string{ "max_wal_senders", "wal_log_hints", "synchronous_standby_names", + + // parameters moved from recovery.conf to postgresql.conf in PostgresSQL 12 + "primary_conninfo", + "primary_slot_name", + "recovery_min_apply_delay", + "restore_command", + "recovery_target_timeline", + "recovery_target", + "recovery_target_lsn", + "recovery_target_name", + "recovery_target_time", + "recovery_target_xid", + "recovery_target_timeline", + "recovery_target_action", } func readPasswordFromFile(filepath string) (string, error) { @@ -370,13 +384,9 @@ func (p *PostgresKeeper) createPGParameters(db *cluster.DB) common.Parameters { return parameters } -func (p *PostgresKeeper) createRecoveryParameters(standbyMode bool, standbySettings *cluster.StandbySettings, archiveRecoverySettings *cluster.ArchiveRecoverySettings, recoveryTargetSettings *cluster.RecoveryTargetSettings) common.Parameters { +func (p *PostgresKeeper) createRecoveryOptions(recoveryMode postgresql.RecoveryMode, standbySettings *cluster.StandbySettings, archiveRecoverySettings *cluster.ArchiveRecoverySettings, recoveryTargetSettings *cluster.RecoveryTargetSettings) *postgresql.RecoveryOptions { parameters := common.Parameters{} - if standbyMode { - parameters["standby_mode"] = "on" - } - if standbySettings != nil { if standbySettings.PrimaryConninfo != "" { parameters["primary_conninfo"] = standbySettings.PrimaryConninfo @@ -417,7 +427,10 @@ func (p *PostgresKeeper) createRecoveryParameters(standbyMode bool, standbySetti parameters["recovery_target_action"] = "promote" } - return parameters + return &postgresql.RecoveryOptions{ + RecoveryMode: recoveryMode, + RecoveryParameters: parameters, + } } type PostgresKeeper struct { @@ -847,7 +860,7 @@ func (p *PostgresKeeper) resync(db, masterDB, followedDB *cluster.DB, tryPgrewin // log pg_rewind error and fallback to pg_basebackup log.Errorw("error syncing with pg_rewind", zap.Error(err)) } else { - pgm.SetRecoveryParameters(p.createRecoveryParameters(true, standbySettings, nil, nil)) + pgm.SetRecoveryOptions(p.createRecoveryOptions(postgresql.RecoveryModeStandby, standbySettings, nil, nil)) return nil } } @@ -876,7 +889,7 @@ func (p *PostgresKeeper) resync(db, masterDB, followedDB *cluster.DB, tryPgrewin } log.Infow("sync succeeded") - pgm.SetRecoveryParameters(p.createRecoveryParameters(true, standbySettings, nil, nil)) + pgm.SetRecoveryOptions(p.createRecoveryOptions(postgresql.RecoveryModeStandby, standbySettings, nil, nil)) return nil } @@ -1086,7 +1099,7 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { } log.Infow("current db UID different than cluster data db UID", "db", p.dbLocalState.UID, "cdDB", db.UID) - pgm.SetRecoveryParameters(nil) + pgm.SetRecoveryOptions(nil) p.waitSyncStandbysSynced = false switch db.Spec.InitMode { @@ -1192,27 +1205,28 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { return } - standbyMode := false + recoveryMode := postgresql.RecoveryModeRecovery var standbySettings *cluster.StandbySettings if db.Spec.FollowConfig != nil && db.Spec.FollowConfig.Type == cluster.FollowTypeExternal { - standbyMode = true + recoveryMode = postgresql.RecoveryModeStandby standbySettings = db.Spec.FollowConfig.StandbySettings } - // if we are initializing a standby cluster then enable standby_mode to not stop recovery - pgm.SetRecoveryParameters(p.createRecoveryParameters(standbyMode, standbySettings, db.Spec.PITRConfig.ArchiveRecoverySettings, db.Spec.PITRConfig.RecoveryTargetSettings)) + pgm.SetRecoveryOptions(p.createRecoveryOptions(recoveryMode, standbySettings, db.Spec.PITRConfig.ArchiveRecoverySettings, db.Spec.PITRConfig.RecoveryTargetSettings)) if err = pgm.StartTmpMerged(); err != nil { log.Errorw("failed to start instance", zap.Error(err)) return } - if !standbyMode { + if recoveryMode == postgresql.RecoveryModeRecovery { // wait for the db having replyed all the wals + log.Infof("waiting for recovery to be completed") if err = pgm.WaitRecoveryDone(cd.Cluster.DefSpec().SyncTimeout.Duration); err != nil { log.Errorw("recovery not finished", zap.Error(err)) return } + log.Infof("recovery completed") } if err = pgm.WaitReady(cd.Cluster.DefSpec().SyncTimeout.Duration); err != nil { log.Errorw("timeout waiting for instance to be ready", zap.Error(err)) @@ -1476,7 +1490,7 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { if localRole == common.RoleStandby { log.Infow("promoting to master") - pgm.SetRecoveryParameters(nil) + pgm.SetRecoveryOptions(nil) if err = pgm.Promote(); err != nil { log.Errorw("failed to promote instance", zap.Error(err)) return @@ -1522,7 +1536,7 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { return } if !started { - pgm.SetRecoveryParameters(p.createRecoveryParameters(true, standbySettings, nil, nil)) + pgm.SetRecoveryOptions(p.createRecoveryOptions(postgresql.RecoveryModeStandby, standbySettings, nil, nil)) if err = pgm.Start(); err != nil { log.Errorw("failed to start postgres", zap.Error(err)) return @@ -1543,13 +1557,13 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { standbySettings := &cluster.StandbySettings{PrimaryConninfo: newReplConnParams.ConnString(), PrimarySlotName: common.StolonName(db.UID)} - curRecoveryParameters := pgm.CurRecoveryParameters() - newRecoveryParameters := p.createRecoveryParameters(true, standbySettings, nil, nil) + curRecoveryOptions := pgm.CurRecoveryOptions() + newRecoveryOptions := p.createRecoveryOptions(postgresql.RecoveryModeStandby, standbySettings, nil, nil) // Update recovery conf if parameters has changed - if !curRecoveryParameters.Equals(newRecoveryParameters) { - log.Infow("recovery parameters changed, restarting postgres instance", "curRecoveryParameters", curRecoveryParameters, "newRecoveryParameters", newRecoveryParameters) - pgm.SetRecoveryParameters(newRecoveryParameters) + if !curRecoveryOptions.RecoveryParameters.Equals(newRecoveryOptions.RecoveryParameters) { + log.Infow("recovery parameters changed, restarting postgres instance", "curRecoveryParameters", curRecoveryOptions.RecoveryParameters, "newRecoveryParameters", newRecoveryOptions.RecoveryParameters) + pgm.SetRecoveryOptions(newRecoveryOptions) if err = pgm.Restart(true); err != nil { log.Errorw("failed to restart postgres instance", zap.Error(err)) @@ -1562,13 +1576,13 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) { } case cluster.FollowTypeExternal: - curRecoveryParameters := pgm.CurRecoveryParameters() - newRecoveryParameters := p.createRecoveryParameters(true, db.Spec.FollowConfig.StandbySettings, db.Spec.FollowConfig.ArchiveRecoverySettings, nil) + curRecoveryOptions := pgm.CurRecoveryOptions() + newRecoveryOptions := p.createRecoveryOptions(postgresql.RecoveryModeStandby, db.Spec.FollowConfig.StandbySettings, db.Spec.FollowConfig.ArchiveRecoverySettings, nil) // Update recovery conf if parameters has changed - if !curRecoveryParameters.Equals(newRecoveryParameters) { - log.Infow("recovery parameters changed, restarting postgres instance", "curRecoveryParameters", curRecoveryParameters, "newRecoveryParameters", newRecoveryParameters) - pgm.SetRecoveryParameters(newRecoveryParameters) + if !curRecoveryOptions.RecoveryParameters.Equals(newRecoveryOptions.RecoveryParameters) { + log.Infow("recovery parameters changed, restarting postgres instance", "curRecoveryParameters", curRecoveryOptions.RecoveryParameters, "newRecoveryParameters", newRecoveryOptions.RecoveryParameters) + pgm.SetRecoveryOptions(newRecoveryOptions) if err = pgm.Restart(true); err != nil { log.Errorw("failed to restart postgres instance", zap.Error(err)) diff --git a/internal/postgresql/postgresql.go b/internal/postgresql/postgresql.go index b49b49764..c92c7310f 100644 --- a/internal/postgresql/postgresql.go +++ b/internal/postgresql/postgresql.go @@ -24,6 +24,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "sort" "strconv" "strings" @@ -41,10 +42,13 @@ import ( //go:generate mockgen -destination=../mock/postgresql/postgresql.go -package=mocks -source=$GOFILE const ( - postgresConf = "postgresql.conf" - postgresRecoveryConf = "recovery.conf" - postgresAutoConf = "postgresql.auto.conf" - tmpPostgresConf = "stolon-temp-postgresql.conf" + postgresConf = "postgresql.conf" + postgresRecoveryConf = "recovery.conf" + postgresStandbySignal = "standby.signal" + postgresRecoverySignal = "recovery.signal" + postgresRecoveryDone = "recovery.done" + postgresAutoConf = "postgresql.auto.conf" + tmpPostgresConf = "stolon-temp-postgresql.conf" startTimeout = 60 * time.Second ) @@ -60,23 +64,51 @@ type PGManager interface { } type Manager struct { - pgBinPath string - dataDir string - parameters common.Parameters - recoveryParameters common.Parameters - hba []string - curParameters common.Parameters - curRecoveryParameters common.Parameters - curHba []string - localConnParams ConnParams - replConnParams ConnParams - suAuthMethod string - suUsername string - suPassword string - replAuthMethod string - replUsername string - replPassword string - requestTimeout time.Duration + pgBinPath string + dataDir string + parameters common.Parameters + recoveryOptions *RecoveryOptions + hba []string + curParameters common.Parameters + curRecoveryOptions *RecoveryOptions + curHba []string + localConnParams ConnParams + replConnParams ConnParams + suAuthMethod string + suUsername string + suPassword string + replAuthMethod string + replUsername string + replPassword string + requestTimeout time.Duration +} + +type RecoveryMode int + +const ( + RecoveryModeNone RecoveryMode = iota + RecoveryModeStandby + RecoveryModeRecovery +) + +type RecoveryOptions struct { + RecoveryMode RecoveryMode + RecoveryParameters common.Parameters +} + +func NewRecoveryOptions() *RecoveryOptions { + return &RecoveryOptions{RecoveryParameters: make(common.Parameters)} +} + +func (r *RecoveryOptions) DeepCopy() *RecoveryOptions { + nr, err := copystructure.Copy(r) + if err != nil { + panic(err) + } + if !reflect.DeepEqual(r, nr) { + panic("not equal") + } + return nr.(*RecoveryOptions) } type SystemData struct { @@ -103,21 +135,21 @@ func SetLogger(l *zap.SugaredLogger) { func NewManager(pgBinPath string, dataDir string, localConnParams, replConnParams ConnParams, suAuthMethod, suUsername, suPassword, replAuthMethod, replUsername, replPassword string, requestTimeout time.Duration) *Manager { return &Manager{ - pgBinPath: pgBinPath, - dataDir: filepath.Join(dataDir, "postgres"), - parameters: make(common.Parameters), - recoveryParameters: make(common.Parameters), - curParameters: make(common.Parameters), - curRecoveryParameters: make(common.Parameters), - replConnParams: replConnParams, - localConnParams: localConnParams, - suAuthMethod: suAuthMethod, - suUsername: suUsername, - suPassword: suPassword, - replAuthMethod: replAuthMethod, - replUsername: replUsername, - replPassword: replPassword, - requestTimeout: requestTimeout, + pgBinPath: pgBinPath, + dataDir: filepath.Join(dataDir, "postgres"), + parameters: make(common.Parameters), + recoveryOptions: NewRecoveryOptions(), + curParameters: make(common.Parameters), + curRecoveryOptions: NewRecoveryOptions(), + replConnParams: replConnParams, + localConnParams: localConnParams, + suAuthMethod: suAuthMethod, + suUsername: suUsername, + suPassword: suPassword, + replAuthMethod: replAuthMethod, + replUsername: replUsername, + replPassword: replPassword, + requestTimeout: requestTimeout, } } @@ -129,12 +161,17 @@ func (p *Manager) CurParameters() common.Parameters { return p.curParameters } -func (p *Manager) SetRecoveryParameters(recoveryParameters common.Parameters) { - p.recoveryParameters = recoveryParameters +func (p *Manager) SetRecoveryOptions(recoveryOptions *RecoveryOptions) { + if recoveryOptions == nil { + p.recoveryOptions = NewRecoveryOptions() + return + } + + p.recoveryOptions = recoveryOptions } -func (p *Manager) CurRecoveryParameters() common.Parameters { - return p.curRecoveryParameters +func (p *Manager) CurRecoveryOptions() *RecoveryOptions { + return p.curRecoveryOptions } func (p *Manager) SetHba(hba []string) { @@ -153,12 +190,8 @@ func (p *Manager) UpdateCurParameters() { p.curParameters = n.(common.Parameters) } -func (p *Manager) UpdateCurRecoveryParameters() { - n, err := copystructure.Copy(p.recoveryParameters) - if err != nil { - panic(err) - } - p.curRecoveryParameters = n.(common.Parameters) +func (p *Manager) UpdateCurRecoveryOptions() { + p.curRecoveryOptions = p.recoveryOptions.DeepCopy() } func (p *Manager) UpdateCurHba() { @@ -245,45 +278,16 @@ out: // StartTmpMerged starts postgres with a conf file different than // postgresql.conf, including it at the start of the conf if it exists func (p *Manager) StartTmpMerged() error { - tmpPostgresConfPath := filepath.Join(p.dataDir, tmpPostgresConf) - - err := common.WriteFileAtomicFunc(tmpPostgresConfPath, 0600, - func(f io.Writer) error { - // include postgresql.conf if it exists - _, err := os.Stat(filepath.Join(p.dataDir, postgresConf)) - if err != nil && !os.IsNotExist(err) { - return err - } - if !os.IsNotExist(err) { - if _, err := f.Write([]byte(fmt.Sprintf("include '%s'\n", postgresConf))); err != nil { - return err - } - } - for k, v := range p.parameters { - // Single quotes needs to be doubled - ev := strings.Replace(v, `'`, `''`, -1) - if _, err := f.Write([]byte(fmt.Sprintf("%s = '%s'\n", k, ev))); err != nil { - return err - } - } - return nil - }) - if err != nil { - return fmt.Errorf("error writing %s file: %v", tmpPostgresConf, err) - } - - if err := p.writePgHba(); err != nil { - return fmt.Errorf("error writing pg_hba.conf file: %v", err) - } - if err := p.writeRecoveryConf(); err != nil { - return fmt.Errorf("error writing %s file: %v", postgresRecoveryConf, err) + if err := p.writeConfs(true); err != nil { + return err } + tmpPostgresConfPath := filepath.Join(p.dataDir, tmpPostgresConf) return p.start("-c", fmt.Sprintf("config_file=%s", tmpPostgresConfPath)) } func (p *Manager) Start() error { - if err := p.writeConfs(); err != nil { + if err := p.writeConfs(false); err != nil { return err } return p.start() @@ -368,7 +372,7 @@ func (p *Manager) start(args ...string) error { } p.UpdateCurParameters() - p.UpdateCurRecoveryParameters() + p.UpdateCurRecoveryOptions() p.UpdateCurHba() return nil @@ -416,7 +420,7 @@ func (p *Manager) IsStarted() (bool, error) { func (p *Manager) Reload() error { log.Infow("reloading database configuration") - if err := p.writeConfs(); err != nil { + if err := p.writeConfs(false); err != nil { return err } @@ -432,7 +436,7 @@ func (p *Manager) Reload() error { } p.UpdateCurParameters() - p.UpdateCurRecoveryParameters() + p.UpdateCurRecoveryOptions() p.UpdateCurHba() return nil @@ -491,22 +495,42 @@ func (p *Manager) WaitReady(timeout time.Duration) error { } func (p *Manager) WaitRecoveryDone(timeout time.Duration) error { + maj, _, err := p.BinaryVersion() + if err != nil { + return fmt.Errorf("error fetching pg version: %v", err) + } + start := time.Now() - for time.Now().Add(-timeout).Before(start) { - _, err := os.Stat(filepath.Join(p.dataDir, "recovery.done")) - if err != nil && !os.IsNotExist(err) { - return err + if maj >= 12 { + for time.Now().Add(-timeout).Before(start) { + _, err := os.Stat(filepath.Join(p.dataDir, postgresRecoverySignal)) + if err != nil && !os.IsNotExist(err) { + return err + } + if os.IsNotExist(err) { + return nil + } + time.Sleep(1 * time.Second) } - if !os.IsNotExist(err) { - return nil + } else { + for time.Now().Add(-timeout).Before(start) { + _, err := os.Stat(filepath.Join(p.dataDir, postgresRecoveryDone)) + if err != nil && !os.IsNotExist(err) { + return err + } + if !os.IsNotExist(err) { + return nil + } + time.Sleep(1 * time.Second) } - time.Sleep(1 * time.Second) } + return fmt.Errorf("timeout waiting for db recovery") } func (p *Manager) Promote() error { log.Infow("promoting database") + name := filepath.Join(p.pgBinPath, "pg_ctl") cmd := exec.Command(name, "promote", "-w", "-D", p.dataDir) log.Debugw("execing cmd", "cmd", cmd) @@ -517,6 +541,11 @@ func (p *Manager) Promote() error { if err := cmd.Run(); err != nil { return fmt.Errorf("error: %v", err) } + + if err := p.writeConfs(false); err != nil { + return err + } + return nil } @@ -679,34 +708,88 @@ func (p *Manager) IsInitialized() (bool, error) { return true, nil } +// GetRole return the current instance role func (p *Manager) GetRole() (common.Role, error) { - // if recovery.conf exists then consider it as a standby - _, err := os.Stat(filepath.Join(p.dataDir, postgresRecoveryConf)) - if err != nil && !os.IsNotExist(err) { - return "", fmt.Errorf("error determining if recovery.conf exists: %v", err) + maj, _, err := p.BinaryVersion() + if err != nil { + return "", fmt.Errorf("error fetching pg version: %v", err) } - if os.IsNotExist(err) { - return common.RoleMaster, nil + + if maj >= 12 { + // if standby.signal file exists then consider it as a standby + _, err := os.Stat(filepath.Join(p.dataDir, postgresStandbySignal)) + if err != nil && !os.IsNotExist(err) { + return "", fmt.Errorf("error determining if %q file exists: %v", postgresStandbySignal, err) + } + if os.IsNotExist(err) { + return common.RoleMaster, nil + } + return common.RoleStandby, nil + } else { + // if recovery.conf file exists then consider it as a standby + _, err := os.Stat(filepath.Join(p.dataDir, postgresRecoveryConf)) + if err != nil && !os.IsNotExist(err) { + return "", fmt.Errorf("error determining if %q file exists: %v", postgresRecoveryConf, err) + } + if os.IsNotExist(err) { + return common.RoleMaster, nil + } + return common.RoleStandby, nil } - return common.RoleStandby, nil } -func (p *Manager) writeConfs() error { - if err := p.writeConf(); err != nil { +func (p *Manager) writeConfs(useTmpPostgresConf bool) error { + maj, _, err := p.BinaryVersion() + if err != nil { + return fmt.Errorf("error fetching pg version: %v", err) + } + + writeRecoveryParamsInPostgresConf := false + if maj >= 12 { + writeRecoveryParamsInPostgresConf = true + } + + if err := p.writeConf(useTmpPostgresConf, writeRecoveryParamsInPostgresConf); err != nil { return fmt.Errorf("error writing %s file: %v", postgresConf, err) } if err := p.writePgHba(); err != nil { return fmt.Errorf("error writing pg_hba.conf file: %v", err) } - if err := p.writeRecoveryConf(); err != nil { - return fmt.Errorf("error writing %s file: %v", postgresRecoveryConf, err) + if !writeRecoveryParamsInPostgresConf { + if err := p.writeRecoveryConf(); err != nil { + return fmt.Errorf("error writing %s file: %v", postgresRecoveryConf, err) + } + } else { + if err := p.writeStandbySignal(); err != nil { + return fmt.Errorf("error writing %s file: %v", postgresStandbySignal, err) + } + if err := p.writeRecoverySignal(); err != nil { + return fmt.Errorf("error writing %s file: %v", postgresRecoverySignal, err) + } } return nil } -func (p *Manager) writeConf() error { - return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, postgresConf), 0600, +func (p *Manager) writeConf(useTmpPostgresConf, writeRecoveryParams bool) error { + confFile := postgresConf + if useTmpPostgresConf { + confFile = tmpPostgresConf + } + + return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, confFile), 0600, func(f io.Writer) error { + if useTmpPostgresConf { + // include postgresql.conf if it exists + _, err := os.Stat(filepath.Join(p.dataDir, postgresConf)) + if err != nil && !os.IsNotExist(err) { + return err + } + if !os.IsNotExist(err) { + if _, err := f.Write([]byte(fmt.Sprintf("include '%s'\n", postgresConf))); err != nil { + return err + } + } + } for k, v := range p.parameters { // Single quotes needs to be doubled ev := strings.Replace(v, `'`, `''`, -1) @@ -714,19 +797,36 @@ func (p *Manager) writeConf() error { return err } } + + if writeRecoveryParams { + // write recovery parameters only if recoveryMode is not none + if p.recoveryOptions.RecoveryMode != RecoveryModeNone { + for n, v := range p.recoveryOptions.RecoveryParameters { + if _, err := f.Write([]byte(fmt.Sprintf("%s = '%s'\n", n, v))); err != nil { + return err + } + } + } + } + return nil }) } func (p *Manager) writeRecoveryConf() error { - // write recovery.conf only if recoveryParameters isn't nil or empty - if len(p.recoveryParameters) == 0 { + // write recovery.conf only if recoveryMode is not none + if p.recoveryOptions.RecoveryMode == RecoveryModeNone { return nil } return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, postgresRecoveryConf), 0600, func(f io.Writer) error { - for n, v := range p.recoveryParameters { + if p.recoveryOptions.RecoveryMode == RecoveryModeStandby { + if _, err := f.Write([]byte("standby_mode = 'on'\n")); err != nil { + return err + } + } + for n, v := range p.recoveryOptions.RecoveryParameters { if _, err := f.Write([]byte(fmt.Sprintf("%s = '%s'\n", n, v))); err != nil { return err } @@ -735,6 +835,34 @@ func (p *Manager) writeRecoveryConf() error { }) } +func (p *Manager) writeStandbySignal() error { + // write standby.signal only if recoveryMode is standby + if p.recoveryOptions.RecoveryMode != RecoveryModeStandby { + return nil + } + + log.Infof("writing standby signal file") + + return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, postgresStandbySignal), 0600, + func(f io.Writer) error { + return nil + }) +} + +func (p *Manager) writeRecoverySignal() error { + // write standby.signal only if recoveryMode is recovery + if p.recoveryOptions.RecoveryMode != RecoveryModeRecovery { + return nil + } + + log.Infof("writing recovery signal file") + + return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, postgresRecoverySignal), 0600, + func(f io.Writer) error { + return nil + }) +} + func (p *Manager) writePgHba() error { return common.WriteFileAtomicFunc(filepath.Join(p.dataDir, "pg_hba.conf"), 0600, func(f io.Writer) error { diff --git a/internal/postgresql/utils.go b/internal/postgresql/utils.go index d78aefe8f..4d1807107 100644 --- a/internal/postgresql/utils.go +++ b/internal/postgresql/utils.go @@ -453,7 +453,7 @@ func isRestartRequiredUsingPgSettingsContext(ctx context.Context, connParams Con } func ParseBinaryVersion(v string) (int, int, error) { - // extact version (removing beta*, rc* etc...) + // extract version (removing beta*, rc* etc...) regex, err := regexp.Compile(`.* \(PostgreSQL\) ([0-9\.]+).*`) if err != nil { return 0, 0, err diff --git a/test b/test index e3a69c8e2..b4808c2d3 100755 --- a/test +++ b/test @@ -57,7 +57,7 @@ fi echo "Checking govet -shadow ..." go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow -export PATH=${GOPATH}/bin:${PATH} +export PATH="$(go env GOPATH)/bin":${PATH} shadow_tool=$(which shadow) vetRes=$(${shadow_tool} ${PACKAGES}) if [ -n "${vetRes}" ]; then diff --git a/tests/integration/pitr_test.go b/tests/integration/pitr_test.go index e4d19327b..cc33b4fe4 100644 --- a/tests/integration/pitr_test.go +++ b/tests/integration/pitr_test.go @@ -24,10 +24,11 @@ import ( "testing" "time" - uuid "github.com/satori/go.uuid" "github.com/sorintlab/stolon/internal/cluster" "github.com/sorintlab/stolon/internal/common" "github.com/sorintlab/stolon/internal/store" + + uuid "github.com/satori/go.uuid" ) func TestPITR(t *testing.T) { @@ -173,7 +174,9 @@ func testPITR(t *testing.T, recoveryTarget bool) { if recoveryTarget { initialClusterSpec.PITRConfig.RecoveryTargetSettings = &cluster.RecoveryTargetSettings{ - RecoveryTargetTime: now.Format(time.RFC3339), + // Looks like Postgres12 cannot parse locations in UTC TZ like "2019-11-19T12:10:54Z" that previous versions were able to parse + // workaround this by not writing the timezone + RecoveryTargetTime: now.Format("2006-01-02T15:04:05"), } } diff --git a/tests/integration/utils.go b/tests/integration/utils.go index cc477771d..c91bba248 100644 --- a/tests/integration/utils.go +++ b/tests/integration/utils.go @@ -443,9 +443,18 @@ func (tk *TestKeeper) PGDataVersion() (int, int, error) { } func (tk *TestKeeper) GetPrimaryConninfo() (pg.ConnParams, error) { + maj, _, err := tk.PGDataVersion() + if err != nil { + return nil, err + } + + confFile := "recovery.conf" + if maj >= 12 { + confFile = "postgresql.conf" + } regex := regexp.MustCompile(`\s*primary_conninfo\s*=\s*'(.*)'$`) - fh, err := os.Open(filepath.Join(tk.dataDir, "postgres", "recovery.conf")) + fh, err := os.Open(filepath.Join(tk.dataDir, "postgres", confFile)) if os.IsNotExist(err) { return nil, nil }