Files
zot/pkg/cli/server/stress_test.go
T
Doug Rabson 432fde45af Fix building zot natively on FreeBSD (#3247)
fix: allow zot to build on a FreeBSD host (#3246)

The build works as long as the protoc package is installed on the build
host. This also fixes lint checks when building on FreeBSD, working
around common lint complaints caused by the fact that rlim_t is int64 on
FreeBSD.

Signed-off-by: Doug Rabson <dfr@rabson.org>
2025-07-08 15:12:15 +03:00

223 lines
5.2 KiB
Go

//go:build stress
// +build stress
package server_test
import (
"fmt"
"os"
"os/exec"
"sync"
"syscall"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/pkg/api"
"zotregistry.dev/zot/pkg/api/config"
cli "zotregistry.dev/zot/pkg/cli/server"
test "zotregistry.dev/zot/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
logFile, err := os.CreateTemp("", "zot-log*.txt")
So(err, ShouldBeNil)
defer func() {
data, err := os.ReadFile(logFile.Name())
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))
os.Remove(logFile.Name())
}()
t.Log("Log file is: ", logFile.Name())
conf.Log.Output = logFile.Name()
ctlr := api.NewController(conf)
dir := t.TempDir()
defer func() {
// list the content of the directory (useful in case of test fail)
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, logFile.Name())
cfgfile, err := os.CreateTemp("", "zot-test*.json")
So(err, ShouldBeNil)
defer os.Remove(cfgfile.Name()) // clean up
_, err = cfgfile.WriteString(content)
So(err, ShouldBeNil)
err = cfgfile.Close()
So(err, ShouldBeNil)
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),
}
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(logFile.Name())
So(err, ShouldBeNil)
So(string(data), ShouldContainSubstring, "too many open files")
ctrlManager.StopServer()
time.Sleep(2 * time.Second)
scrubFile, err := os.CreateTemp("", "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))
os.Remove(scrubFile.Name())
}()
t.Log("Scrub file is: ", scrubFile.Name())
os.Args = []string{"cli_test", "scrub", cfgfile.Name()}
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,
}
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
}