...

Source file src/github.com/emissary-ingress/emissary/v3/pkg/envoytest/harness.go

Documentation: github.com/emissary-ingress/emissary/v3/pkg/envoytest

     1  package envoytest
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/datawire/dlib/dexec"
    16  	"github.com/datawire/dlib/dgroup"
    17  	"github.com/datawire/dlib/dhttp"
    18  )
    19  
    20  func GetLoopbackAddr(ctx context.Context, port int) (string, error) {
    21  	ip, err := GetLoopbackIp(ctx)
    22  	if err != nil {
    23  		return "", err
    24  	}
    25  	return fmt.Sprintf("%s:%d", ip, port), nil
    26  }
    27  
    28  func GetLoopbackIp(ctx context.Context) (string, error) {
    29  	cmd := dexec.CommandContext(ctx, "docker", "network", "inspect", "bridge", "--format={{(index .IPAM.Config 0).Gateway}}")
    30  	bs, err := cmd.Output()
    31  	if err != nil {
    32  		return "", errors.Wrapf(err, "error finding loopback ip")
    33  	}
    34  	return strings.TrimSpace(string(bs)), nil
    35  }
    36  
    37  func getOSSHome(ctx context.Context) (string, error) {
    38  	dat, err := dexec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel").Output()
    39  	if err != nil {
    40  		return "", err
    41  	}
    42  	return strings.TrimSpace(string(dat)), nil
    43  }
    44  
    45  func getLocalEnvoyImage(ctx context.Context) (string, error) {
    46  	// TODO(lukeshu): Consider unifying GetLocalEnvoyImage() with
    47  	// agent_test.go:needsDockerBuilds().
    48  	if env := os.Getenv("ENVOY_DOCKER_TAG"); env != "" { // Same env-var as tests/utils.py:assert_valid_envoy_config()
    49  		return env, nil
    50  	}
    51  
    52  	ossHome, err := getOSSHome(ctx)
    53  	if err != nil {
    54  		return "", err
    55  	}
    56  
    57  	if err := dexec.CommandContext(ctx, "make", "-C", ossHome, "docker/base-envoy.docker.tag.local").Run(); err != nil {
    58  		return "", err
    59  	}
    60  	dat, err := os.ReadFile(filepath.Join(ossHome, "docker/base-envoy.docker"))
    61  	if err != nil {
    62  		return "", err
    63  	}
    64  	return strings.TrimSpace(string(dat)), nil
    65  }
    66  
    67  var (
    68  	cacheDevNullMu sync.Mutex
    69  	cacheDevNull   *os.File
    70  )
    71  
    72  func getDevNull() (*os.File, error) {
    73  	cacheDevNullMu.Lock()
    74  	defer cacheDevNullMu.Unlock()
    75  	if cacheDevNull != nil {
    76  		return cacheDevNull, nil
    77  	}
    78  	var err error
    79  	cacheDevNull, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
    80  	return cacheDevNull, err
    81  }
    82  
    83  func LocalEnvoyCmd(ctx context.Context, dockerFlags, envoyFlags []string) (*dexec.Cmd, error) {
    84  	image, err := getLocalEnvoyImage(ctx)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	cmdline := []string{"docker", "run", "--rm"}
    90  	cmdline = append(cmdline, dockerFlags...)
    91  	cmdline = append(cmdline, image, "/usr/local/bin/envoy-static-stripped")
    92  	cmdline = append(cmdline, envoyFlags...)
    93  
    94  	cmd := dexec.CommandContext(ctx, cmdline[0], cmdline[1:]...)
    95  	if os.Getenv("DEV_SHUTUP_ENVOY") != "" {
    96  		devNull, _ := getDevNull()
    97  		cmd.Stdout = devNull
    98  		cmd.Stderr = devNull
    99  	}
   100  	return cmd, nil
   101  }
   102  
   103  // RunEnvoy runs and waits on  an envoy docker container that is configured to connect to the supplied ads
   104  // address and expose the supplied portmaps. A Cleanup function is registered to shutdown the
   105  // container at the end of the test suite.
   106  func RunEnvoy(ctx context.Context, adsAddress string, portmaps ...string) error {
   107  	dockerFlags := []string{
   108  		"--interactive",
   109  	}
   110  	for _, pm := range portmaps {
   111  		dockerFlags = append(dockerFlags,
   112  			"--publish="+pm)
   113  	}
   114  
   115  	host, port, err := net.SplitHostPort(adsAddress)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	envoyFlags := []string{
   120  		"--config-yaml", fmt.Sprintf(bootstrap, host, port),
   121  	}
   122  
   123  	cmd, err := LocalEnvoyCmd(ctx, dockerFlags, envoyFlags)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	return cmd.Run()
   129  }
   130  
   131  // TODO(lance) - this makes the test brittle and breaks when we change bootstrap configuration
   132  // This is the bootstrap we use for starting envoy. This is hardcoded for now, but we may want to
   133  // make it configurable for fancier tests in the future.
   134  const bootstrap = `
   135  {
   136    "node": {
   137      "cluster": "ambassador-default",
   138      "id": "test-id"
   139    },
   140    "layered_runtime": {
   141      "layers": [
   142        {
   143          "name": "static_layer",
   144          "static_layer": {
   145            "envoy.reloadable_features.no_extension_lookup_by_name": false,
   146            "re2.max_program_size.error_level": 200
   147          }
   148        }
   149      ]
   150    },
   151    "dynamic_resources": {
   152      "ads_config": {
   153        "api_type": "GRPC",
   154        "grpc_services": [
   155          {
   156            "envoy_grpc": {
   157              "cluster_name": "ads_cluster"
   158            }
   159          }
   160        ],
   161  		  "transport_api_version": "V3"
   162      },
   163      "cds_config": {
   164        "ads": {},
   165  		  "resource_api_version": "V3"
   166      },
   167      "lds_config": {
   168        "ads": {},
   169  		  "resource_api_version": "V3"
   170      }
   171    },
   172    "static_resources": {
   173      "clusters": [
   174        {
   175          "connect_timeout": "1s",
   176          "dns_lookup_family": "V4_ONLY",
   177          "http2_protocol_options": {},
   178          "lb_policy": "ROUND_ROBIN",
   179          "load_assignment": {
   180            "cluster_name": "ads_cluster",
   181            "endpoints": [
   182              {
   183                "lb_endpoints": [
   184                  {
   185                    "endpoint": {
   186                      "address": {
   187                        "socket_address": {
   188                          "address": "%s",
   189                          "port_value": %s,
   190                          "protocol": "TCP"
   191                        }
   192                      }
   193                    }
   194                  }
   195                ]
   196              }
   197            ]
   198          },
   199          "name": "ads_cluster"
   200        }
   201      ]
   202    }
   203  }
   204  `
   205  
   206  // A RequestLogger can serve HTTP on multiple ports and records all requests to .Requests for later
   207  // examination.
   208  type RequestLogger struct {
   209  	Requests []*http.Request
   210  }
   211  
   212  func (rl *RequestLogger) Log(r *http.Request) {
   213  	rl.Requests = append(rl.Requests, r)
   214  }
   215  
   216  func (rl *RequestLogger) ListenAndServeHTTP(ctx context.Context, addresses ...string) error {
   217  	sc := &dhttp.ServerConfig{
   218  		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   219  			rl.Log(r)
   220  			_, _ = w.Write([]byte("Hello World"))
   221  		}),
   222  	}
   223  
   224  	grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
   225  		ShutdownOnNonError: true,
   226  	})
   227  
   228  	for _, addr := range addresses {
   229  		addr := addr // capture the value for the closure
   230  		grp.Go(addr, func(ctx context.Context) error {
   231  			return sc.ListenAndServe(ctx, addr)
   232  		})
   233  	}
   234  
   235  	return grp.Wait()
   236  }
   237  

View as plain text