Toolkit: Add retry to safemount.Close(). (#6762)
This commit is contained in:
Родитель
18dd756586
Коммит
bde2e39244
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package safemount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpDir string
|
||||||
|
workingDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
logger.InitStderrLog()
|
||||||
|
|
||||||
|
workingDir, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Panicf("Failed to get working directory, error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir = filepath.Join(workingDir, "_tmp")
|
||||||
|
|
||||||
|
err = os.MkdirAll(tmpDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Panicf("Failed to create tmp directory, error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retVal := m.Run()
|
||||||
|
|
||||||
|
err = os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warnf("Failed to cleanup tmp dir (%s). Error: %s", tmpDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(retVal)
|
||||||
|
}
|
|
@ -7,8 +7,10 @@ package safemount
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/logger"
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/logger"
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/retry"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,7 +94,13 @@ func (m *Mount) close(async bool) error {
|
||||||
if m.isMounted {
|
if m.isMounted {
|
||||||
if !async {
|
if !async {
|
||||||
logger.Log.Debugf("Unmounting (%s)", m.target)
|
logger.Log.Debugf("Unmounting (%s)", m.target)
|
||||||
err = unix.Unmount(m.target, 0)
|
_, err = retry.RunWithExpBackoff(
|
||||||
|
func() error {
|
||||||
|
logger.Log.Debugf("Trying to unmount (%s)", m.target)
|
||||||
|
umountErr := unix.Unmount(m.target, 0)
|
||||||
|
return umountErr
|
||||||
|
},
|
||||||
|
3, time.Second, 2.0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmount (%s):\n%w", m.target, err)
|
return fmt.Errorf("failed to unmount (%s):\n%w", m.target, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package safemount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/imagegen/configuration"
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/imagegen/diskutils"
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/buildpipeline"
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/file"
|
||||||
|
"github.com/microsoft/CBL-Mariner/toolkit/tools/internal/safeloopback"
|
||||||
|
"github.com/moby/sys/mountinfo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RetryDuration = 3 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResourceBusy(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Short mode enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !buildpipeline.IsRegularBuild() {
|
||||||
|
t.Skip("loopback block device not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
t.Skip("Test must be run as root because it uses loopback devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDir := filepath.Join(tmpDir, "TestResourceBusy")
|
||||||
|
err := os.MkdirAll(buildDir, 0o770)
|
||||||
|
if !assert.NoError(t, err, "failed to test temp directory (%s)", buildDir) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diskConfig := configuration.Disk{
|
||||||
|
PartitionTableType: configuration.PartitionTableTypeGpt,
|
||||||
|
MaxSize: 4096,
|
||||||
|
Partitions: []configuration.Partition{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
Start: 1,
|
||||||
|
End: 0,
|
||||||
|
FsType: "ext4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create raw disk image file.
|
||||||
|
rawDisk, err := diskutils.CreateEmptyDisk(buildDir, "disk.raw", diskConfig.MaxSize)
|
||||||
|
assert.NoError(t, err, "failed to create empty disk file (%s)", buildDir)
|
||||||
|
|
||||||
|
// Connect raw disk image file.
|
||||||
|
loopback, err := safeloopback.NewLoopback(rawDisk)
|
||||||
|
if !assert.NoError(t, err, "failed to mount raw disk as a loopback device (%s)", rawDisk) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer loopback.Close()
|
||||||
|
|
||||||
|
// Set up partitions.
|
||||||
|
_, _, _, _, err = diskutils.CreatePartitions(loopback.DevicePath(), diskConfig,
|
||||||
|
configuration.RootEncryption{}, configuration.ReadOnlyVerityRoot{})
|
||||||
|
if !assert.NoError(t, err, "failed to create partitions on disk", loopback.DevicePath()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount the partition.
|
||||||
|
partitionDevPath := loopback.DevicePath() + "p1"
|
||||||
|
partitionMountPath := filepath.Join(buildDir, "mount")
|
||||||
|
|
||||||
|
mount, err := NewMount(partitionDevPath, partitionMountPath, "ext4", 0, "", true)
|
||||||
|
if !assert.NoError(t, err, "failed to mount partition", partitionDevPath, partitionMountPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mount.Close()
|
||||||
|
|
||||||
|
// Check that the mount exists.
|
||||||
|
exists, err := file.PathExists(partitionMountPath)
|
||||||
|
if !assert.NoError(t, err, "failed to check if mount directory exists") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, true, exists, "mount directory doesn't exist") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isMounted, err := mountinfo.Mounted(partitionMountPath)
|
||||||
|
if !assert.NoError(t, err, "failed to check if directory is not a mount point") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, true, isMounted, "directory is not a mount point") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a file.
|
||||||
|
fileOnPartitionPath := filepath.Join(partitionMountPath, "test")
|
||||||
|
|
||||||
|
fileOnPartition, err := os.OpenFile(fileOnPartitionPath, os.O_RDWR|os.O_CREATE, 0)
|
||||||
|
if !assert.NoErrorf(t, err, "failed to open file", fileOnPartitionPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fileOnPartition.Close()
|
||||||
|
|
||||||
|
// Try to close the mount.
|
||||||
|
startTime := time.Now()
|
||||||
|
err = mount.CleanClose()
|
||||||
|
endTime := time.Now()
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.ErrorContains(t, err, "busy")
|
||||||
|
|
||||||
|
// Sanity check that the retries were attempted.
|
||||||
|
assert.LessOrEqual(t, RetryDuration, endTime.Sub(startTime))
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
fileOnPartition.Close()
|
||||||
|
|
||||||
|
// Try to close the mount again.
|
||||||
|
err = mount.CleanClose()
|
||||||
|
assert.NoError(t, err, "failed to close the mount")
|
||||||
|
|
||||||
|
// Make sure directory is deleted.
|
||||||
|
exists, err = file.PathExists(partitionMountPath)
|
||||||
|
if !assert.NoError(t, err, "failed to check if mount still directory exists") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, false, exists, "mount directory still exists")
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче