0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -05:00
zot/pkg/cli/server/stress_test.go
Andrei Aaron ba6f347d8d
refactor(pkg/test): split logic in pkg/test/common.go into multiple packages (#1861)
Which could be imported independently. See more details:
1. "zotregistry.io/zot/pkg/test/common" - currently used as
   tcommon "zotregistry.io/zot/pkg/test/common" - inside pkg/test
   test "zotregistry.io/zot/pkg/test/common" - in tests
   . "zotregistry.io/zot/pkg/test/common" - in tests
Decouple zb from code in test/pkg in order to keep the size small.

2. "zotregistry.io/zot/pkg/test/image-utils" - curently used as
   . "zotregistry.io/zot/pkg/test/image-utils"

3. "zotregistry.io/zot/pkg/test/deprecated" -  curently used as
   "zotregistry.io/zot/pkg/test/deprecated"
This one will bre replaced gradually by image-utils in the future.

4. "zotregistry.io/zot/pkg/test/signature" - (cosign + notation) use as
   "zotregistry.io/zot/pkg/test/signature"

5. "zotregistry.io/zot/pkg/test/auth" - (bearer + oidc)  curently used as
   authutils "zotregistry.io/zot/pkg/test/auth"

 6. "zotregistry.io/zot/pkg/test/oci-utils" -  curently used as
   ociutils "zotregistry.io/zot/pkg/test/oci-utils"

Some unused functions were removed, some were replaced, and in
a few cases specific funtions were moved to the files they were used in.

Added an interface for the StoreController, this reduces the number of imports
of the entire image store, decreasing binary size for tests.
If the zb code was still coupled with pkg/test, this would have reflected in zb size.

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
2023-09-27 11:34:48 -07:00

214 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.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
cli "zotregistry.io/zot/pkg/cli/server"
test "zotregistry.io/zot/pkg/test/common"
)
const (
MaxFileDescriptors = 100
WorkerRunningTime = 60 * time.Second
)
func TestSressTooManyOpenFiles(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.Write([]byte(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 uint64) (uint64, 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
}