Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions lib/hypervisor/firecracker/binaries.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var binaryFS embed.FS
var (
customBinaryPathMu sync.RWMutex
customBinaryPath string
extractBinaryMu sync.Mutex
)

var versionRegex = regexp.MustCompile(`v?\d+\.\d+\.\d+`)
Expand Down Expand Up @@ -90,6 +91,14 @@ func extractBinary(p *paths.Paths, version Version) (string, error) {
return extractPath, nil
}

extractBinaryMu.Lock()
defer extractBinaryMu.Unlock()

// Another goroutine may have already extracted the binary while we waited.
if err := validateExecutable(extractPath); err == nil {
return extractPath, nil
}

data, err := binaryFS.ReadFile(embeddedPath)
if err != nil {
return "", fmt.Errorf("embedded firecracker binary not found at %s (run `make download-firecracker-binaries` or set hypervisor.firecracker_binary_path): %w", embeddedPath, err)
Expand All @@ -98,9 +107,35 @@ func extractBinary(p *paths.Paths, version Version) (string, error) {
if err := os.MkdirAll(filepath.Dir(extractPath), 0755); err != nil {
return "", fmt.Errorf("create firecracker binary directory: %w", err)
}
if err := os.WriteFile(extractPath, data, 0755); err != nil {
return "", fmt.Errorf("write firecracker binary: %w", err)

tmpFile, err := os.CreateTemp(filepath.Dir(extractPath), "firecracker-*")
if err != nil {
return "", fmt.Errorf("create firecracker temp binary: %w", err)
}
tmpPath := tmpFile.Name()
cleanupTmp := true
defer func() {
if cleanupTmp {
_ = os.Remove(tmpPath)
}
}()

if _, err := tmpFile.Write(data); err != nil {
_ = tmpFile.Close()
return "", fmt.Errorf("write firecracker temp binary: %w", err)
}
if err := tmpFile.Chmod(0755); err != nil {
_ = tmpFile.Close()
return "", fmt.Errorf("chmod firecracker temp binary: %w", err)
}
if err := tmpFile.Close(); err != nil {
return "", fmt.Errorf("close firecracker temp binary: %w", err)
}

if err := os.Rename(tmpPath, extractPath); err != nil {
return "", fmt.Errorf("install firecracker binary: %w", err)
}
cleanupTmp = false

return extractPath, nil
}
Expand Down
51 changes: 51 additions & 0 deletions lib/hypervisor/firecracker/binaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package firecracker
import (
"os"
"path/filepath"
"sync"
"testing"

"github.com/kernel/hypeman/lib/paths"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -38,3 +40,52 @@ func TestParseVersionFallback(t *testing.T) {
assert.Equal(t, defaultVersion, parseVersion("unknown"))
assert.Equal(t, V1_14_2, parseVersion("v1.14.2"))
}

func TestResolveBinaryPathConcurrentExtraction(t *testing.T) {
SetCustomBinaryPath("")
t.Cleanup(func() { SetCustomBinaryPath("") })

arch, err := normalizeArch()
require.NoError(t, err)
embeddedPath := filepath.ToSlash(filepath.Join("binaries", "firecracker", string(defaultVersion), arch, "firecracker"))
if _, err := binaryFS.ReadFile(embeddedPath); err != nil {
t.Skipf("embedded binary %s not present in this checkout", embeddedPath)
}

p := paths.New(t.TempDir())

const workers = 16
results := make(chan string, workers)
errs := make(chan error, workers)
var wg sync.WaitGroup

for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
path, err := resolveBinaryPath(p, "")
results <- path
errs <- err
}()
}

wg.Wait()
close(results)
close(errs)

var firstPath string
for path := range results {
if firstPath == "" {
firstPath = path
continue
}
assert.Equal(t, firstPath, path)
}

for err := range errs {
require.NoError(t, err)
}

require.NotEmpty(t, firstPath)
require.NoError(t, validateExecutable(firstPath))
}
Loading
Loading