diff --git a/test/integration/consul-container/libs/service/common.go b/test/integration/consul-container/libs/service/common.go index 0e0e2739571b..775113a58027 100644 --- a/test/integration/consul-container/libs/service/common.go +++ b/test/integration/consul-container/libs/service/common.go @@ -9,5 +9,5 @@ import ( const ( envoyLogLevel = "debug" - hashicorpDockerProxy = "docker.mirror.hashicorp.services" + HashicorpDockerProxy = "docker.mirror.hashicorp.services" ) diff --git a/test/integration/consul-container/libs/service/examples.go b/test/integration/consul-container/libs/service/examples.go index cc37cdb0a750..bfcaf22b2d5b 100644 --- a/test/integration/consul-container/libs/service/examples.go +++ b/test/integration/consul-container/libs/service/examples.go @@ -187,7 +187,7 @@ func NewExampleService(ctx context.Context, name string, httpPort int, grpcPort command = append(command, containerArgs...) req := testcontainers.ContainerRequest{ - Image: hashicorpDockerProxy + "/fortio/fortio", + Image: HashicorpDockerProxy + "/fortio/fortio", WaitingFor: wait.ForLog("").WithStartupTimeout(60 * time.Second), AutoRemove: false, Name: containerName, diff --git a/test/integration/consul-container/test/gateways/gateway_endpoint_test.go b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go index 84981cc5966d..76d233051d36 100644 --- a/test/integration/consul-container/test/gateways/gateway_endpoint_test.go +++ b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go @@ -227,6 +227,7 @@ type checkOptions struct { responseHeaders map[string]string statusCode int testName string + expectedBody string } // checkRoute, customized version of libassert.RouteEchos to allow for headers/distinguishing between the server instances @@ -289,8 +290,13 @@ func checkRoute(t *testing.T, port int, path string, headers map[string]string, return false } } - if !strings.Contains(string(body), "hello") { - t.Log("body does not contain 'hello'") + expectedBody := expected.expectedBody + if expectedBody == "" { + expectedBody = "hello" + } + if !strings.Contains(string(body), expectedBody) { + t.Log(string(body)) + t.Log("body does not contain " + expectedBody) return false } diff --git a/test/integration/consul-container/test/gateways/http_route_test.go b/test/integration/consul-container/test/gateways/http_route_test.go index bc9c971e7d97..96c93da955a4 100644 --- a/test/integration/consul-container/test/gateways/http_route_test.go +++ b/test/integration/consul-container/test/gateways/http_route_test.go @@ -8,6 +8,8 @@ import ( "crypto/rand" "encoding/hex" "fmt" + "github.com/testcontainers/testcontainers-go" + "k8s.io/utils/pointer" "testing" "time" @@ -737,3 +739,233 @@ func TestHTTPRouteParentRefChange(t *testing.T) { "Host": "test.foo", }, "") } + +func TestHTTPRouteRetryAndTimeout(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + // infrastructure set up + listenerPort := 6018 + retryServiceHTTPPort := 6019 + retryServiceGRPCPort := 6020 + timeoutServiceHTTPPort := 6021 + timeoutServiceGRPCPort := 6022 + + retryServiceName := randomName("service", 16) + gatewayName := randomName("gw", 16) + retryRouteName := randomName("route", 16) + timeoutServiceName := randomName("service", 16) + timeoutRouteName := randomName("route", 16) + retryPath := "/retry" + timeoutPath := "/timeout" + + clusterConfig := &libtopology.ClusterConfig{ + NumServers: 1, + NumClients: 1, + BuildOpts: &libcluster.BuildOptions{ + Datacenter: "dc1", + InjectAutoEncryption: true, + InjectGossipEncryption: true, + AllowHTTPAnyway: true, + }, + ExposedPorts: []int{ + listenerPort, + retryServiceGRPCPort, + retryServiceHTTPPort, + timeoutServiceGRPCPort, + timeoutServiceHTTPPort, + }, + ApplyDefaultProxySettings: true, + } + + cluster, _, _ := libtopology.NewCluster(t, clusterConfig) + client := cluster.Agents[0].GetClient() + + namespace := getOrCreateNamespace(t, client) + + _, _, err := libservice.CreateAndRegisterCustomServiceAndSidecar( + cluster.Agents[0], &libservice.ServiceOpts{ + ID: retryServiceName, + Name: retryServiceName, + Namespace: namespace, + HTTPPort: retryServiceHTTPPort, + GRPCPort: retryServiceGRPCPort, + }, + testcontainers.ContainerRequest{ + Image: libservice.HashicorpDockerProxy + "/nicholasjackson/fake-service:v0.26.0", + Env: map[string]string{ + "LISTEN_ADDR": fmt.Sprintf("0.0.0.0:%d", retryServiceHTTPPort), + "ERROR_RATE": "0.5", + }, + }, + nil, + ) + require.NoError(t, err) + + _, _, err = libservice.CreateAndRegisterCustomServiceAndSidecar( + cluster.Agents[0], &libservice.ServiceOpts{ + ID: timeoutServiceName, + Name: timeoutServiceName, + Namespace: namespace, + HTTPPort: timeoutServiceHTTPPort, + GRPCPort: timeoutServiceGRPCPort, + }, + testcontainers.ContainerRequest{ + Image: libservice.HashicorpDockerProxy + "/nicholasjackson/fake-service:v0.26.0", + Env: map[string]string{ + "LISTEN_ADDR": fmt.Sprintf("0.0.0.0:%d", timeoutServiceHTTPPort), + "ERROR_RATE": "1.0", + "ERROR_DELAY": "1m", + }, + }, + nil, + ) + + require.NoError(t, err) + + // write config entries + proxyDefaults := &api.ProxyConfigEntry{ + Kind: api.ProxyDefaults, + Name: api.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(proxyDefaults)) + + apiGateway := &api.APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: gatewayName, + Listeners: []api.APIGatewayListener{ + { + Name: "listener", + Port: listenerPort, + Protocol: "http", + }, + }, + Namespace: namespace, + } + + retryRoute := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: retryRouteName, + Namespace: namespace, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + "test.example", + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + RetryFilter: &api.RetryFilter{ + NumRetries: pointer.Uint32(10), + RetryOnStatusCodes: []uint32{500}, + }, + }, + Services: []api.HTTPService{ + { + Name: retryServiceName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: retryPath, + }, + }, + }, + }, + }, + } + + timeoutRoute := &api.HTTPRouteConfigEntry{ + Kind: api.HTTPRoute, + Name: timeoutRouteName, + Namespace: namespace, + Parents: []api.ResourceReference{ + { + Kind: api.APIGateway, + Name: gatewayName, + Namespace: namespace, + }, + }, + Hostnames: []string{ + "test.foo", + "test.example", + }, + Rules: []api.HTTPRouteRule{ + { + Filters: api.HTTPFilters{ + TimeoutFilter: &api.TimeoutFilter{ + RequestTimeout: 1, + IdleTimeout: 1, + }, + }, + Services: []api.HTTPService{ + { + Name: timeoutServiceName, + Namespace: namespace, + }, + }, + Matches: []api.HTTPMatch{ + { + Path: api.HTTPPathMatch{ + Match: api.HTTPPathMatchPrefix, + Value: timeoutPath, + }, + }, + }, + }, + }, + } + + require.NoError(t, cluster.ConfigEntryWrite(apiGateway)) + require.NoError(t, cluster.ConfigEntryWrite(timeoutRoute)) + require.NoError(t, cluster.ConfigEntryWrite(retryRoute)) + + // create gateway service + gwCfg := libservice.GatewayConfig{ + Name: gatewayName, + Kind: "api", + Namespace: namespace, + } + gatewayService, err := libservice.NewGatewayService(context.Background(), gwCfg, cluster.Agents[0], listenerPort) + require.NoError(t, err) + libassert.CatalogServiceExists(t, client, gatewayName, &api.QueryOptions{Namespace: namespace}) + + // make sure config entries have been properly created + checkGatewayConfigEntry(t, client, gatewayName, &api.QueryOptions{Namespace: namespace}) + t.Log("checking retry route") + checkHTTPRouteConfigEntry(t, client, retryRouteName, &api.QueryOptions{Namespace: namespace}) + + t.Log("checking timeout route") + checkHTTPRouteConfigEntry(t, client, timeoutRouteName, &api.QueryOptions{Namespace: namespace}) + + // gateway resolves routes + gatewayPort, err := gatewayService.GetPort(listenerPort) + require.NoError(t, err) + fmt.Println("Gateway Port: ", gatewayPort) + + // hit service 1 by hitting root path + checkRoute(t, gatewayPort, retryPath, map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: 200, testName: "retry should succeed cleanly", expectedBody: "Hello World"}) + + checkRoute(t, gatewayPort, timeoutPath, map[string]string{ + "Host": "test.foo", + }, checkOptions{debug: false, statusCode: 500, testName: "timeout should timeout", expectedBody: "timeout"}) + +}