Skip to content

Commit 25f8a08

Browse files
authored
Address duplicate node-id issue with math/rand (#127)
* Addresses duplicate node-id issue with math/rand * Remove extra newline * Add tests * Throw error if we are unable to resolve the node id from the config file * Cleanup * Cleaning up test
1 parent 36f3ff5 commit 25f8a08

File tree

4 files changed

+134
-11
lines changed

4 files changed

+134
-11
lines changed

internal/flypg/node.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package flypg
22

33
import (
44
"context"
5-
"encoding/binary"
65
"errors"
76
"fmt"
8-
"math/rand"
97
"net"
108
"os"
119
"os/exec"
@@ -86,13 +84,7 @@ func NewNode() (*Node, error) {
8684
Password: os.Getenv("REPL_PASSWORD"),
8785
}
8886

89-
// Generate a random, reconstructable signed int32
90-
machineID := os.Getenv("FLY_ALLOC_ID")
91-
seed := binary.LittleEndian.Uint64([]byte(machineID))
92-
rand.Seed(int64(seed))
93-
9487
node.RepMgr = RepMgr{
95-
ID: rand.Int31(), // skipcq: GSC-G404
9688
AppName: node.AppName,
9789
PrimaryRegion: node.PrimaryRegion,
9890
Region: os.Getenv("FLY_REGION"),

internal/flypg/repmgr.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package flypg
22

33
import (
44
"context"
5+
"crypto/rand"
56
"fmt"
7+
"math"
8+
"math/big"
69
"net"
710
"os"
811
"strconv"
@@ -95,7 +98,9 @@ func (r *RepMgr) NewRemoteConnection(ctx context.Context, hostname string) (*pgx
9598
}
9699

97100
func (r *RepMgr) initialize() error {
98-
r.setDefaults()
101+
if err := r.setDefaults(); err != nil {
102+
return fmt.Errorf("failed to set repmgr defaults: %s", err)
103+
}
99104

100105
file, err := os.Create(r.ConfigPath)
101106
if err != nil {
@@ -139,9 +144,14 @@ func (r *RepMgr) setup(ctx context.Context, conn *pgx.Conn) error {
139144
return nil
140145
}
141146

142-
func (r *RepMgr) setDefaults() {
147+
func (r *RepMgr) setDefaults() error {
148+
nodeID, err := r.resolveNodeID()
149+
if err != nil {
150+
return err
151+
}
152+
143153
conf := ConfigMap{
144-
"node_id": fmt.Sprint(r.ID),
154+
"node_id": nodeID,
145155
"node_name": fmt.Sprintf("'%s'", r.PrivateIP),
146156
"conninfo": fmt.Sprintf("'host=%s port=%d user=%s dbname=%s connect_timeout=10'", r.PrivateIP, r.Port, r.Credentials.Username, r.DatabaseName),
147157
"data_directory": fmt.Sprintf("'%s'", r.DataDir),
@@ -163,6 +173,36 @@ func (r *RepMgr) setDefaults() {
163173
}
164174

165175
r.internalConfig = conf
176+
177+
return nil
178+
}
179+
180+
func (r *RepMgr) resolveNodeID() (string, error) {
181+
var nodeID string
182+
if utils.FileExists(r.InternalConfigFile()) {
183+
// Pull existing id from configuraiton file
184+
config, err := r.CurrentConfig()
185+
if err != nil {
186+
return "", fmt.Errorf("failed to resolve current repmgr config: %s", err)
187+
}
188+
189+
if val, ok := config["node_id"]; ok {
190+
nodeID = fmt.Sprint(val)
191+
}
192+
193+
if nodeID == "" {
194+
return "", fmt.Errorf("failed to resolve existing node_id: %s", err)
195+
}
196+
} else {
197+
// Generate a new random id
198+
id, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
199+
if err != nil {
200+
return "", fmt.Errorf("failed to generate node id: %s", err)
201+
}
202+
nodeID = id.String()
203+
}
204+
205+
return nodeID, nil
166206
}
167207

168208
func (r *RepMgr) registerPrimary() error {

internal/flypg/repmgr_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package flypg
2+
3+
import (
4+
"testing"
5+
)
6+
7+
const (
8+
repmgrTestDirectory = "./test_results"
9+
repmgrConfigFilePath = "./test_results/repmgr.conf"
10+
repgmrInternalConfigFilePath = "./test_results/repmgr.internal.conf"
11+
repgmrUserConfigFilePath = "./test_results/repmgr.internal.conf"
12+
)
13+
14+
func TestRepmgrConfigDefaults(t *testing.T) {
15+
if err := setup(t); err != nil {
16+
t.Fatal(err)
17+
}
18+
defer cleanup()
19+
20+
conf := &RepMgr{
21+
AppName: "test-app",
22+
PrimaryRegion: "dev",
23+
Region: "dev",
24+
ConfigPath: repmgrConfigFilePath,
25+
InternalConfigPath: repgmrInternalConfigFilePath,
26+
UserConfigPath: repgmrUserConfigFilePath,
27+
DataDir: repmgrTestDirectory,
28+
PrivateIP: "127.0.0.1",
29+
Port: 5433,
30+
DatabaseName: "repmgr",
31+
}
32+
33+
if err := conf.setDefaults(); err != nil {
34+
t.Error(err)
35+
}
36+
37+
if conf.internalConfig["node_name"] != "'127.0.0.1'" {
38+
t.Fatalf("expected node_name to be '127.0.0.1', got %v", conf.internalConfig["node_name"])
39+
}
40+
41+
if conf.internalConfig["node_id"] == "" {
42+
t.Fatalf("expected node_id to not be empty, got %q", conf.internalConfig["node_id"])
43+
}
44+
45+
}
46+
47+
func TestRepmgrNodeIDGeneration(t *testing.T) {
48+
if err := setup(t); err != nil {
49+
t.Fatal(err)
50+
}
51+
defer cleanup()
52+
53+
conf := &RepMgr{
54+
AppName: "test-app",
55+
PrimaryRegion: "dev",
56+
Region: "dev",
57+
ConfigPath: repmgrConfigFilePath,
58+
InternalConfigPath: repgmrInternalConfigFilePath,
59+
UserConfigPath: repgmrUserConfigFilePath,
60+
DataDir: repmgrTestDirectory,
61+
PrivateIP: "127.0.0.1",
62+
Port: 5433,
63+
DatabaseName: "repmgr",
64+
}
65+
66+
if err := conf.setDefaults(); err != nil {
67+
t.Error(err)
68+
}
69+
70+
if err := writeInternalConfigFile(conf); err != nil {
71+
t.Error(err)
72+
}
73+
74+
nodeID := conf.internalConfig["node_id"]
75+
76+
resolvedNodeID, err := conf.resolveNodeID()
77+
if err != nil {
78+
t.Error(err)
79+
}
80+
81+
if nodeID != resolvedNodeID {
82+
t.Fatalf("expected node_id to be %s, got %q", nodeID, resolvedNodeID)
83+
}
84+
}

internal/utils/shell.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ func SetFileOwnership(pathToFile, owner string) error {
3939
return nil
4040
}
4141

42+
func FileExists(path string) bool {
43+
if _, err := os.Stat(path); err != nil {
44+
return false
45+
}
46+
return true
47+
}
48+
4249
func SystemUserIDs(usr string) (int, int, error) {
4350
pgUser, err := user.Lookup(usr)
4451
if err != nil {

0 commit comments

Comments
 (0)