Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
d252ea2b
Unverified
Commit
d252ea2b
authored
Jun 22, 2023
by
Adrian Sutton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
op-node: Add logic for reading and persisting sequencer state
parent
72c6fb0a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
234 additions
and
0 deletions
+234
-0
config_persistence.go
op-node/node/config_persistence.go
+134
-0
config_persistence_test.go
op-node/node/config_persistence_test.go
+100
-0
No files found.
op-node/node/config_persistence.go
0 → 100644
View file @
d252ea2b
package
node
import
(
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"sync"
)
type
RunningState
int
const
(
Unset
RunningState
=
iota
Started
Stopped
)
type
persistedState
struct
{
SequencerStarted
*
bool
`json:"sequencerStarted,omitempty"`
}
type
ConfigPersistence
interface
{
SequencerStarted
()
error
SequencerStopped
()
error
SequencerState
()
(
RunningState
,
error
)
}
var
_
ConfigPersistence
=
(
*
ActiveConfigPersistence
)(
nil
)
var
_
ConfigPersistence
=
DisabledConfigPersistence
{}
type
ActiveConfigPersistence
struct
{
lock
sync
.
Mutex
file
string
}
func
NewConfigPersistence
(
file
string
)
(
*
ActiveConfigPersistence
,
error
)
{
return
&
ActiveConfigPersistence
{
file
:
file
},
nil
}
func
(
p
*
ActiveConfigPersistence
)
SequencerStarted
()
error
{
return
p
.
persist
(
true
)
}
func
(
p
*
ActiveConfigPersistence
)
SequencerStopped
()
error
{
return
p
.
persist
(
false
)
}
// persist writes the new config state to the file as safely as possible.
// It uses sync to ensure the data is actually persisted to disk and initially writes to a temp file
// before renaming it into place. On UNIX systems this rename is typically atomic, ensuring the
// actual file isn't corrupted if IO errors occur during writing.
func
(
p
*
ActiveConfigPersistence
)
persist
(
sequencerStarted
bool
)
error
{
p
.
lock
.
Lock
()
defer
p
.
lock
.
Unlock
()
data
,
err
:=
json
.
Marshal
(
persistedState
{
SequencerStarted
:
&
sequencerStarted
})
if
err
!=
nil
{
return
fmt
.
Errorf
(
"marshall new config: %w"
,
err
)
}
dir
:=
filepath
.
Dir
(
p
.
file
)
if
err
:=
os
.
MkdirAll
(
dir
,
0755
);
err
!=
nil
{
return
fmt
.
Errorf
(
"create config dir (%v): %w"
,
p
.
file
,
err
)
}
// Write the new content to a temp file first, then rename into place
// Avoids corrupting the content if the disk is full or there are IO errors
tmpFile
:=
p
.
file
+
".tmp"
file
,
err
:=
os
.
OpenFile
(
tmpFile
,
os
.
O_WRONLY
|
os
.
O_CREATE
|
os
.
O_TRUNC
,
0644
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"open file (%v) for writing: %w"
,
tmpFile
,
err
)
}
defer
file
.
Close
()
_
,
err
=
file
.
Write
(
data
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"write new config to temp file (%v): %w"
,
tmpFile
,
err
)
}
if
err
:=
file
.
Sync
();
err
!=
nil
{
return
fmt
.
Errorf
(
"sync new config temp file (%v): %w"
,
tmpFile
,
err
)
}
// Rename to replace the previous file
if
err
:=
os
.
Rename
(
tmpFile
,
p
.
file
);
err
!=
nil
{
return
fmt
.
Errorf
(
"rename temp config file to final destination: %w"
,
err
)
}
return
nil
}
func
(
p
*
ActiveConfigPersistence
)
SequencerState
()
(
RunningState
,
error
)
{
config
,
err
:=
p
.
read
()
if
err
!=
nil
{
return
Unset
,
err
}
if
config
.
SequencerStarted
==
nil
{
return
Unset
,
nil
}
else
if
*
config
.
SequencerStarted
{
return
Started
,
nil
}
else
{
return
Stopped
,
nil
}
}
func
(
p
*
ActiveConfigPersistence
)
read
()
(
persistedState
,
error
)
{
p
.
lock
.
Lock
()
defer
p
.
lock
.
Unlock
()
data
,
err
:=
os
.
ReadFile
(
p
.
file
)
if
errors
.
Is
(
err
,
os
.
ErrNotExist
)
{
return
persistedState
{},
nil
}
else
if
err
!=
nil
{
return
persistedState
{},
fmt
.
Errorf
(
"read config file (%v): %w"
,
p
.
file
,
err
)
}
var
config
persistedState
if
err
=
json
.
Unmarshal
(
data
,
&
config
);
err
!=
nil
{
return
persistedState
{},
fmt
.
Errorf
(
"invalid config file (%v): %w"
,
p
.
file
,
err
)
}
return
config
,
nil
}
// DisabledConfigPersistence provides an implementation of config persistence
// that does not persist anything and reports unset for all values
type
DisabledConfigPersistence
struct
{
}
func
(
d
DisabledConfigPersistence
)
SequencerState
()
(
RunningState
,
error
)
{
return
Unset
,
nil
}
func
(
d
DisabledConfigPersistence
)
SequencerStarted
()
error
{
return
nil
}
func
(
d
DisabledConfigPersistence
)
SequencerStopped
()
error
{
return
nil
}
op-node/node/config_persistence_test.go
0 → 100644
View file @
d252ea2b
package
node
import
(
"testing"
"github.com/stretchr/testify/require"
)
func
TestActive
(
t
*
testing
.
T
)
{
create
:=
func
()
*
ActiveConfigPersistence
{
dir
:=
t
.
TempDir
()
config
,
err
:=
NewConfigPersistence
(
dir
+
"/state"
)
require
.
NoError
(
t
,
err
)
return
config
}
t
.
Run
(
"SequencerStateUnsetWhenFileDoesNotExist"
,
func
(
t
*
testing
.
T
)
{
config
:=
create
()
state
,
err
:=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Unset
,
state
)
})
t
.
Run
(
"PersistSequencerStarted"
,
func
(
t
*
testing
.
T
)
{
config1
:=
create
()
require
.
NoError
(
t
,
config1
.
SequencerStarted
())
state
,
err
:=
config1
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Started
,
state
)
config2
,
err
:=
NewConfigPersistence
(
config1
.
file
)
require
.
NoError
(
t
,
err
)
state
,
err
=
config2
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Started
,
state
)
})
t
.
Run
(
"PersistSequencerStopped"
,
func
(
t
*
testing
.
T
)
{
config1
:=
create
()
require
.
NoError
(
t
,
config1
.
SequencerStopped
())
state
,
err
:=
config1
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Stopped
,
state
)
config2
,
err
:=
NewConfigPersistence
(
config1
.
file
)
require
.
NoError
(
t
,
err
)
state
,
err
=
config2
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Stopped
,
state
)
})
t
.
Run
(
"PersistMultipleChanges"
,
func
(
t
*
testing
.
T
)
{
config
:=
create
()
require
.
NoError
(
t
,
config
.
SequencerStarted
())
state
,
err
:=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Started
,
state
)
require
.
NoError
(
t
,
config
.
SequencerStopped
())
state
,
err
=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Stopped
,
state
)
})
t
.
Run
(
"CreateParentDirs"
,
func
(
t
*
testing
.
T
)
{
dir
:=
t
.
TempDir
()
config
,
err
:=
NewConfigPersistence
(
dir
+
"/some/dir/state"
)
require
.
NoError
(
t
,
err
)
// Should be unset before file exists
state
,
err
:=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Unset
,
state
)
require
.
NoFileExists
(
t
,
config
.
file
)
// Should create directories when updating
require
.
NoError
(
t
,
config
.
SequencerStarted
())
require
.
FileExists
(
t
,
config
.
file
)
state
,
err
=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Started
,
state
)
})
}
func
TestDisabledConfigPersistence_AlwaysUnset
(
t
*
testing
.
T
)
{
config
:=
DisabledConfigPersistence
{}
state
,
err
:=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Unset
,
state
)
require
.
NoError
(
t
,
config
.
SequencerStarted
())
state
,
err
=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Unset
,
state
)
require
.
NoError
(
t
,
config
.
SequencerStopped
())
state
,
err
=
config
.
SequencerState
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
Unset
,
state
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment