...

Source file src/github.com/emissary-ingress/emissary/v3/pkg/gateway/gw_transforms_test.go

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

     1  package gateway_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	gw "sigs.k8s.io/gateway-api/apis/v1alpha1"
    14  
    15  	"github.com/datawire/dlib/dgroup"
    16  	"github.com/datawire/dlib/dlog"
    17  	"github.com/emissary-ingress/emissary/v3/pkg/envoytest"
    18  	"github.com/emissary-ingress/emissary/v3/pkg/gateway"
    19  	"github.com/emissary-ingress/emissary/v3/pkg/kates"
    20  )
    21  
    22  func TestGatewayMatches(t *testing.T) {
    23  	t.Parallel()
    24  
    25  	ctx := dlog.NewTestContext(t, false)
    26  	grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
    27  		EnableWithSoftness: true,
    28  		ShutdownOnNonError: true,
    29  	})
    30  
    31  	grp.Go("upstream", func(ctx context.Context) error {
    32  		var reqLogger envoytest.RequestLogger
    33  		return reqLogger.ListenAndServeHTTP(ctx, ":9000", ":9002")
    34  	})
    35  	e := envoytest.NewEnvoyController(":8003")
    36  	grp.Go("envoyController", func(ctx context.Context) error {
    37  		return e.Run(ctx)
    38  	})
    39  	grp.Go("envoy", func(ctx context.Context) error {
    40  		addr, err := envoytest.GetLoopbackAddr(ctx, 8003)
    41  		if err != nil {
    42  			return err
    43  		}
    44  		return envoytest.RunEnvoy(ctx, addr, "8080:8080")
    45  	})
    46  	grp.Go("downstream", func(ctx context.Context) error {
    47  		d, err := makeDispatcher()
    48  		if err != nil {
    49  			return err
    50  		}
    51  
    52  		// One rule for each type of path match (exact, prefix, regex) and each type of header match
    53  		// (exact and regex).
    54  		if err := d.UpsertYaml(`
    55  ---
    56  kind: Gateway
    57  apiVersion: networking.x-k8s.io/v1alpha1
    58  metadata:
    59    name: my-gateway
    60    namespace: default
    61  spec:
    62    listeners:
    63    - protocol: HTTP
    64      port: 8080
    65  ---
    66  kind: HTTPRoute
    67  apiVersion: networking.x-k8s.io/v1alpha1
    68  metadata:
    69    name: my-route
    70    namespace: default
    71  
    72  spec:
    73    rules:
    74    - matches:
    75      - path:
    76          type: Exact
    77          value: /exact
    78      forwardTo:
    79      - serviceName: foo-backend-1
    80        port: 9000
    81        weight: 100
    82    - matches:
    83      - path:
    84          type: Prefix
    85          value: /prefix
    86      forwardTo:
    87      - serviceName: foo-backend-1
    88        weight: 100
    89    - matches:
    90      - path:
    91          type: RegularExpression
    92          value: "/regular_expression(_[aA]+)?"
    93      forwardTo:
    94      - serviceName: foo-backend-1
    95        weight: 100
    96    - matches:
    97      - headers:
    98          type: Exact
    99          values:
   100            exact: foo
   101      forwardTo:
   102      - serviceName: foo-backend-1
   103        weight: 100
   104    - matches:
   105      - headers:
   106          type: RegularExpression
   107          values:
   108            regular_expression: "foo(_[aA]+)?"
   109      forwardTo:
   110      - serviceName: foo-backend-1
   111        weight: 100
   112  `); err != nil {
   113  			return err
   114  		}
   115  
   116  		loopbackIp, err := envoytest.GetLoopbackIp(ctx)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		if err := d.Upsert(makeEndpoint("default", "foo-backend-1", loopbackIp, 9000)); err != nil {
   122  			return err
   123  		}
   124  		if err := d.Upsert(makeEndpoint("default", "foo-backend-2", loopbackIp, 9001)); err != nil {
   125  			return err
   126  		}
   127  
   128  		// Note: we know that the snapshot will not be nil, so no need to check. If it was then there is a
   129  		// programming error with the test, so we are OK with it panic'ing and failing the test.
   130  		version, snapshot := d.GetSnapshot(ctx)
   131  		if status, err := e.Configure(ctx, "test-id", version, snapshot); err != nil {
   132  			return err
   133  		} else if status != nil {
   134  			return fmt.Errorf("envoy error: %s", status.Message)
   135  		}
   136  
   137  		// Sometimes envoy seems to acknowledge the configuration before listening on the port. (This is
   138  		// weird because sometimes envoy sends back an error indicating that it cannot bind to the
   139  		// port. Either way, we need to check that we can actually connect before running the rest of
   140  		// the tests.
   141  		if err := checkReady(ctx, "http://127.0.0.1:8080/"); err != nil {
   142  			return err
   143  		}
   144  
   145  		assertGet(&err, ctx, "http://127.0.0.1:8080/exact", 200, "Hello World")
   146  		assertGet(&err, ctx, "http://127.0.0.1:8080/exact/foo", 404, "")
   147  		assertGet(&err, ctx, "http://127.0.0.1:8080/prefix", 200, "Hello World")
   148  		assertGet(&err, ctx, "http://127.0.0.1:8080/prefix/foo", 200, "Hello World")
   149  
   150  		assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression", 200, "Hello World")
   151  		assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_a", 200, "Hello World")
   152  		assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaaaaaaa", 200, "Hello World")
   153  		assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaAaaaAa", 200, "Hello World")
   154  		assertGet(&err, ctx, "http://127.0.0.1:8080/regular_expression_aaAaaaAab", 404, "")
   155  
   156  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "exact", "foo", 200, "Hello World")
   157  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "exact", "bar", 404, "")
   158  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo", 200, "Hello World")
   159  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaa", 200, "Hello World")
   160  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "foo_aaaaAaaaab", 404, "")
   161  		assertGetHeader(&err, ctx, "http://127.0.0.1:8080", "regular_expression", "bar", 404, "")
   162  
   163  		return err
   164  	})
   165  	assert.NoError(t, grp.Wait())
   166  }
   167  
   168  func TestBadMatchTypes(t *testing.T) {
   169  	t.Parallel()
   170  	d, err := makeDispatcher()
   171  	require.NoError(t, err)
   172  
   173  	// One rule for each type of path match (exact, prefix, regex) and each type of header match
   174  	// (exact and regex).
   175  	err = d.UpsertYaml(`
   176  ---
   177  kind: HTTPRoute
   178  apiVersion: networking.x-k8s.io/v1alpha1
   179  metadata:
   180    name: my-route
   181    namespace: default
   182  spec:
   183    rules:
   184    - matches:
   185      - path:
   186          type: Blah
   187          value: /exact
   188      forwardTo:
   189      - serviceName: foo-backend-1
   190        port: 9000
   191        weight: 100
   192  `)
   193  	assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown path match type: "Blah"`)
   194  
   195  	err = d.UpsertYaml(`
   196  ---
   197  kind: HTTPRoute
   198  apiVersion: networking.x-k8s.io/v1alpha1
   199  metadata:
   200    name: my-route
   201    namespace: default
   202  spec:
   203    rules:
   204    - matches:
   205      - headers:
   206          type: Bleh
   207          values:
   208            exact: foo
   209      forwardTo:
   210      - serviceName: foo-backend-1
   211        weight: 100
   212  `)
   213  	assertErrorContains(t, err, `processing HTTPRoute:default:my-route: unknown header match type: Bleh`)
   214  }
   215  
   216  func makeDispatcher() (*gateway.Dispatcher, error) {
   217  	d := gateway.NewDispatcher()
   218  
   219  	if err := d.Register("Gateway", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
   220  		return gateway.Compile_Gateway(untyped.(*gw.Gateway))
   221  	}); err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	if err := d.Register("HTTPRoute", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
   226  		return gateway.Compile_HTTPRoute(untyped.(*gw.HTTPRoute))
   227  	}); err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	if err := d.Register("Endpoints", func(untyped kates.Object) (*gateway.CompiledConfig, error) {
   232  		return gateway.Compile_Endpoints(untyped.(*kates.Endpoints))
   233  	}); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	return d, nil
   238  }
   239  
   240  func makeEndpoint(namespace, name, ip string, port int) *kates.Endpoints {
   241  	ports := []kates.EndpointPort{{Port: int32(port)}}
   242  	addrs := []kates.EndpointAddress{{IP: ip}}
   243  
   244  	return &kates.Endpoints{
   245  		TypeMeta:   kates.TypeMeta{Kind: "Endpoints"},
   246  		ObjectMeta: kates.ObjectMeta{Namespace: namespace, Name: name},
   247  		Subsets:    []kates.EndpointSubset{{Addresses: addrs, Ports: ports}},
   248  	}
   249  }
   250  
   251  func checkReady(ctx context.Context, url string) error {
   252  	delay := 10 * time.Millisecond
   253  	for {
   254  		if delay > 10*time.Second {
   255  			return fmt.Errorf("url never became ready: %v", url)
   256  		}
   257  		_, err := http.Get(url)
   258  		if err != nil {
   259  			dlog.Infof(ctx, "error %v, retrying...", err)
   260  			delay = delay * 2
   261  			time.Sleep(delay)
   262  			continue
   263  		}
   264  		return nil
   265  	}
   266  }
   267  
   268  func get(ctx context.Context, url string, expectedCode int, expectedBody string, headers map[string]string) error {
   269  	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	for k, v := range headers {
   274  		req.Header.Set(k, v)
   275  	}
   276  	resp, err := http.DefaultClient.Do(req)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	if resp.StatusCode != expectedCode {
   281  		return fmt.Errorf("expected HTTP status code %d but got %d",
   282  			expectedCode, resp.StatusCode)
   283  	}
   284  	actualBody, err := ioutil.ReadAll(resp.Body)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	if string(actualBody) != expectedBody {
   289  		return fmt.Errorf("expected body %q but got %q",
   290  			expectedBody, string(actualBody))
   291  	}
   292  	return nil
   293  }
   294  
   295  func assertGet(errPtr *error, ctx context.Context, url string, code int, expected string) {
   296  	if *errPtr != nil {
   297  		return
   298  	}
   299  	err := get(ctx, url, code, expected, nil)
   300  	if err != nil && *errPtr == nil {
   301  		*errPtr = err
   302  	}
   303  }
   304  
   305  func assertGetHeader(errPtr *error, ctx context.Context, url, header, value string, code int, expected string) {
   306  	if *errPtr != nil {
   307  		return
   308  	}
   309  	err := get(ctx, url, code, expected, map[string]string{
   310  		header: value,
   311  	})
   312  	if err != nil && *errPtr == nil {
   313  		*errPtr = err
   314  	}
   315  }
   316  

View as plain text