refactor(thothctl): share Pi lifecycle lock
This commit is contained in:
@@ -246,6 +246,10 @@ func (i Installation) UpdateStatePath() string {
|
||||
return filepath.Join(i.ControlDirectory(), "update-state.json")
|
||||
}
|
||||
|
||||
func (i Installation) RestartStatePath() string {
|
||||
return filepath.Join(i.ControlDirectory(), "restart-state.json")
|
||||
}
|
||||
|
||||
// ProjectName is stable for one installation and avoids collisions between different checkouts.
|
||||
func (i Installation) ProjectName() string {
|
||||
sum := sha256.Sum256([]byte(i.Path))
|
||||
|
||||
@@ -224,6 +224,9 @@ func TestInstallationControlPathsAreIsolatedForDescriptorsSharingOneCheckout(t *
|
||||
if filepath.Dir(installation.UpdateStatePath()) != filepath.Dir(installation.CurrentImageOverridePath()) {
|
||||
t.Fatalf("state %q and selector %q do not share one installation control directory", installation.UpdateStatePath(), installation.CurrentImageOverridePath())
|
||||
}
|
||||
if got, want := installation.RestartStatePath(), filepath.Join(installation.ControlDirectory(), "restart-state.json"); got != want {
|
||||
t.Fatalf("RestartStatePath() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,13 +180,17 @@ type updateLock struct {
|
||||
metadata string
|
||||
}
|
||||
|
||||
var ErrLockHeld = errors.New("another Pi update or rollback is already in progress")
|
||||
var ErrLockHeld = errors.New("another Pi update, restart, or rollback is already in progress")
|
||||
|
||||
func lifecycleLockPath(statePath string) string {
|
||||
return filepath.Join(filepath.Dir(statePath), "pi-lifecycle.lock")
|
||||
}
|
||||
|
||||
func acquireLock(statePath string) (*updateLock, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(statePath), 0o700); err != nil {
|
||||
return nil, errors.New("could not create Pi update recovery directory")
|
||||
}
|
||||
path := statePath + ".lock"
|
||||
path := lifecycleLockPath(statePath)
|
||||
file := flock.New(path, flock.SetPermissions(0o600))
|
||||
locked, err := file.TryLock()
|
||||
if err != nil {
|
||||
|
||||
@@ -24,6 +24,20 @@ func TestAdvisoryLockRejectsAConcurrentOwner(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAndRestartStatePathsShareOneLifecycleLock(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
first, err := acquireLock(filepath.Join(dir, "update-state.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer first.Release()
|
||||
|
||||
second, err := acquireLock(filepath.Join(dir, "restart-state.json"))
|
||||
if !errors.Is(err, ErrLockHeld) || second != nil {
|
||||
t.Fatalf("second lock = %#v, %v; want nil, ErrLockHeld", second, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) {
|
||||
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
||||
if os.Getenv("THOTHCTL_LOCK_CRASH_HELPER") == "1" {
|
||||
@@ -47,10 +61,11 @@ func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) {
|
||||
|
||||
func TestAdvisoryLockIgnoresPartialDiagnosticMetadata(t *testing.T) {
|
||||
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
||||
if err := os.WriteFile(statePath+".lock", nil, 0o600); err != nil {
|
||||
lockPath := lifecycleLockPath(statePath)
|
||||
if err := os.WriteFile(lockPath, nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(statePath+".lock.owner.json", []byte("{partial"), 0o600); err != nil {
|
||||
if err := os.WriteFile(lockPath+".owner.json", []byte("{partial"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lock, err := acquireLock(statePath)
|
||||
|
||||
Reference in New Issue
Block a user