Skip to content

Commit 511725b

Browse files
authored
Config mgmt updates + tests (#119)
* Config updates + tests * Remove state changes * Revert utils change * Fix deepsource complaints * Don't write to internal config file when syncing user config * Don't overwrite internal config * Config fix * Expand tests a bit more * Ditch commented out code * cleanup
1 parent 324b529 commit 511725b

File tree

5 files changed

+197
-56
lines changed

5 files changed

+197
-56
lines changed

internal/api/handle_admin.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,37 +138,35 @@ func handleUpdatePostgresSettings(w http.ResponseWriter, r *http.Request) {
138138
return
139139
}
140140

141-
user, err := flypg.ReadFromFile(node.PGConfig.UserConfigFile())
141+
cfg, err := flypg.ReadFromFile(node.PGConfig.UserConfigFile())
142142
if err != nil {
143143
renderErr(w, err)
144144
return
145145
}
146146

147-
var in map[string]interface{}
148-
149-
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
147+
var requestedChanges map[string]interface{}
148+
if err := json.NewDecoder(r.Body).Decode(&requestedChanges); err != nil {
150149
renderErr(w, err)
151150
return
152151
}
153152

154-
for k, v := range in {
153+
for k, v := range requestedChanges {
155154
exists, err := admin.SettingExists(r.Context(), conn, k)
156155
if err != nil {
157156
renderErr(w, err)
158-
return
159157
}
160158
if !exists {
161159
renderErr(w, fmt.Errorf("invalid config option: %s", k))
162160
return
163161
}
164-
user[k] = v
162+
cfg[k] = v
165163
}
166164

167-
node.PGConfig.SetUserConfig(user)
165+
node.PGConfig.SetUserConfig(cfg)
168166

169167
var requiresRestart []string
170168

171-
for k := range user {
169+
for k := range cfg {
172170
restart, err := admin.SettingRequiresRestart(r.Context(), conn, k)
173171
if err != nil {
174172
renderErr(w, err)

internal/flypg/config.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ func SyncUserConfig(c Config, consul *state.Store) error {
6161
}
6262
c.SetUserConfig(cfg)
6363

64-
if err := WriteConfigFiles(c); err != nil {
65-
return fmt.Errorf("failed to write to pg config file: %s", err)
64+
if err := writeUserConfigFile(c); err != nil {
65+
return fmt.Errorf("failed to write user config: %s", err)
6666
}
6767

6868
return nil
@@ -167,11 +167,8 @@ func writeUserConfigFile(c Config) error {
167167
}
168168
defer func() { _ = file.Close() }()
169169

170-
internal := c.InternalConfig()
171-
172170
for key, value := range c.UserConfig() {
173171
entry := fmt.Sprintf("%s = %v\n", key, value)
174-
delete(internal, key)
175172
file.Write([]byte(entry))
176173
}
177174

internal/flypg/pg.go

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,14 @@ func (c *PGConfig) Print(w io.Writer) error {
118118
return e.Encode(cfg)
119119
}
120120

121-
// SetDefaults WriteDefaults will resolve the default configuration settings and write them to the
121+
// SetDefaults will resolve the default configuration settings and write them to the
122122
// internal config file.
123123
func (c *PGConfig) SetDefaults() error {
124124
// The default wal_segment_size in mb
125125
const walSegmentSize = 16
126126

127127
// Calculate total allocated disk in bytes
128-
diskSizeBytes, err := diskSizeInBytes()
128+
diskSizeBytes, err := diskSizeInBytes(c.dataDir)
129129
if err != nil {
130130
return fmt.Errorf("failed to fetch disk size: %s", err)
131131
}
@@ -166,7 +166,7 @@ func (c *PGConfig) SetDefaults() error {
166166
sharedPreloadLibraries = append(sharedPreloadLibraries, "timescaledb")
167167
}
168168

169-
conf := ConfigMap{
169+
c.internalConfig = ConfigMap{
170170
"random_page_cost": "1.1",
171171
"port": c.port,
172172
"shared_buffers": fmt.Sprintf("%dMB", sharedBuffersMb),
@@ -183,11 +183,37 @@ func (c *PGConfig) SetDefaults() error {
183183
"shared_preload_libraries": fmt.Sprintf("'%s'", strings.Join(sharedPreloadLibraries, ",")),
184184
}
185185

186-
c.internalConfig = conf
186+
if err := writeInternalConfigFile(c); err != nil {
187+
return fmt.Errorf("failed to write internal config file: %s", err)
188+
}
187189

188190
return nil
189191
}
190192

193+
func memTotalInBytes() (int64, error) {
194+
memoryStr := os.Getenv("FLY_VM_MEMORY_MB")
195+
if memoryStr == "" {
196+
return 0, fmt.Errorf("FLY_VM_MEMORY_MB envvar has not been set")
197+
}
198+
199+
parsed, err := strconv.ParseInt(memoryStr, 10, 64)
200+
if err != nil {
201+
return 0, err
202+
}
203+
204+
memoryBytes := parsed * (1024 * 1024)
205+
206+
return memoryBytes, nil
207+
}
208+
209+
func diskSizeInBytes(dir string) (uint64, error) {
210+
var stat syscall.Statfs_t
211+
if err := syscall.Statfs(dir, &stat); err != nil {
212+
return 0, err
213+
}
214+
return stat.Blocks * uint64(stat.Bsize), nil
215+
}
216+
191217
func (c *PGConfig) RuntimeApply(ctx context.Context, conn *pgx.Conn) error {
192218
for key, value := range c.userConfig {
193219
if err := admin.SetConfigurationSetting(ctx, conn, key, value); err != nil {
@@ -201,24 +227,20 @@ func (c *PGConfig) RuntimeApply(ctx context.Context, conn *pgx.Conn) error {
201227
// initialize will ensure the required configuration files are stubbed and the parent
202228
// postgresql.conf file includes them.
203229
func (c *PGConfig) initialize() error {
204-
if _, err := os.Stat(c.internalConfigFilePath); err != nil {
205-
if os.IsNotExist(err) {
206-
if _, err := utils.RunCommand(fmt.Sprintf("touch %s", c.internalConfigFilePath), "postgres"); err != nil {
207-
return err
208-
}
209-
} else {
210-
return err
211-
}
230+
if err := stubConfigurationFile(c.internalConfigFilePath); err != nil {
231+
return err
212232
}
213233

214-
if _, err := os.Stat(c.userConfigFilePath); err != nil {
215-
if os.IsNotExist(err) {
216-
if _, err := utils.RunCommand(fmt.Sprintf("touch %s", c.userConfigFilePath), "postgres"); err != nil {
217-
return err
218-
}
219-
} else {
220-
return err
221-
}
234+
if err := utils.SetFileOwnership(c.internalConfigFilePath, "postgres"); err != nil {
235+
return err
236+
}
237+
238+
if err := stubConfigurationFile(c.userConfigFilePath); err != nil {
239+
return err
240+
}
241+
242+
if err := utils.SetFileOwnership(c.userConfigFilePath, "postgres"); err != nil {
243+
return err
222244
}
223245

224246
b, err := os.ReadFile(c.configFilePath)
@@ -264,26 +286,19 @@ func (c *PGConfig) writePGConfigEntries(entries []string) error {
264286
return file.Sync()
265287
}
266288

267-
func memTotalInBytes() (int64, error) {
268-
memoryStr := os.Getenv("FLY_VM_MEMORY_MB")
269-
if memoryStr == "" {
270-
return 0, fmt.Errorf("FLY_VM_MEMORY_MB envvar has not been set")
271-
}
289+
func stubConfigurationFile(path string) error {
290+
_, err := os.Stat(path)
291+
if os.IsNotExist(err) {
292+
file, err := os.Create(path)
293+
if err != nil {
294+
return err
295+
}
296+
defer func() { _ = file.Close() }()
272297

273-
parsed, err := strconv.ParseInt(memoryStr, 10, 64)
274-
if err != nil {
275-
return 0, err
298+
return file.Sync()
299+
} else if err != nil {
300+
return nil
276301
}
277302

278-
memoryBytes := parsed * (1024 * 1024)
279-
280-
return memoryBytes, nil
281-
}
282-
283-
func diskSizeInBytes() (uint64, error) {
284-
var stat syscall.Statfs_t
285-
if err := syscall.Statfs("/data", &stat); err != nil {
286-
return 0, err
287-
}
288-
return stat.Blocks * uint64(stat.Bsize), nil
303+
return nil
289304
}

internal/flypg/pg_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package flypg
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
)
8+
9+
const (
10+
pgTestDirectory = "./test_results"
11+
pgConfigFilePath = "./test_results/postgresql.conf"
12+
pgInternalConfigFilePath = "./test_results/postgresql.internal.conf"
13+
pgUserConfigFilePath = "./test_results/postgresql.user.conf"
14+
)
15+
16+
func TestPGConfigDefaults(t *testing.T) {
17+
if err := setup(t); err != nil {
18+
t.Fatal(err)
19+
}
20+
defer cleanup()
21+
22+
pgConf := &PGConfig{
23+
dataDir: pgTestDirectory,
24+
port: 5433,
25+
configFilePath: pgConfigFilePath,
26+
internalConfigFilePath: pgInternalConfigFilePath,
27+
userConfigFilePath: pgUserConfigFilePath,
28+
userConfig: ConfigMap{},
29+
internalConfig: ConfigMap{},
30+
}
31+
32+
if err := pgConf.initialize(); err != nil {
33+
t.Error(err)
34+
}
35+
36+
cfg, err := ReadFromFile(pgInternalConfigFilePath)
37+
if err != nil {
38+
t.Error(err)
39+
}
40+
41+
if cfg["port"] != "5433" {
42+
t.Fatalf("expected port to be 5433, got %v", cfg["port"])
43+
}
44+
45+
if cfg["hot_standby"] != "true" {
46+
t.Fatalf("expected hot_standby to be true, got %v", cfg["hot_standby"])
47+
}
48+
}
49+
50+
func TestPGSettingOverride(t *testing.T) {
51+
if err := setup(t); err != nil {
52+
t.Fatal(err)
53+
}
54+
defer cleanup()
55+
56+
pgConf := &PGConfig{
57+
dataDir: pgTestDirectory,
58+
port: 5433,
59+
configFilePath: pgConfigFilePath,
60+
internalConfigFilePath: pgInternalConfigFilePath,
61+
userConfigFilePath: pgUserConfigFilePath,
62+
}
63+
64+
if err := pgConf.initialize(); err != nil {
65+
t.Error(err)
66+
}
67+
68+
pgConf.SetUserConfig(ConfigMap{
69+
"log_statement": "ddl",
70+
"port": "10000",
71+
})
72+
73+
if err := WriteConfigFiles(pgConf); err != nil {
74+
t.Error(err)
75+
}
76+
77+
cfg, err := pgConf.CurrentConfig()
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
82+
if cfg["port"] != "10000" {
83+
t.Fatalf("expected port to be 10000, got %v", cfg["port"])
84+
}
85+
86+
if cfg["log_statement"] != "ddl" {
87+
t.Fatalf("expected log_statement to be ddl, got %v", cfg["log_statement"])
88+
}
89+
90+
// Ensure defaults were not touched
91+
if cfg["max_connections"] != "300" {
92+
t.Fatalf("expected max_connections to be 300, got %v", cfg["max_connections"])
93+
}
94+
95+
if cfg["hot_standby"] != "true" {
96+
t.Fatalf("expected hot_standby to be true, got %v", cfg["hot_standby"])
97+
}
98+
}
99+
100+
func setup(t *testing.T) error {
101+
t.Setenv("FLY_VM_MEMORY_MB", fmt.Sprint(256*(1024*1024)))
102+
t.Setenv("UNIT_TESTING", "true")
103+
104+
if _, err := os.Stat(pgTestDirectory); err != nil {
105+
if os.IsNotExist(err) {
106+
if err := os.Mkdir(pgTestDirectory, 0750); err != nil {
107+
return err
108+
}
109+
} else {
110+
return err
111+
}
112+
}
113+
114+
file, err := os.Create(pgConfigFilePath)
115+
if err != nil {
116+
return err
117+
}
118+
defer func() { _ = file.Close() }()
119+
120+
return file.Sync()
121+
122+
}
123+
124+
func cleanup() {
125+
os.RemoveAll(pgTestDirectory)
126+
}

internal/utils/shell.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,24 @@ import (
99
"syscall"
1010
)
1111

12-
func RunCommand(cmdStr, user string) ([]byte, error) {
13-
pgUID, pgGID, err := SystemUserIDs(user)
12+
func RunCommand(cmdStr, usr string) ([]byte, error) {
13+
uid, gid, err := SystemUserIDs(usr)
1414
if err != nil {
1515
return nil, err
1616
}
1717

1818
cmd := exec.Command("sh", "-c", cmdStr)
1919
cmd.SysProcAttr = &syscall.SysProcAttr{}
20-
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(pgUID), Gid: uint32(pgGID)}
20+
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
21+
2122
return cmd.Output()
2223
}
2324

2425
func SetFileOwnership(pathToFile, owner string) error {
26+
if os.Getenv("UNIT_TESTING") != "" {
27+
return nil
28+
}
29+
2530
uid, gid, err := SystemUserIDs(owner)
2631
if err != nil {
2732
return fmt.Errorf("failed to resolve system user ids: %s", err)

0 commit comments

Comments
 (0)