Files
zot/pkg/cli/server/stress_test.go
T
Andrei Aaron 9dfa7c3ae6 refactor(test): new apis for creating temporary files (#3605)
Replace MakeTempFile usage with MakeTempFilePath and MakeTempFileWithContent
helpers that automatically handle file lifecycle. This prevents resource
leaks by ensuring temporary files are properly closed.

Shoudld also make the tests easier to read.

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
2025-12-05 09:54:38 +02:00

217 lines
5.1 KiB
Go

//go:build stress
package server_test
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/v2/pkg/api"
"zotregistry.dev/zot/v2/pkg/api/config"
cli "zotregistry.dev/zot/v2/pkg/cli/server"
test "zotregistry.dev/zot/v2/pkg/test/common"
)
const (
MaxFileDescriptors = 100
WorkerRunningTime = 60 * time.Second
)
func TestStressTooManyOpenFiles(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
Convey("configure zot with dedupe=false", t, func(c C) {
// In case one of the So()-assertions will fail it will allow us to print
// all the log files to figure out what happened in this test (zot log file, scrub output, storage rootFS tree)
SetDefaultFailureMode(FailureContinues)
initialLimit, err := setMaxOpenFilesLimit(MaxFileDescriptors)
So(err, ShouldBeNil)
port := test.GetFreePort()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.Dedupe = false
conf.Storage.GC = true
tempDir := t.TempDir()
logPath := test.MakeTempFilePath(t, "zot-log.txt")
defer func() {
data, err := os.ReadFile(logPath)
if err != nil {
t.Logf("error when reading zot log file:\n%s\n", err)
}
t.Logf("\n\n Zot log file content:\n%s\n", string(data))
}()
t.Log("Log file is: ", logPath)
conf.Log.Output = logPath
ctlr := api.NewController(conf)
dir := t.TempDir()
defer func() {
// list the content of the directory (useful in case of test fail)
//nolint: noctx // old code, no context available
out, err := exec.Command("du", "-ab", dir).Output()
if err != nil {
t.Logf("error when listing storage files:\n%s\n", err)
}
t.Logf("Listing Storage root FS:\n%s\n", out)
}()
t.Log("Storage root dir is: ", dir)
ctlr.Config.Storage.RootDirectory = dir
ctrlManager := test.NewControllerManager(ctlr)
ctrlManager.StartAndWait(port)
content := fmt.Sprintf(`{
"storage": {
"rootDirectory": "%s",
"dedupe": %t,
"gc": %t
},
"http": {
"address": "127.0.0.1",
"port": "%s"
},
"log": {
"level": "debug",
"output": "%s"
}
}`, dir, conf.Storage.Dedupe, conf.Storage.GC, port, logPath)
cfgfile := test.MakeTempFileWithContent(t, "zot-test.json", content)
skopeoArgs := []string{
"copy", "--format=oci", "--insecure-policy", "--dest-tls-verify=false",
"docker://public.ecr.aws/zomato/alpine:3.11.3", fmt.Sprintf("oci:%s:alpine", dir),
}
//nolint: noctx // old code, no context available
out, err := exec.Command("skopeo", skopeoArgs...).Output()
if err != nil {
t.Logf("\nerror on skopeo copy:\n%s\n", err)
}
So(err, ShouldBeNil)
t.Logf("\nCopy test image locally:\n%s\n", out)
var wg sync.WaitGroup
for i := 1; i <= MaxFileDescriptors; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
worker(i, port, dir)
}()
}
wg.Wait()
_, err = setMaxOpenFilesLimit(initialLimit)
So(err, ShouldBeNil)
data, err := os.ReadFile(logPath)
So(err, ShouldBeNil)
So(string(data), ShouldContainSubstring, "too many open files")
ctrlManager.StopServer()
time.Sleep(2 * time.Second)
scrubFile, err := os.Create(filepath.Join(tempDir, "zot-scrub.txt"))
So(err, ShouldBeNil)
defer func() {
data, err := os.ReadFile(scrubFile.Name())
if err != nil {
t.Logf("error when reading zot scrub file:\n%s\n", err)
}
t.Logf("\n\n Zot scrub file content:\n%s\n", string(data))
}()
t.Log("Scrub file is: ", scrubFile.Name())
os.Args = []string{"cli_test", "scrub", cfgfile}
cobraCmd := cli.NewServerRootCmd()
cobraCmd.SetOut(scrubFile)
err = cobraCmd.Execute()
So(err, ShouldBeNil)
data, err = os.ReadFile(scrubFile.Name())
So(err, ShouldBeNil)
So(string(data), ShouldNotContainSubstring, "affected")
})
}
func worker(id int, zotPort, rootDir string) {
start := time.Now()
for i := 0; ; i++ {
sourceImg := fmt.Sprintf("oci:%s:alpine", rootDir)
destImg := fmt.Sprintf("docker://localhost:%s/client%d:%d", zotPort, id, i)
skopeoArgs := []string{
"copy", "--format=oci", "--insecure-policy", "--dest-tls-verify=false",
sourceImg, destImg,
}
//nolint: noctx // old code, no context available
err := exec.Command("skopeo", skopeoArgs...).Run()
if err != nil { //nolint: wsl
continue // we expect clients to receive errors due to FD limit reached on server
}
time.Sleep(100 * time.Millisecond)
end := time.Now()
latency := end.Sub(start)
if latency > WorkerRunningTime {
break
}
}
}
func setMaxOpenFilesLimit(limit test.RlimT) (test.RlimT, error) {
var rLimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return 0, err
}
fmt.Println("Current max. open files ", rLimit.Cur)
initialLimit := rLimit.Cur
rLimit.Cur = limit
fmt.Println("Changing max. open files to ", limit)
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return initialLimit, err
}
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
return initialLimit, err
}
fmt.Println("Max. open files is set to", rLimit.Cur)
return initialLimit, nil
}