zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/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.io/zot/pkg/api" 18 "zotregistry.io/zot/pkg/api/config" 19 cli "zotregistry.io/zot/pkg/cli/server" 20 test "zotregistry.io/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 }