zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/server/stress_test.go (about)

     1  //go:build stress
     2  // +build stress
     3  
     4  package server_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"sync"
    11  	"syscall"
    12  	"testing"
    13  	"time"
    14  
    15  	. "github.com/smartystreets/goconvey/convey"
    16  
    17  	"zotregistry.dev/zot/pkg/api"
    18  	"zotregistry.dev/zot/pkg/api/config"
    19  	cli "zotregistry.dev/zot/pkg/cli/server"
    20  	test "zotregistry.dev/zot/pkg/test/common"
    21  )
    22  
    23  const (
    24  	MaxFileDescriptors = 100
    25  	WorkerRunningTime  = 60 * time.Second
    26  )
    27  
    28  func TestStressTooManyOpenFiles(t *testing.T) {
    29  	oldArgs := os.Args
    30  
    31  	defer func() { os.Args = oldArgs }()
    32  
    33  	Convey("configure zot with dedupe=false", t, func(c C) {
    34  		// In case one of the So()-assertions will fail it will allow us to print
    35  		// all the log files to figure out what happened in this test (zot log file, scrub output, storage rootFS tree)
    36  		SetDefaultFailureMode(FailureContinues)
    37  
    38  		initialLimit, err := setMaxOpenFilesLimit(MaxFileDescriptors)
    39  		So(err, ShouldBeNil)
    40  
    41  		port := test.GetFreePort()
    42  		conf := config.New()
    43  		conf.HTTP.Port = port
    44  		conf.Storage.Dedupe = false
    45  		conf.Storage.GC = true
    46  
    47  		logFile, err := os.CreateTemp("", "zot-log*.txt")
    48  		So(err, ShouldBeNil)
    49  
    50  		defer func() {
    51  			data, err := os.ReadFile(logFile.Name())
    52  			if err != nil {
    53  				t.Logf("error when reading zot log file:\n%s\n", err)
    54  			}
    55  			t.Logf("\n\n Zot log file content:\n%s\n", string(data))
    56  			os.Remove(logFile.Name())
    57  		}()
    58  		t.Log("Log file is: ", logFile.Name())
    59  		conf.Log.Output = logFile.Name()
    60  
    61  		ctlr := api.NewController(conf)
    62  		dir := t.TempDir()
    63  
    64  		defer func() {
    65  			// list the content of the directory (useful in case of test fail)
    66  			out, err := exec.Command("du", "-ab", dir).Output()
    67  			if err != nil {
    68  				t.Logf("error when listing storage files:\n%s\n", err)
    69  			}
    70  			t.Logf("Listing Storage root FS:\n%s\n", out)
    71  		}()
    72  
    73  		t.Log("Storage root dir is: ", dir)
    74  		ctlr.Config.Storage.RootDirectory = dir
    75  
    76  		ctrlManager := test.NewControllerManager(ctlr)
    77  		ctrlManager.StartAndWait(port)
    78  
    79  		content := fmt.Sprintf(`{
    80  				"storage": {
    81  					"rootDirectory": "%s",
    82  					"dedupe": %t,
    83  					"gc": %t
    84  				},
    85  				"http": {
    86  					"address": "127.0.0.1",
    87  					"port": "%s"
    88  				},
    89  				"log": {
    90  					"level": "debug",
    91  					"output": "%s"
    92  				}
    93  			}`, dir, conf.Storage.Dedupe, conf.Storage.GC, port, logFile.Name())
    94  
    95  		cfgfile, err := os.CreateTemp("", "zot-test*.json")
    96  		So(err, ShouldBeNil)
    97  		defer os.Remove(cfgfile.Name()) // clean up
    98  		_, err = cfgfile.WriteString(content)
    99  		So(err, ShouldBeNil)
   100  		err = cfgfile.Close()
   101  		So(err, ShouldBeNil)
   102  
   103  		skopeoArgs := []string{
   104  			"copy", "--format=oci", "--insecure-policy", "--dest-tls-verify=false",
   105  			"docker://public.ecr.aws/zomato/alpine:3.11.3", fmt.Sprintf("oci:%s:alpine", dir),
   106  		}
   107  		out, err := exec.Command("skopeo", skopeoArgs...).Output()
   108  		if err != nil {
   109  			t.Logf("\nerror on skopeo copy:\n%s\n", err)
   110  		}
   111  		So(err, ShouldBeNil)
   112  		t.Logf("\nCopy test image locally:\n%s\n", out)
   113  
   114  		var wg sync.WaitGroup
   115  		for i := 1; i <= MaxFileDescriptors; i++ {
   116  			wg.Add(1)
   117  
   118  			i := i
   119  
   120  			go func() {
   121  				defer wg.Done()
   122  				worker(i, port, dir)
   123  			}()
   124  		}
   125  		wg.Wait()
   126  
   127  		_, err = setMaxOpenFilesLimit(initialLimit)
   128  		So(err, ShouldBeNil)
   129  
   130  		data, err := os.ReadFile(logFile.Name())
   131  		So(err, ShouldBeNil)
   132  		So(string(data), ShouldContainSubstring, "too many open files")
   133  
   134  		ctrlManager.StopServer()
   135  		time.Sleep(2 * time.Second)
   136  
   137  		scrubFile, err := os.CreateTemp("", "zot-scrub*.txt")
   138  		So(err, ShouldBeNil)
   139  
   140  		defer func() {
   141  			data, err := os.ReadFile(scrubFile.Name())
   142  			if err != nil {
   143  				t.Logf("error when reading zot scrub file:\n%s\n", err)
   144  			}
   145  			t.Logf("\n\n Zot scrub file content:\n%s\n", string(data))
   146  			os.Remove(scrubFile.Name())
   147  		}()
   148  		t.Log("Scrub file is: ", scrubFile.Name())
   149  
   150  		os.Args = []string{"cli_test", "scrub", cfgfile.Name()}
   151  		cobraCmd := cli.NewServerRootCmd()
   152  		cobraCmd.SetOut(scrubFile)
   153  		err = cobraCmd.Execute()
   154  		So(err, ShouldBeNil)
   155  
   156  		data, err = os.ReadFile(scrubFile.Name())
   157  		So(err, ShouldBeNil)
   158  		So(string(data), ShouldNotContainSubstring, "affected")
   159  	})
   160  }
   161  
   162  func worker(id int, zotPort, rootDir string) {
   163  	start := time.Now()
   164  
   165  	for i := 0; ; i++ {
   166  		sourceImg := fmt.Sprintf("oci:%s:alpine", rootDir)
   167  		destImg := fmt.Sprintf("docker://localhost:%s/client%d:%d", zotPort, id, i)
   168  
   169  		skopeoArgs := []string{
   170  			"copy", "--format=oci", "--insecure-policy", "--dest-tls-verify=false",
   171  			sourceImg, destImg,
   172  		}
   173  		err := exec.Command("skopeo", skopeoArgs...).Run()
   174  		if err != nil { //nolint: wsl
   175  			continue // we expect clients to receive errors due to FD limit reached on server
   176  		}
   177  
   178  		time.Sleep(100 * time.Millisecond)
   179  		end := time.Now()
   180  		latency := end.Sub(start)
   181  
   182  		if latency > WorkerRunningTime {
   183  			break
   184  		}
   185  	}
   186  }
   187  
   188  func setMaxOpenFilesLimit(limit uint64) (uint64, error) {
   189  	var rLimit syscall.Rlimit
   190  
   191  	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   192  	if err != nil {
   193  		return 0, err
   194  	}
   195  
   196  	fmt.Println("Current max. open files ", rLimit.Cur)
   197  	initialLimit := rLimit.Cur
   198  	rLimit.Cur = limit
   199  	fmt.Println("Changing max. open files to ", limit)
   200  
   201  	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   202  	if err != nil {
   203  		return initialLimit, err
   204  	}
   205  
   206  	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
   207  	if err != nil {
   208  		return initialLimit, err
   209  	}
   210  
   211  	fmt.Println("Max. open files is set to", rLimit.Cur)
   212  
   213  	return initialLimit, nil
   214  }