mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
fix(daemon): root local state at DataDir not BaseDir
LocalStatePath was falling back to BaseDir when DataDir was empty, putting the file at /var/lib/spinifex/spinifex/state/instance-state.json (under the daemon's own subdir) instead of the documented /var/lib/spinifex/state/instance-state.json (the platform-shared root). Drop the BaseDir fallback. Empty DataDir resolves to DefaultDataDir (/var/lib/spinifex). Tests now set DataDir alongside BaseDir on their config fixtures.
This commit is contained in:
@@ -1492,9 +1492,9 @@ const kvSyncTimeout = time.Second
|
|||||||
// localStatePath returns the on-disk path to this daemon's instance state file.
|
// localStatePath returns the on-disk path to this daemon's instance state file.
|
||||||
func (d *Daemon) localStatePath() string {
|
func (d *Daemon) localStatePath() string {
|
||||||
if d.config == nil {
|
if d.config == nil {
|
||||||
return LocalStatePath("", "")
|
return LocalStatePath("")
|
||||||
}
|
}
|
||||||
return LocalStatePath(d.config.DataDir, d.config.BaseDir)
|
return LocalStatePath(d.config.DataDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState persists the instance state. Local file is the source of truth;
|
// WriteState persists the instance state. Local file is the source of truth;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func createTestDaemon(t *testing.T, natsURL string) *Daemon {
|
|||||||
|
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
BaseDir: tmpDir,
|
BaseDir: tmpDir,
|
||||||
|
DataDir: tmpDir,
|
||||||
WalDir: tmpDir,
|
WalDir: tmpDir,
|
||||||
NATS: config.NATSConfig{
|
NATS: config.NATSConfig{
|
||||||
Host: natsURL,
|
Host: natsURL,
|
||||||
@@ -364,6 +365,7 @@ func TestDaemon_Initialization(t *testing.T) {
|
|||||||
|
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
BaseDir: tmpDir,
|
BaseDir: tmpDir,
|
||||||
|
DataDir: tmpDir,
|
||||||
WalDir: tmpDir,
|
WalDir: tmpDir,
|
||||||
NATS: config.NATSConfig{
|
NATS: config.NATSConfig{
|
||||||
Host: "nats://localhost:4222",
|
Host: "nats://localhost:4222",
|
||||||
@@ -898,11 +900,11 @@ func TestDaemon_BootAllocation(t *testing.T) {
|
|||||||
// Create daemon with NATS connection
|
// Create daemon with NATS connection
|
||||||
clusterCfg := &config.ClusterConfig{
|
clusterCfg := &config.ClusterConfig{
|
||||||
Node: "node-1",
|
Node: "node-1",
|
||||||
Nodes: map[string]config.Config{"node-1": {BaseDir: tmpDir}},
|
Nodes: map[string]config.Config{"node-1": {BaseDir: tmpDir, DataDir: tmpDir}},
|
||||||
}
|
}
|
||||||
daemon, err := NewDaemon(clusterCfg)
|
daemon, err := NewDaemon(clusterCfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
daemon.config = &config.Config{BaseDir: tmpDir}
|
daemon.config = &config.Config{BaseDir: tmpDir, DataDir: tmpDir}
|
||||||
|
|
||||||
// Connect to NATS and initialize JetStream
|
// Connect to NATS and initialize JetStream
|
||||||
nc, err := nats.Connect(natsURL)
|
nc, err := nats.Connect(natsURL)
|
||||||
@@ -3133,12 +3135,12 @@ func TestDaemon_WriteState_NilJSManager(t *testing.T) {
|
|||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
d := &Daemon{
|
d := &Daemon{
|
||||||
jsManager: nil,
|
jsManager: nil,
|
||||||
config: &config.Config{BaseDir: tmpDir},
|
config: &config.Config{DataDir: tmpDir},
|
||||||
Instances: vm.Instances{VMS: map[string]*vm.VM{"i-1": {ID: "i-1"}}},
|
Instances: vm.Instances{VMS: map[string]*vm.VM{"i-1": {ID: "i-1"}}},
|
||||||
}
|
}
|
||||||
require.NoError(t, d.WriteState())
|
require.NoError(t, d.WriteState())
|
||||||
|
|
||||||
state, err := ReadLocalState(LocalStatePath("", tmpDir))
|
state, err := ReadLocalState(LocalStatePath(tmpDir))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, state)
|
require.NotNil(t, state)
|
||||||
assert.Contains(t, state.VMS, "i-1")
|
assert.Contains(t, state.VMS, "i-1")
|
||||||
@@ -3146,13 +3148,13 @@ func TestDaemon_WriteState_NilJSManager(t *testing.T) {
|
|||||||
|
|
||||||
func TestDaemon_LoadState_NilJSManager(t *testing.T) {
|
func TestDaemon_LoadState_NilJSManager(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
require.NoError(t, WriteLocalState(LocalStatePath("", tmpDir), &vm.Instances{
|
require.NoError(t, WriteLocalState(LocalStatePath(tmpDir), &vm.Instances{
|
||||||
VMS: map[string]*vm.VM{"i-seed": {ID: "i-seed"}},
|
VMS: map[string]*vm.VM{"i-seed": {ID: "i-seed"}},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
d := &Daemon{
|
d := &Daemon{
|
||||||
jsManager: nil,
|
jsManager: nil,
|
||||||
config: &config.Config{BaseDir: tmpDir},
|
config: &config.Config{DataDir: tmpDir},
|
||||||
}
|
}
|
||||||
require.NoError(t, d.LoadState())
|
require.NoError(t, d.LoadState())
|
||||||
assert.Contains(t, d.Instances.VMS, "i-seed")
|
assert.Contains(t, d.Instances.VMS, "i-seed")
|
||||||
@@ -3161,7 +3163,7 @@ func TestDaemon_LoadState_NilJSManager(t *testing.T) {
|
|||||||
func TestDaemon_LoadState_MissingFileIsFreshInstall(t *testing.T) {
|
func TestDaemon_LoadState_MissingFileIsFreshInstall(t *testing.T) {
|
||||||
d := &Daemon{
|
d := &Daemon{
|
||||||
jsManager: nil,
|
jsManager: nil,
|
||||||
config: &config.Config{BaseDir: t.TempDir()},
|
config: &config.Config{DataDir: t.TempDir()},
|
||||||
}
|
}
|
||||||
require.NoError(t, d.LoadState())
|
require.NoError(t, d.LoadState())
|
||||||
assert.Empty(t, d.Instances.VMS)
|
assert.Empty(t, d.Instances.VMS)
|
||||||
@@ -3169,13 +3171,13 @@ func TestDaemon_LoadState_MissingFileIsFreshInstall(t *testing.T) {
|
|||||||
|
|
||||||
func TestDaemon_LoadState_CorruptFileFatal(t *testing.T) {
|
func TestDaemon_LoadState_CorruptFileFatal(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
path := LocalStatePath("", tmpDir)
|
path := LocalStatePath(tmpDir)
|
||||||
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o750))
|
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o750))
|
||||||
require.NoError(t, os.WriteFile(path, []byte("{not json"), 0o600))
|
require.NoError(t, os.WriteFile(path, []byte("{not json"), 0o600))
|
||||||
|
|
||||||
d := &Daemon{
|
d := &Daemon{
|
||||||
jsManager: nil,
|
jsManager: nil,
|
||||||
config: &config.Config{BaseDir: tmpDir},
|
config: &config.Config{DataDir: tmpDir},
|
||||||
}
|
}
|
||||||
err := d.LoadState()
|
err := d.LoadState()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -4061,6 +4063,7 @@ func TestClusterManager_TLSServesHTTPS(t *testing.T) {
|
|||||||
Nodes: map[string]config.Config{
|
Nodes: map[string]config.Config{
|
||||||
"node-1": {
|
"node-1": {
|
||||||
BaseDir: tmpDir,
|
BaseDir: tmpDir,
|
||||||
|
DataDir: tmpDir,
|
||||||
Daemon: config.DaemonConfig{
|
Daemon: config.DaemonConfig{
|
||||||
Host: addr,
|
Host: addr,
|
||||||
TLSCert: certPath,
|
TLSCert: certPath,
|
||||||
|
|||||||
@@ -34,19 +34,16 @@ type LocalState struct {
|
|||||||
VMS map[string]*vm.VM `json:"vms"`
|
VMS map[string]*vm.VM `json:"vms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalStatePath returns the absolute path to the per-node instance state file.
|
// LocalStatePath returns the absolute path to the per-node instance state file
|
||||||
// Resolution order: dataDir, then baseDir, then DefaultDataDir. The first
|
// rooted at dataDir. Empty dataDir falls back to DefaultDataDir
|
||||||
// non-empty value wins. Production sets dataDir (/var/lib/spinifex); tests
|
// (/var/lib/spinifex), the platform-shared root. The state directory sits at
|
||||||
// typically set baseDir to a t.TempDir().
|
// the platform root so it lives next to other shared state (predastore,
|
||||||
func LocalStatePath(dataDir, baseDir string) string {
|
// viperblock data dirs) rather than nested under the daemon's own subdir.
|
||||||
root := dataDir
|
func LocalStatePath(dataDir string) string {
|
||||||
if root == "" {
|
if dataDir == "" {
|
||||||
root = baseDir
|
dataDir = DefaultDataDir
|
||||||
}
|
}
|
||||||
if root == "" {
|
return filepath.Join(dataDir, DefaultLocalStateDir, LocalStateFileName)
|
||||||
root = DefaultDataDir
|
|
||||||
}
|
|
||||||
return filepath.Join(root, DefaultLocalStateDir, LocalStateFileName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteLocalState atomically writes the instance state to path.
|
// WriteLocalState atomically writes the instance state to path.
|
||||||
|
|||||||
@@ -11,18 +11,13 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocalStatePath_PrefersDataDir(t *testing.T) {
|
func TestLocalStatePath_UsesDataDir(t *testing.T) {
|
||||||
got := LocalStatePath("/data", "/base")
|
got := LocalStatePath("/data")
|
||||||
assert.Equal(t, "/data/state/instance-state.json", got)
|
assert.Equal(t, "/data/state/instance-state.json", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocalStatePath_FallsBackToBaseDir(t *testing.T) {
|
func TestLocalStatePath_DefaultWhenEmpty(t *testing.T) {
|
||||||
got := LocalStatePath("", "/base")
|
got := LocalStatePath("")
|
||||||
assert.Equal(t, "/base/state/instance-state.json", got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocalStatePath_DefaultWhenBothEmpty(t *testing.T) {
|
|
||||||
got := LocalStatePath("", "")
|
|
||||||
assert.Equal(t, "/var/lib/spinifex/state/instance-state.json", got)
|
assert.Equal(t, "/var/lib/spinifex/state/instance-state.json", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ func createDaemonWithJetStream(t *testing.T) *Daemon {
|
|||||||
|
|
||||||
clusterCfg := &config.ClusterConfig{
|
clusterCfg := &config.ClusterConfig{
|
||||||
Node: "node-1",
|
Node: "node-1",
|
||||||
Nodes: map[string]config.Config{"node-1": {BaseDir: tmpDir}},
|
Nodes: map[string]config.Config{"node-1": {BaseDir: tmpDir, DataDir: tmpDir}},
|
||||||
}
|
}
|
||||||
daemon, err := NewDaemon(clusterCfg)
|
daemon, err := NewDaemon(clusterCfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
daemon.config = &config.Config{BaseDir: tmpDir}
|
daemon.config = &config.Config{BaseDir: tmpDir, DataDir: tmpDir}
|
||||||
|
|
||||||
daemon.natsConn = nc
|
daemon.natsConn = nc
|
||||||
daemon.jsManager, err = NewJetStreamManager(nc, 1)
|
daemon.jsManager, err = NewJetStreamManager(nc, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user