github.com/wasilibs/nottinygc@v0.7.2-0.20240312114022-d59c9478ef51/magefiles/e2e.go (about) 1 // Copyright wasilibs authors 2 // SPDX-License-Identifier: MIT 3 4 package main 5 6 import ( 7 "encoding/json" 8 "errors" 9 "fmt" 10 "net/http" 11 "os" 12 "path/filepath" 13 "sync" 14 "sync/atomic" 15 "time" 16 17 "github.com/magefile/mage/sh" 18 ) 19 20 func E2eCoraza() error { 21 if _, err := os.Stat(filepath.Join("e2e", "coraza-proxy-wasm")); os.IsNotExist(err) { 22 // Try not pinning version, there should be no compatibility issues causing unexpected failures from a 23 // green coraza build so we get to keep forward coverage this way. 24 if err := sh.RunV("git", "clone", "https://github.com/corazawaf/coraza-proxy-wasm.git", filepath.Join("e2e", "coraza-proxy-wasm")); err != nil { 25 return err 26 } 27 } 28 29 if err := os.Chdir(filepath.Join("e2e", "coraza-proxy-wasm")); err != nil { 30 return err 31 } 32 defer func() { 33 for _, f := range []string{"ftw-envoy.log"} { 34 content, err := os.ReadFile(filepath.Join("build", f)) 35 if err != nil { 36 panic(err) 37 } 38 if err := os.WriteFile(filepath.Join("..", "..", "build", "logs", f), content, 0o644); err != nil { 39 panic(err) 40 } 41 } 42 }() 43 44 if err := sh.RunV("go", "mod", "edit", "-replace=github.com/wasilibs/nottinygc=../.."); err != nil { 45 return err 46 } 47 defer func() { 48 if err := sh.RunV("go", "mod", "edit", "-dropreplace=github.com/wasilibs/nottinygc"); err != nil { 49 panic(err) 50 } 51 }() 52 53 if err := sh.RunV("go", "run", "mage.go", "build"); err != nil { 54 return err 55 } 56 57 if err := sh.RunV("go", "run", "mage.go", "ftw"); err != nil { 58 return err 59 } 60 61 return nil 62 } 63 64 func E2eEnvoyDispatchCall() error { 65 if err := os.MkdirAll(filepath.Join("e2e", "envoy-dispatch-call", "build"), 0o755); err != nil { 66 return err 67 } 68 69 if err := sh.RunV("tinygo", "build", "-target=wasi", "-gc=custom", "-tags='custommalloc nottinygc_envoy'", "-scheduler=none", 70 "-o", filepath.Join("e2e", "envoy-dispatch-call", "build", "plugin.wasm"), "./e2e/envoy-dispatch-call"); err != nil { 71 return err 72 } 73 74 if err := sh.RunV("docker-compose", "--file", filepath.Join("e2e", "envoy-dispatch-call", "docker-compose.yml"), "up", "-d"); err != nil { 75 return err 76 } 77 defer func() { 78 if err := sh.RunV("docker-compose", "--file", filepath.Join("e2e", "envoy-dispatch-call", "docker-compose.yml"), "down", "-v"); err != nil { 79 panic(err) 80 } 81 }() 82 83 stats, err := e2eLoad("http://localhost:8080/status/200", "http://localhost:8082/stats", 40, 5000) 84 if err != nil { 85 return err 86 } 87 88 requestCount := 0 89 authCallbackCount := 0 90 authSuccessCount := 0 91 for _, s := range stats.Stats { 92 switch s.Name { 93 case "wasmcustom.envoy_wasm_plugin_on_http_request_headers_count": 94 requestCount = s.Value 95 case "wasmcustom.envoy_wasm_plugin_authCallback_count": 96 authCallbackCount = s.Value 97 case "wasmcustom.envoy_wasm_plugin_authCallback_success_count": 98 authSuccessCount = s.Value 99 } 100 } 101 if requestCount == 0 || authCallbackCount == 0 || authSuccessCount == 0 { 102 return fmt.Errorf("invalid stats: %v", stats) 103 } 104 105 if authCallbackCount != requestCount { 106 return fmt.Errorf("expected authCallback_count to equal request count, got %d != %d", authCallbackCount, requestCount) 107 } 108 if authSuccessCount != requestCount { 109 return fmt.Errorf("expected authSuccess_count to equal request count, got %d != %d", authSuccessCount, requestCount) 110 } 111 112 return nil 113 } 114 115 func E2eHigressGCTest() error { 116 if err := os.MkdirAll(filepath.Join("e2e", "higress-gc-test", "build"), 0o755); err != nil { 117 return err 118 } 119 120 if err := sh.RunV("tinygo", "build", "-target=wasi", "-gc=custom", "-tags='custommalloc nottinygc_envoy'", "-scheduler=none", 121 "-o", filepath.Join("e2e", "higress-gc-test", "build", "plugin.wasm"), "./e2e/higress-gc-test"); err != nil { 122 return err 123 } 124 125 if err := sh.RunV("docker-compose", "--file", filepath.Join("e2e", "higress-gc-test", "docker-compose.yml"), "up", "-d"); err != nil { 126 return err 127 } 128 defer func() { 129 if err := sh.RunV("docker-compose", "--file", filepath.Join("e2e", "higress-gc-test", "docker-compose.yml"), "down", "-v"); err != nil { 130 panic(err) 131 } 132 }() 133 134 _, err := e2eLoad("http://localhost:8080/hello", "http://localhost:8082/stats", 2, 10000) 135 if err != nil { 136 return err 137 } 138 139 type memStats struct { 140 Sys int `json:"Sys"` 141 } 142 143 res, err := http.Get("http://localhost:8080/hello") 144 if err != nil { 145 return err 146 } 147 defer res.Body.Close() 148 var stats memStats 149 if err := json.NewDecoder(res.Body).Decode(&stats); err != nil { 150 return err 151 } 152 153 // We expect around 20MB per VM (this reports per VM stat), a conservative 154 // 100MB should be a fine check without flakiness 155 if mem := stats.Sys; mem > 100_000_000 { 156 return fmt.Errorf("expected <100MB memory used, actual: %d", mem) 157 } 158 159 return nil 160 } 161 162 func E2eGzip() error { 163 if err := os.MkdirAll(filepath.Join("e2e", "gzip", "build"), 0o755); err != nil { 164 return err 165 } 166 167 if err := sh.RunV("tinygo", "build", "-target=wasi", "-gc=custom", "-tags='custommalloc'", "-scheduler=none", 168 "-o", filepath.Join("e2e", "gzip", "build", "main.wasm"), "./e2e/gzip"); err != nil { 169 return err 170 } 171 172 if err := sh.RunV("wasmtime", filepath.Join("e2e", "gzip", "build", "main.wasm")); err != nil { 173 return err 174 } 175 176 return nil 177 } 178 179 type counterStat struct { 180 Name string `json:"name"` 181 Value int `json:"value"` 182 } 183 184 type counterStats struct { 185 Stats []counterStat `json:"stats"` 186 } 187 188 // If needed, we can try being more sophisticated later but run some simple load for now. 189 func e2eLoad(url string, statsURL string, p int, n int) (*counterStats, error) { 190 wg := sync.WaitGroup{} 191 192 var success atomic.Uint32 193 var fails atomic.Uint32 194 195 healthy := false 196 // Wait for healthy 197 for i := 0; i < 100; i++ { 198 time.Sleep(100 * time.Millisecond) 199 res, err := http.Get(url) 200 if err != nil { 201 continue 202 } 203 if res.StatusCode == http.StatusOK { 204 healthy = true 205 break 206 } 207 } 208 209 if !healthy { 210 return nil, errors.New("failed to get healthy in 100 attempts") 211 } 212 213 for i := 0; i < p; i++ { 214 wg.Add(1) 215 go func() { 216 defer wg.Done() 217 218 for j := 0; j < n; j++ { 219 res, err := http.Get(url) 220 switch { 221 case err != nil: 222 fallthrough 223 case res.StatusCode != http.StatusOK: 224 fails.Add(1) 225 default: 226 success.Add(1) 227 } 228 } 229 }() 230 } 231 232 wg.Wait() 233 234 res, err := http.Get(fmt.Sprintf("%s?filter=wasmcustom&format=json", statsURL)) 235 if err != nil { 236 return nil, err 237 } 238 defer res.Body.Close() 239 var stats counterStats 240 if err := json.NewDecoder(res.Body).Decode(&stats); err != nil { 241 return nil, err 242 } 243 244 if s := success.Load(); s != uint32(p*n) { 245 return &stats, fmt.Errorf("expected all requests to succeed, got success=%d, fails=%d, stats=%v", s, fails.Load(), stats) 246 } 247 248 return &stats, nil 249 } 250 251 func init() { 252 if err := os.MkdirAll(filepath.Join("build", "logs"), 0o755); err != nil { 253 panic(err) 254 } 255 }