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")
|
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.
|
// ProjectName is stable for one installation and avoids collisions between different checkouts.
|
||||||
func (i Installation) ProjectName() string {
|
func (i Installation) ProjectName() string {
|
||||||
sum := sha256.Sum256([]byte(i.Path))
|
sum := sha256.Sum256([]byte(i.Path))
|
||||||
|
|||||||
@@ -224,6 +224,9 @@ func TestInstallationControlPathsAreIsolatedForDescriptorsSharingOneCheckout(t *
|
|||||||
if filepath.Dir(installation.UpdateStatePath()) != filepath.Dir(installation.CurrentImageOverridePath()) {
|
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())
|
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
|
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) {
|
func acquireLock(statePath string) (*updateLock, error) {
|
||||||
if err := os.MkdirAll(filepath.Dir(statePath), 0o700); err != nil {
|
if err := os.MkdirAll(filepath.Dir(statePath), 0o700); err != nil {
|
||||||
return nil, errors.New("could not create Pi update recovery directory")
|
return nil, errors.New("could not create Pi update recovery directory")
|
||||||
}
|
}
|
||||||
path := statePath + ".lock"
|
path := lifecycleLockPath(statePath)
|
||||||
file := flock.New(path, flock.SetPermissions(0o600))
|
file := flock.New(path, flock.SetPermissions(0o600))
|
||||||
locked, err := file.TryLock()
|
locked, err := file.TryLock()
|
||||||
if err != nil {
|
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) {
|
func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) {
|
||||||
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
||||||
if os.Getenv("THOTHCTL_LOCK_CRASH_HELPER") == "1" {
|
if os.Getenv("THOTHCTL_LOCK_CRASH_HELPER") == "1" {
|
||||||
@@ -47,10 +61,11 @@ func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) {
|
|||||||
|
|
||||||
func TestAdvisoryLockIgnoresPartialDiagnosticMetadata(t *testing.T) {
|
func TestAdvisoryLockIgnoresPartialDiagnosticMetadata(t *testing.T) {
|
||||||
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
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)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
lock, err := acquireLock(statePath)
|
lock, err := acquireLock(statePath)
|
||||||
|
|||||||
Reference in New Issue
Block a user