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  }