From 462052427f1717a1a0c3f9c80e08f4422adf5633 Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Mon, 11 Apr 2022 10:35:17 +0200 Subject: [PATCH] Add constellation node state Signed-off-by: Malte Poll --- coordinator/nodestate/nodestate.go | 31 ++++++++ coordinator/nodestate/nodestate_test.go | 100 ++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 coordinator/nodestate/nodestate.go create mode 100644 coordinator/nodestate/nodestate_test.go diff --git a/coordinator/nodestate/nodestate.go b/coordinator/nodestate/nodestate.go new file mode 100644 index 000000000..34a47636f --- /dev/null +++ b/coordinator/nodestate/nodestate.go @@ -0,0 +1,31 @@ +package nodestate + +import ( + "fmt" + + "github.com/edgelesssys/constellation/cli/file" + "github.com/edgelesssys/constellation/coordinator/role" +) + +const nodeStatePath = "/run/state/constellation/node_state.json" + +// NodeState is the state of a constellation node that is required to recover from a reboot. +// Can be persisted to disk and reloaded later. +type NodeState struct { + Role role.Role + VPNPrivKey []byte +} + +// FromFile reads a NodeState from disk. +func FromFile(fileHandler file.Handler) (*NodeState, error) { + nodeState := &NodeState{} + if err := fileHandler.ReadJSON(nodeStatePath, nodeState); err != nil { + return nil, fmt.Errorf("could not load node state: %w", err) + } + return nodeState, nil +} + +// ToFile writes a NodeState to disk. +func (nodeState *NodeState) ToFile(fileHandler file.Handler) error { + return fileHandler.WriteJSON(nodeStatePath, nodeState, false) +} diff --git a/coordinator/nodestate/nodestate_test.go b/coordinator/nodestate/nodestate_test.go new file mode 100644 index 000000000..4a1d50005 --- /dev/null +++ b/coordinator/nodestate/nodestate_test.go @@ -0,0 +1,100 @@ +package nodestate + +import ( + "path/filepath" + "testing" + + "github.com/edgelesssys/constellation/cli/file" + "github.com/edgelesssys/constellation/coordinator/role" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFromFile(t *testing.T) { + testCases := map[string]struct { + fileContents string + expectedState *NodeState + errExpected bool + }{ + "nodestate exists": { + fileContents: `{ "Role": "Coordinator", "VPNPrivKey": "dGVzdA==" }`, + expectedState: &NodeState{ + Role: role.Coordinator, + VPNPrivKey: []byte("test"), + }, + }, + "nodestate file does not exist": { + errExpected: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + fs := afero.NewMemMapFs() + if tc.fileContents != "" { + require.NoError(fs.MkdirAll(filepath.Dir(nodeStatePath), 0o755)) + require.NoError(afero.WriteFile(fs, nodeStatePath, []byte(tc.fileContents), 0o644)) + } + fileHandler := file.NewHandler(fs) + state, err := FromFile(fileHandler) + if tc.errExpected { + assert.Error(err) + return + } + require.NoError(err) + assert.Equal(tc.expectedState, state) + }) + } +} + +func TestToFile(t *testing.T) { + testCases := map[string]struct { + precreateFile bool + state *NodeState + expectedFile string + errExpected bool + }{ + "writing works": { + state: &NodeState{ + Role: role.Coordinator, + VPNPrivKey: []byte("test"), + }, + expectedFile: `{ + "Role": "Coordinator", + "VPNPrivKey": "dGVzdA==" +}`, + }, + "file exists already": { + precreateFile: true, + errExpected: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + fs := afero.NewMemMapFs() + require.NoError(fs.MkdirAll(filepath.Dir(nodeStatePath), 0o755)) + if tc.precreateFile { + require.NoError(afero.WriteFile(fs, nodeStatePath, []byte("pre-existing"), 0o644)) + } + fileHandler := file.NewHandler(fs) + err := tc.state.ToFile(fileHandler) + if tc.errExpected { + assert.Error(err) + return + } + require.NoError(err) + + fileContents, err := afero.ReadFile(fs, nodeStatePath) + require.NoError(err) + assert.Equal(tc.expectedFile, string(fileContents)) + }) + } +}