Skip to content

Commit 6dc582d

Browse files
committed
Add test with external locking process
1 parent 9f7f728 commit 6dc582d

File tree

5 files changed

+153
-17
lines changed

5 files changed

+153
-17
lines changed

internal/pkg/agent/application/upgrade/step_mark.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,12 @@ func markerFilePath(dataDirPath string) string {
340340

341341
func lockMarkerFile(markerFileName string) (*flock.Flock, error) {
342342
fLock := flock.New(markerFileName + ".lock")
343-
_, err := fLock.TryLock()
343+
locked, err := fLock.TryLock()
344344
if err != nil {
345-
return nil, err
345+
return nil, fmt.Errorf("locking %s: %w", fLock, err)
346+
}
347+
if !locked {
348+
return nil, fmt.Errorf("failed locking %s", fLock)
346349
}
347350

348351
return fLock, nil

internal/pkg/agent/application/upgrade/step_mark_test.go

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
package upgrade
66

77
import (
8+
"bufio"
9+
"context"
810
"fmt"
911
"os"
12+
"os/exec"
1013
"path/filepath"
14+
"runtime"
15+
"strings"
1116
"testing"
1217
"time"
1318

@@ -299,11 +304,11 @@ func Test_markUpgradeLocking(t *testing.T) {
299304
}{
300305
{
301306
name: "Lock file is created when writing update marker", args: args{
302-
agent: newAgent456,
303-
previousAgent: prevAgent123,
304-
action: nil,
305-
upgradeDetails: nil,
306-
},
307+
agent: newAgent456,
308+
previousAgent: prevAgent123,
309+
action: nil,
310+
upgradeDetails: nil,
311+
},
307312
afterUpdateMarkerCreation: func(t *testing.T, dataDir string) {
308313
assert.FileExists(t, markerFilePath(dataDir), "Update marker file must exist")
309314
assert.FileExists(t, markerFilePath(dataDir)+".lock", "Update marker lock file must exist")
@@ -316,11 +321,11 @@ func Test_markUpgradeLocking(t *testing.T) {
316321
},
317322
{
318323
name: "Update marker is re-lockable after writing", args: args{
319-
agent: newAgent456,
320-
previousAgent: prevAgent123,
321-
action: nil,
322-
upgradeDetails: nil,
323-
},
324+
agent: newAgent456,
325+
previousAgent: prevAgent123,
326+
action: nil,
327+
upgradeDetails: nil,
328+
},
324329
afterUpdateMarkerCreation: func(t *testing.T, dataDir string) {
325330
assert.FileExists(t, markerFilePath(dataDir), "Update marker file must exist")
326331
assert.FileExists(t, markerFilePath(dataDir)+".lock", "Update marker lock file must exist")
@@ -335,11 +340,11 @@ func Test_markUpgradeLocking(t *testing.T) {
335340
},
336341
{
337342
name: "Update marker creation should not fail if marker is already locked by the same process", args: args{
338-
agent: newAgent456,
339-
previousAgent: prevAgent123,
340-
action: nil,
341-
upgradeDetails: nil,
342-
},
343+
agent: newAgent456,
344+
previousAgent: prevAgent123,
345+
action: nil,
346+
upgradeDetails: nil,
347+
},
343348
beforeUpdateMarkerCreation: func(t *testing.T, dataDir string) {
344349
// write some fake data in update marker file
345350
updateMarkerFilePath := markerFilePath(dataDir)
@@ -362,6 +367,62 @@ func Test_markUpgradeLocking(t *testing.T) {
362367
},
363368
wantErr: assert.NoError,
364369
},
370+
{
371+
name: "Update marker creation should fail if marker is already locked by another process", args: args{
372+
agent: newAgent456,
373+
previousAgent: prevAgent123,
374+
action: nil,
375+
upgradeDetails: nil,
376+
},
377+
beforeUpdateMarkerCreation: func(t *testing.T, dataDir string) {
378+
// write some fake data in update marker file
379+
updateMarkerFilePath := markerFilePath(dataDir)
380+
err := os.WriteFile(updateMarkerFilePath, []byte("this: is not a real update marker"), 0o664)
381+
require.NoError(t, err, "error creating fake update marker")
382+
383+
// lock the fake update marker using an external process
384+
lockFilePath := updateMarkerFilePath + ".lock"
385+
cmdCancel, lockFileCmd := createFileLockerCmd(t, lockFilePath)
386+
387+
fileLockerStdErr, err := lockFileCmd.StderrPipe()
388+
require.NoError(t, err, "Error getting stderr pipe from filelocker")
389+
390+
fileLockedCh := make(chan struct{})
391+
392+
// consume stderr to check for locking
393+
go func() {
394+
scanner := bufio.NewScanner(fileLockerStdErr)
395+
for scanner.Scan() {
396+
line := scanner.Text()
397+
if strings.Contains(line, "Acquired lock on file") {
398+
fileLockedCh <- struct{}{}
399+
}
400+
}
401+
}()
402+
403+
err = lockFileCmd.Start()
404+
require.NoError(t, err, "running filelocker should not fail")
405+
406+
t.Cleanup(func() {
407+
cmdCancel()
408+
_ = lockFileCmd.Wait()
409+
})
410+
411+
select {
412+
case <-fileLockedCh:
413+
// file was locked from the external process: all good
414+
t.Log("external filelocker acquired the lock!")
415+
case <-time.After(30 * time.Second):
416+
t.Fatalf("timed out waiting for file locker to lock the file")
417+
}
418+
},
419+
afterUpdateMarkerCreation: func(t *testing.T, dataDir string) {
420+
// verify we can't read the actual update marker since it's still locked
421+
_, err := LoadMarker(dataDir)
422+
require.Error(t, err, "loading update marker should fail")
423+
},
424+
wantErr: assert.Error,
425+
},
365426
}
366427
for _, tt := range tests {
367428
t.Run(tt.name, func(t *testing.T) {
@@ -378,6 +439,24 @@ func Test_markUpgradeLocking(t *testing.T) {
378439
}
379440
}
380441

442+
func createFileLockerCmd(t *testing.T, lockFilePath string) (context.CancelFunc, *exec.Cmd) {
443+
executableName := "filelocker"
444+
if runtime.GOOS == "windows" {
445+
executableName += ".exe"
446+
}
447+
filelockerExecutablePath := filepath.Join("test", "filelocker", executableName)
448+
require.FileExistsf(
449+
t,
450+
filelockerExecutablePath,
451+
"filelocker executable %s should exist. Please ensure that mage build:testbinaries has been executed.",
452+
filelockerExecutablePath,
453+
)
454+
455+
cmdCtx, cmdCancel := context.WithCancel(t.Context())
456+
lockFileCmd := exec.CommandContext(cmdCtx, filelockerExecutablePath, "-lockfile", lockFilePath)
457+
return cmdCancel, lockFileCmd
458+
}
459+
381460
func checkUpgradeMarker(t *testing.T, updateMarker *UpdateMarker, prevAgent agentInstall, newAgent agentInstall) {
382461
t.Helper()
383462
require.NotNil(t, updateMarker, "update marker should not be nil")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore test binary
2+
filelocker*
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
// This is a simple program that will lock a file passed using the -lockfile option, used for testing file lock works properly.
6+
// os.Interrupt or signal.SIGTERM will make the program release the lock and exit
7+
package main
8+
9+
import (
10+
"flag"
11+
"log"
12+
"os"
13+
"os/signal"
14+
"syscall"
15+
16+
"github.com/gofrs/flock"
17+
)
18+
19+
const AcquiredLockLogFmt = "Acquired lock on file %s\n"
20+
21+
const lockFileFlagName = "lockfile"
22+
23+
var lockFile = flag.String(lockFileFlagName, "", "path to lock file")
24+
25+
func main() {
26+
27+
signalCh := make(chan os.Signal, 1)
28+
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
29+
30+
flag.Parse()
31+
if *lockFile == "" {
32+
log.Fatalf("No lockfile specified. Please run %s -%s <path to lockfile>", os.Args[0], lockFileFlagName)
33+
}
34+
35+
fLock := flock.New(*lockFile)
36+
37+
locked, err := fLock.TryLock()
38+
if err != nil {
39+
log.Fatalf("Error locking %s: %s", *lockFile, err.Error())
40+
}
41+
42+
if !locked {
43+
log.Fatalf("Failed acquiring lock on %s", *lockFile)
44+
}
45+
defer fLock.Unlock()
46+
47+
log.Printf(AcquiredLockLogFmt, *lockFile)
48+
49+
s := <-signalCh
50+
log.Printf("Received signal: %s, exiting", s.String())
51+
}

magefile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ func getTestBinariesPath() ([]string, error) {
435435
filepath.Join(wd, "pkg", "component", "fake", "component"),
436436
filepath.Join(wd, "internal", "pkg", "agent", "install", "testblocking"),
437437
filepath.Join(wd, "pkg", "core", "process", "testsignal"),
438+
filepath.Join(wd, "internal", "pkg", "agent", "application", "upgrade", "test", "filelocker"),
438439
}
439440
return testBinaryPkgs, nil
440441
}

0 commit comments

Comments
 (0)