diff --git a/.changelog/8458.txt b/.changelog/8458.txt new file mode 100644 index 000000000..e9fc0e4e9 --- /dev/null +++ b/.changelog/8458.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: Add support for http2 and grpc to ingress gateways +``` diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index 27a025d52..a5557dbaf 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -38,7 +38,7 @@ type IngressListener struct { // Protocol declares what type of traffic this listener is expected to // receive. Depending on the protocol, a listener might support multiplexing // services over a single port, or additional discovery chain features. The - // current supported values are: (tcp | http). + // current supported values are: (tcp | http | http2 | grpc). Protocol string // Services declares the set of services to which the listener forwards @@ -122,8 +122,10 @@ func (e *IngressGatewayConfigEntry) Normalize() error { func (e *IngressGatewayConfigEntry) Validate() error { validProtocols := map[string]bool{ - "http": true, - "tcp": true, + "tcp": true, + "http": true, + "http2": true, + "grpc": true, } declaredPorts := make(map[int]bool) @@ -134,7 +136,7 @@ func (e *IngressGatewayConfigEntry) Validate() error { declaredPorts[listener.Port] = true if _, ok := validProtocols[listener.Protocol]; !ok { - return fmt.Errorf("Protocol must be either 'http' or 'tcp', '%s' is an unsupported protocol.", listener.Protocol) + return fmt.Errorf("protocol must be 'tcp', 'http', 'http2', or 'grpc'. '%s' is an unsupported protocol", listener.Protocol) } if len(listener.Services) == 0 { diff --git a/agent/structs/config_entry_gateways_test.go b/agent/structs/config_entry_gateways_test.go index cf341f4b3..3a71dba6d 100644 --- a/agent/structs/config_entry_gateways_test.go +++ b/agent/structs/config_entry_gateways_test.go @@ -326,7 +326,7 @@ func TestIngressConfigEntry_Validate(t *testing.T) { }, }, }, - expectErr: "Protocol must be either 'http' or 'tcp', 'asdf' is an unsupported protocol.", + expectErr: "protocol must be 'tcp', 'http', 'http2', or 'grpc'. 'asdf' is an unsupported protocol", }, { name: "hosts cannot be set on a tcp listener", diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 13a5ec707..9d3ee0a6a 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -44,7 +44,7 @@ type IngressListener struct { // Protocol declares what type of traffic this listener is expected to // receive. Depending on the protocol, a listener might support multiplexing // services over a single port, or additional discovery chain features. The - // current supported values are: (tcp | http). + // current supported values are: (tcp | http | http2 | grpc). Protocol string // Services declares the set of services to which the listener forwards diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/capture.sh b/test/integration/connect/envoy/case-ingress-gateway-grpc/capture.sh new file mode 100644 index 000000000..564b963a1 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/capture.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 ingress-gateway primary || true diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-grpc/config_entries.hcl new file mode 100644 index 000000000..fc4cfc697 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/config_entries.hcl @@ -0,0 +1,26 @@ +enable_central_service_config = true + +config_entries { + bootstrap { + kind = "service-defaults" + name = "s1" + protocol = "grpc" + } + bootstrap { + kind = "ingress-gateway" + name = "ingress-gateway" + + listeners = [ + { + port = 9999 + protocol = "grpc" + services = [ + { + name = "s1" + hosts = ["localhost:9999"] + } + ] + } + ] + } +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/gateway.hcl b/test/integration/connect/envoy/case-ingress-gateway-grpc/gateway.hcl new file mode 100644 index 000000000..781ef1851 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/gateway.hcl @@ -0,0 +1,4 @@ +services { + name = "ingress-gateway" + kind = "ingress-gateway" +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/s1.hcl b/test/integration/connect/envoy/case-ingress-gateway-grpc/s1.hcl new file mode 100644 index 000000000..3a3a9f815 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/s1.hcl @@ -0,0 +1,13 @@ +services { + name = "s1" + port = 8079 + connect { + sidecar_service { + proxy { + config { + protocol = "grpc" + } + } + } + } +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/setup.sh b/test/integration/connect/envoy/case-ingress-gateway-grpc/setup.sh new file mode 100644 index 000000000..7aa601897 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/setup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +# wait for bootstrap to apply config entries +wait_for_config_entry ingress-gateway ingress-gateway + +gen_envoy_bootstrap ingress-gateway 20000 primary true +gen_envoy_bootstrap s1 19000 +gen_envoy_bootstrap s2 19001 diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/vars.sh b/test/integration/connect/envoy/case-ingress-gateway-grpc/vars.sh new file mode 100644 index 000000000..c97ad2ea5 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/vars.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +export REQUIRED_SERVICES="$DEFAULT_REQUIRED_SERVICES ingress-gateway-primary" diff --git a/test/integration/connect/envoy/case-ingress-gateway-grpc/verify.bats b/test/integration/connect/envoy/case-ingress-gateway-grpc/verify.bats new file mode 100644 index 000000000..56223fa2a --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-grpc/verify.bats @@ -0,0 +1,32 @@ +#!/usr/bin/env bats + +load helpers + +@test "ingress proxy admin is up on :20000" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "s1 proxy admin is up on :19000" { + retry_default curl -f -s localhost:19000/stats -o /dev/null +} + +@test "s2 proxy admin is up on :19001" { + retry_default curl -f -s localhost:19001/stats -o /dev/null +} + +@test "s1 proxy listener should be up and have right cert" { + assert_proxy_presents_cert_uri localhost:21000 s1 +} + +@test "ingress-gateway should have healthy endpoints for s1" { + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 +} + +@test "ingress should be able to connect to s1 via grpc" { + # This test also covers http2 since gRPC always uses http2 + run fortio grpcping localhost:9999 + + echo "OUTPUT: $output" + + [ "$status" == 0 ] +} diff --git a/test/integration/connect/envoy/main_test.go b/test/integration/connect/envoy/main_test.go index bb483db54..d7cdf2fb2 100644 --- a/test/integration/connect/envoy/main_test.go +++ b/test/integration/connect/envoy/main_test.go @@ -33,6 +33,7 @@ func TestEnvoy(t *testing.T) { "case-http", "case-http-badauthz", "case-ingress-gateway-http", + "case-ingress-gateway-grpc", "case-ingress-gateway-multiple-services", "case-ingress-gateway-simple", "case-ingress-gateway-tls", diff --git a/website/pages/docs/agent/config-entries/ingress-gateway.mdx b/website/pages/docs/agent/config-entries/ingress-gateway.mdx index eca70a017..01d1fbc39 100644 --- a/website/pages/docs/agent/config-entries/ingress-gateway.mdx +++ b/website/pages/docs/agent/config-entries/ingress-gateway.mdx @@ -343,7 +343,7 @@ Also make two services in the frontend namespace available over a custom port wi - `Port` `(int: 0)` - The port that the listener should receive traffic on. - `Protocol` `(string: "tcp")` - The protocol associated with the listener. - Either `tcp` or `http`. + One of `tcp`, `http`, `http2`, or `grpc`. - `Services` `(array: )` - A list of services to be exposed via this listener. For "tcp" listeners, only a single service is