diff --git a/node/config/load.go b/node/config/load.go index 1590efedb..b0786643f 100644 --- a/node/config/load.go +++ b/node/config/load.go @@ -12,7 +12,7 @@ import ( ) // FromFile loads config from a specified file overriding defaults specified in -// the def parameter. If file does not exist or is empty defaults are asummed. +// the def parameter. If file does not exist or is empty defaults are assumed. func FromFile(path string, def interface{}) (interface{}, error) { file, err := os.Open(path) switch { diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 6733b0868..b223731d9 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -11,6 +11,7 @@ import ( "strings" "sync" + "github.com/BurntSushi/toml" "github.com/ipfs/go-datastore" fslock "github.com/ipfs/go-fs-lock" logging "github.com/ipfs/go-log/v2" @@ -229,6 +230,7 @@ type fsLockedRepo struct { dsOnce sync.Once storageLk sync.Mutex + configLk sync.Mutex } func (fsr *fsLockedRepo) Path() string { @@ -265,12 +267,50 @@ func (fsr *fsLockedRepo) stillValid() error { } func (fsr *fsLockedRepo) Config() (interface{}, error) { - if err := fsr.stillValid(); err != nil { - return nil, err - } + fsr.configLk.Lock() + defer fsr.configLk.Unlock() + + return fsr.loadConfigFromDisk() +} + +func (fsr *fsLockedRepo) loadConfigFromDisk() (interface{}, error) { return config.FromFile(fsr.join(fsConfig), defConfForType(fsr.repoType)) } +func (fsr *fsLockedRepo) SetConfig(c func(interface{})) error { + if err := fsr.stillValid(); err != nil { + return err + } + + fsr.configLk.Lock() + defer fsr.configLk.Unlock() + + cfg, err := fsr.loadConfigFromDisk() + if err != nil { + return err + } + + // mutate in-memory representation of config + c(cfg) + + // buffer into which we write TOML bytes + buf := new(bytes.Buffer) + + // encode now-mutated config as TOML and write to buffer + err = toml.NewEncoder(buf).Encode(cfg) + if err != nil { + return err + } + + // write buffer of TOML bytes to config file + err = ioutil.WriteFile(fsr.join(fsConfig), buf.Bytes(), 0644) + if err != nil { + return err + } + + return nil +} + func (fsr *fsLockedRepo) GetStorage() (stores.StorageConfig, error) { fsr.storageLk.Lock() defer fsr.storageLk.Unlock() diff --git a/node/repo/interface.go b/node/repo/interface.go index d81d644c7..5950f813f 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -38,6 +38,7 @@ type LockedRepo interface { // Returns config in this repo Config() (interface{}, error) + SetConfig(func(interface{})) error GetStorage() (stores.StorageConfig, error) SetStorage(func(*stores.StorageConfig)) error diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 17eeb0253..399b239c1 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -30,8 +30,16 @@ type MemRepo struct { token *byte datastore datastore.Datastore - configF func(t RepoType) interface{} keystore map[string]types.KeyInfo + + // given a repo type, produce the default config + configF func(t RepoType) interface{} + + // holds the current config value + config struct { + sync.Mutex + val interface{} + } } type lockedMemRepo struct { @@ -45,6 +53,10 @@ type lockedMemRepo struct { } func (lmem *lockedMemRepo) GetStorage() (stores.StorageConfig, error) { + if err := lmem.checkToken(); err != nil { + return stores.StorageConfig{}, err + } + if lmem.sc == nil { lmem.sc = &stores.StorageConfig{StoragePaths: []stores.LocalPath{ {Path: lmem.Path()}, @@ -55,6 +67,10 @@ func (lmem *lockedMemRepo) GetStorage() (stores.StorageConfig, error) { } func (lmem *lockedMemRepo) SetStorage(c func(*stores.StorageConfig)) error { + if err := lmem.checkToken(); err != nil { + return err + } + _, _ = lmem.GetStorage() c(lmem.sc) @@ -221,11 +237,32 @@ func (lmem *lockedMemRepo) Config() (interface{}, error) { if err := lmem.checkToken(); err != nil { return nil, err } - return lmem.mem.configF(lmem.t), nil + + lmem.mem.config.Lock() + defer lmem.mem.config.Unlock() + + if lmem.mem.config.val == nil { + lmem.mem.config.val = lmem.mem.configF(lmem.t) + } + + return lmem.mem.config.val, nil } -func (lmem *lockedMemRepo) Storage() (stores.StorageConfig, error) { - panic("implement me") +func (lmem *lockedMemRepo) SetConfig(c func(interface{})) error { + if err := lmem.checkToken(); err != nil { + return err + } + + lmem.mem.config.Lock() + defer lmem.mem.config.Unlock() + + if lmem.mem.config.val == nil { + lmem.mem.config.val = lmem.mem.configF(lmem.t) + } + + c(lmem.mem.config.val) + + return nil } func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error { diff --git a/node/repo/repo_test.go b/node/repo/repo_test.go index 9c43e8f4b..444fab267 100644 --- a/node/repo/repo_test.go +++ b/node/repo/repo_test.go @@ -9,6 +9,8 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/config" + + "github.com/stretchr/testify/require" ) func basicTest(t *testing.T, repo Repo) { @@ -47,10 +49,23 @@ func basicTest(t *testing.T, repo Repo) { assert.NoError(t, err, "setting multiaddr shouldn't error") assert.Equal(t, ma, apima, "returned API multiaddr should be the same") - cfg, err := lrepo.Config() - assert.Equal(t, config.DefaultFullNode(), cfg, "there should be a default config") + c1, err := lrepo.Config() + assert.Equal(t, config.DefaultFullNode(), c1, "there should be a default config") assert.NoError(t, err, "config should not error") + // mutate config and persist back to repo + err = lrepo.SetConfig(func(c interface{}) { + cfg := c.(*config.FullNode) + cfg.Client.IpfsMAddr = "duvall" + }) + assert.NoError(t, err) + + // load config and verify changes + c2, err := lrepo.Config() + require.NoError(t, err) + cfg2 := c2.(*config.FullNode) + require.Equal(t, cfg2.Client.IpfsMAddr, "duvall") + err = lrepo.Close() assert.NoError(t, err, "should be able to close")