Skip to content

Commit 7ca494d

Browse files
authored
Fix wal-level config validation (#161)
* Fix wal-level config validation fix logic Max wal senders must be 0 for wal-level minimal Fix error message fix * Addressing edge cases
1 parent 98cee67 commit 7ca494d

File tree

2 files changed

+127
-16
lines changed

2 files changed

+127
-16
lines changed

internal/flypg/pg.go

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -318,29 +318,82 @@ func (c *PGConfig) validateCompatibility(requested ConfigMap) (ConfigMap, error)
318318
// Wal-level
319319
if v, ok := requested["wal_level"]; ok {
320320
value := v.(string)
321+
switch value {
322+
case "minimal":
323+
var maxWalSenders int64
321324

322-
// Postgres will not boot properly if minimal is set with archive_mode enabled.
323-
if value == "minimal" {
324-
valid := false
325-
if val, ok := requested["archive_mode"]; ok {
326-
if val == "off" {
327-
valid = true
328-
}
325+
// flyctl passes in `max_wal_senders` in as a string.
326+
maxWalSendersInterface := resolveConfigValue(requested, current, "max_wal_senders", "10")
327+
328+
// Convert string to int
329+
maxWalSenders, err = strconv.ParseInt(maxWalSendersInterface.(string), 10, 64)
330+
if err != nil {
331+
return requested, fmt.Errorf("failed to parse max-wal-senders: %s", err)
329332
}
330333

331-
if !valid && current["archive_mode"] == "off" {
332-
valid = true
334+
if maxWalSenders > 0 {
335+
return requested, fmt.Errorf("max_wal_senders must be set to `0` before wal-level can be set to `minimal`")
333336
}
334337

335-
if !valid {
338+
archiveMode := resolveConfigValue(requested, current, "archive_mode", "off")
339+
if archiveMode.(string) != "off" {
336340
return requested, errors.New("archive_mode must be set to `off` before wal_level can be set to `minimal`")
337341
}
342+
343+
case "replica", "logical":
344+
var maxWalSenders int64
345+
maxWalSendersInterface := resolveConfigValue(requested, current, "max_wal_senders", "10")
346+
347+
// Convert string to int
348+
maxWalSenders, err = strconv.ParseInt(maxWalSendersInterface.(string), 10, 64)
349+
if err != nil {
350+
return requested, fmt.Errorf("failed to parse max-wal-senders: %s", err)
351+
}
352+
353+
if maxWalSenders == 0 {
354+
return requested, fmt.Errorf("max_wal_senders must be greater than `0`")
355+
}
356+
}
357+
}
358+
359+
// Max-wal-senders
360+
if v, ok := requested["max_wal_senders"]; ok {
361+
val := v.(string)
362+
363+
// Convert string to int
364+
maxWalSenders, err := strconv.ParseInt(val, 10, 64)
365+
if err != nil {
366+
return requested, fmt.Errorf("failed to parse max-wal-senders: %s", err)
338367
}
368+
369+
walLevel := resolveConfigValue(requested, current, "wal_level", "replica")
370+
371+
if maxWalSenders > 0 && walLevel == "minimal" {
372+
return requested, fmt.Errorf("max_wal_senders must be set to `0` when wal_level is `minimal`")
373+
}
374+
375+
if maxWalSenders == 0 && walLevel != "minimal" {
376+
return requested, fmt.Errorf("max_wal_senders must be greater than `0` when wal_level is set to `%s`", walLevel.(string))
377+
}
378+
339379
}
340380

341381
return requested, nil
342382
}
343383

384+
func resolveConfigValue(requested ConfigMap, current ConfigMap, key string, defaultVal interface{}) interface{} {
385+
val := requested[key]
386+
if val == nil {
387+
val = current[key]
388+
}
389+
390+
if val == nil {
391+
val = defaultVal
392+
}
393+
394+
return val
395+
}
396+
344397
type HBAEntry struct {
345398
Type string
346399
Database string

internal/flypg/pg_test.go

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -346,29 +346,87 @@ func TestValidateCompatibility(t *testing.T) {
346346
t.Fatal(err)
347347
}
348348

349-
valid = ConfigMap{
350-
"wal_level": "minimal",
351-
"archive_mode": "off",
349+
invalid := ConfigMap{
350+
"wal_level": "logical",
351+
"max_wal_senders": "0",
352+
}
353+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
354+
t.Fatal(err)
355+
}
356+
357+
invalid = ConfigMap{
358+
"wal_level": "replica",
359+
"max_wal_senders": "0",
360+
}
361+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
362+
t.Fatal(err)
363+
}
364+
365+
})
366+
367+
t.Run("WalLevelMinimal", func(t *testing.T) {
368+
valid := ConfigMap{
369+
"wal_level": "minimal",
370+
"archive_mode": "off",
371+
"max_wal_senders": "0",
352372
}
353373
if _, err := pgConf.validateCompatibility(valid); err != nil {
354374
t.Fatal(err)
355375
}
356376

357377
invalid := ConfigMap{
378+
"wal_level": "minimal",
379+
"archive_mode": "on",
380+
"max_wal_senders": "0",
381+
}
382+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
383+
t.Fatal(err)
384+
}
385+
386+
invalid = ConfigMap{
387+
"wal_level": "minimal",
388+
"archive_mode": "off",
389+
"max_wal_senders": "10",
390+
}
391+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
392+
t.Fatal(err)
393+
}
394+
395+
invalid = ConfigMap{
358396
"wal_level": "minimal",
359397
}
398+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
399+
t.Fatal(err)
400+
}
401+
})
402+
403+
t.Run("maxWalSenders", func(t *testing.T) {
404+
valid := ConfigMap{
405+
"wal_level": "minimal",
406+
"archive_mode": "off",
407+
"max_wal_senders": "0",
408+
}
409+
if _, err := pgConf.validateCompatibility(valid); err != nil {
410+
t.Fatal(err)
411+
}
360412

413+
invalid := ConfigMap{
414+
"wal_level": "replica",
415+
"max_wal_senders": "0",
416+
}
361417
if _, err := pgConf.validateCompatibility(invalid); err == nil {
362-
t.Fatal("expected wal_level minimal to fail with archiving enabled")
418+
t.Fatal(err)
363419
}
364420

365421
invalid = ConfigMap{
366-
"wal_level": "logical",
422+
"wal_level": "logical",
423+
"max_wal_senders": "0",
367424
}
368-
if _, err := pgConf.validateCompatibility(invalid); err != nil {
425+
if _, err := pgConf.validateCompatibility(invalid); err == nil {
369426
t.Fatal(err)
370427
}
371428
})
429+
372430
}
373431

374432
func stubPGConfigFile() error {

0 commit comments

Comments
 (0)