Port envd from e2b with internalized shared packages and Connect RPC
- Copy envd source from e2b-dev/infra, internalize shared dependencies
into envd/internal/shared/ (keys, filesystem, id, smap, utils)
- Switch from gRPC to Connect RPC for all envd services
- Update module paths to git.omukk.dev/wrenn/{sandbox,sandbox/envd}
- Add proto specs (process, filesystem) with buf-based code generation
- Implement full envd: process exec, filesystem ops, port forwarding,
cgroup management, MMDS integration, and HTTP API
- Update main module dependencies (firecracker SDK, pgx, goose, etc.)
- Remove placeholder .gitkeep files replaced by real implementations
This commit is contained in:
249
envd/internal/api/upload_test.go
Normal file
249
envd/internal/api/upload_test.go
Normal file
@ -0,0 +1,249 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProcessFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
uid := os.Getuid()
|
||||
gid := os.Getgid()
|
||||
|
||||
newRequest := func(content []byte) (*http.Request, io.Reader) {
|
||||
request := &http.Request{
|
||||
ContentLength: int64(len(content)),
|
||||
}
|
||||
buffer := bytes.NewBuffer(content)
|
||||
|
||||
return request, buffer
|
||||
}
|
||||
|
||||
var emptyReq http.Request
|
||||
var emptyPart *bytes.Buffer
|
||||
var emptyLogger zerolog.Logger
|
||||
|
||||
t.Run("failed to ensure directories", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpStatus, err := processFile(&emptyReq, "/proc/invalid/not-real", emptyPart, uid, gid, emptyLogger)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusInternalServerError, httpStatus)
|
||||
assert.ErrorContains(t, err, "error ensuring directories: ")
|
||||
})
|
||||
|
||||
t.Run("attempt to replace directory with a file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
httpStatus, err := processFile(&emptyReq, tempDir, emptyPart, uid, gid, emptyLogger)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusBadRequest, httpStatus, err.Error())
|
||||
assert.ErrorContains(t, err, "path is a directory: ")
|
||||
})
|
||||
|
||||
t.Run("fail to create file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpStatus, err := processFile(&emptyReq, "/proc/invalid-filename", emptyPart, uid, gid, emptyLogger)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusInternalServerError, httpStatus)
|
||||
assert.ErrorContains(t, err, "error opening file: ")
|
||||
})
|
||||
|
||||
t.Run("out of disk space", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// make a tiny tmpfs mount
|
||||
mountSize := 1024
|
||||
tempDir := createTmpfsMount(t, mountSize)
|
||||
|
||||
// create test file
|
||||
firstFileSize := mountSize / 2
|
||||
tempFile1 := filepath.Join(tempDir, "test-file-1")
|
||||
|
||||
// fill it up
|
||||
cmd := exec.CommandContext(t.Context(),
|
||||
"dd", "if=/dev/zero", "of="+tempFile1, fmt.Sprintf("bs=%d", firstFileSize), "count=1")
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a new file that would fill up the
|
||||
secondFileContents := make([]byte, mountSize*2)
|
||||
for index := range secondFileContents {
|
||||
secondFileContents[index] = 'a'
|
||||
}
|
||||
|
||||
// try to replace it
|
||||
request, buffer := newRequest(secondFileContents)
|
||||
tempFile2 := filepath.Join(tempDir, "test-file-2")
|
||||
httpStatus, err := processFile(request, tempFile2, buffer, uid, gid, emptyLogger)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusInsufficientStorage, httpStatus)
|
||||
assert.ErrorContains(t, err, "attempted to write 2048 bytes: not enough disk space")
|
||||
})
|
||||
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
tempFile := filepath.Join(tempDir, "test-file")
|
||||
|
||||
content := []byte("test-file-contents")
|
||||
request, buffer := newRequest(content)
|
||||
|
||||
httpStatus, err := processFile(request, tempFile, buffer, uid, gid, emptyLogger)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, httpStatus)
|
||||
|
||||
data, err := os.ReadFile(tempFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, data)
|
||||
})
|
||||
|
||||
t.Run("overwrite file on full disk", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// make a tiny tmpfs mount
|
||||
sizeInBytes := 1024
|
||||
tempDir := createTmpfsMount(t, 1024)
|
||||
|
||||
// create test file
|
||||
tempFile := filepath.Join(tempDir, "test-file")
|
||||
|
||||
// fill it up
|
||||
cmd := exec.CommandContext(t.Context(), "dd", "if=/dev/zero", "of="+tempFile, fmt.Sprintf("bs=%d", sizeInBytes), "count=1")
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to replace it
|
||||
content := []byte("test-file-contents")
|
||||
request, buffer := newRequest(content)
|
||||
httpStatus, err := processFile(request, tempFile, buffer, uid, gid, emptyLogger)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, httpStatus)
|
||||
})
|
||||
|
||||
t.Run("write new file on full disk", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// make a tiny tmpfs mount
|
||||
sizeInBytes := 1024
|
||||
tempDir := createTmpfsMount(t, 1024)
|
||||
|
||||
// create test file
|
||||
tempFile1 := filepath.Join(tempDir, "test-file")
|
||||
|
||||
// fill it up
|
||||
cmd := exec.CommandContext(t.Context(), "dd", "if=/dev/zero", "of="+tempFile1, fmt.Sprintf("bs=%d", sizeInBytes), "count=1")
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to write a new file
|
||||
tempFile2 := filepath.Join(tempDir, "test-file-2")
|
||||
content := []byte("test-file-contents")
|
||||
request, buffer := newRequest(content)
|
||||
httpStatus, err := processFile(request, tempFile2, buffer, uid, gid, emptyLogger)
|
||||
require.ErrorContains(t, err, "not enough disk space available")
|
||||
assert.Equal(t, http.StatusInsufficientStorage, httpStatus)
|
||||
})
|
||||
|
||||
t.Run("write new file with no inodes available", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// make a tiny tmpfs mount
|
||||
tempDir := createTmpfsMountWithInodes(t, 1024, 2)
|
||||
|
||||
// create test file
|
||||
tempFile1 := filepath.Join(tempDir, "test-file")
|
||||
|
||||
// fill it up
|
||||
cmd := exec.CommandContext(t.Context(), "dd", "if=/dev/zero", "of="+tempFile1, fmt.Sprintf("bs=%d", 100), "count=1")
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to write a new file
|
||||
tempFile2 := filepath.Join(tempDir, "test-file-2")
|
||||
content := []byte("test-file-contents")
|
||||
request, buffer := newRequest(content)
|
||||
httpStatus, err := processFile(request, tempFile2, buffer, uid, gid, emptyLogger)
|
||||
require.ErrorContains(t, err, "not enough inodes available")
|
||||
assert.Equal(t, http.StatusInsufficientStorage, httpStatus)
|
||||
})
|
||||
|
||||
t.Run("update sysfs or other virtual fs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("skipping sysfs updates: Operation not permitted with non-root user")
|
||||
}
|
||||
|
||||
filePath := "/sys/fs/cgroup/user.slice/cpu.weight"
|
||||
newContent := []byte("102\n")
|
||||
request, buffer := newRequest(newContent)
|
||||
|
||||
httpStatus, err := processFile(request, filePath, buffer, uid, gid, emptyLogger)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, httpStatus)
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newContent, data)
|
||||
})
|
||||
|
||||
t.Run("replace file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
tempFile := filepath.Join(tempDir, "test-file")
|
||||
|
||||
err := os.WriteFile(tempFile, []byte("old-contents"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
newContent := []byte("new-file-contents")
|
||||
request, buffer := newRequest(newContent)
|
||||
|
||||
httpStatus, err := processFile(request, tempFile, buffer, uid, gid, emptyLogger)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, httpStatus)
|
||||
|
||||
data, err := os.ReadFile(tempFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newContent, data)
|
||||
})
|
||||
}
|
||||
|
||||
func createTmpfsMount(t *testing.T, sizeInBytes int) string {
|
||||
t.Helper()
|
||||
|
||||
return createTmpfsMountWithInodes(t, sizeInBytes, 5)
|
||||
}
|
||||
|
||||
func createTmpfsMountWithInodes(t *testing.T, sizeInBytes, inodesCount int) string {
|
||||
t.Helper()
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
t.Skip("skipping sysfs updates: Operation not permitted with non-root user")
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
|
||||
cmd := exec.CommandContext(t.Context(),
|
||||
"mount",
|
||||
"tmpfs",
|
||||
tempDir,
|
||||
"-t", "tmpfs",
|
||||
"-o", fmt.Sprintf("size=%d,nr_inodes=%d", sizeInBytes, inodesCount))
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
ctx := context.WithoutCancel(t.Context())
|
||||
cmd := exec.CommandContext(ctx, "umount", tempDir)
|
||||
err := cmd.Run()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
return tempDir
|
||||
}
|
||||
Reference in New Issue
Block a user