From 632d5c1291bc0775d6280a58536ca9902676541f Mon Sep 17 00:00:00 2001 From: Xin Rong Date: Thu, 17 Nov 2022 21:12:46 +0800 Subject: [PATCH] feat: support HTTPRequestMirrorFilter in HTTPRoute (#1461) --- .../gateway/translation/gateway_httproute.go | 32 ++++++- pkg/types/apisix/v1/plugin_types.go | 6 ++ pkg/types/apisix/v1/zz_generated.deepcopy.go | 16 ++++ test/e2e/suite-gateway/gateway_httproute.go | 93 +++++++++++++++++-- 4 files changed, 135 insertions(+), 12 deletions(-) diff --git a/pkg/providers/gateway/translation/gateway_httproute.go b/pkg/providers/gateway/translation/gateway_httproute.go index 4b2d0eb204..cc2dd42a99 100644 --- a/pkg/providers/gateway/translation/gateway_httproute.go +++ b/pkg/providers/gateway/translation/gateway_httproute.go @@ -32,7 +32,7 @@ import ( apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1" ) -func (t *translator) generatePluginsFromHTTPRouteFilter(filters []gatewayv1beta1.HTTPRouteFilter) apisixv1.Plugins { +func (t *translator) generatePluginsFromHTTPRouteFilter(namespace string, filters []gatewayv1beta1.HTTPRouteFilter) apisixv1.Plugins { plugins := apisixv1.Plugins{} for _, filter := range filters { switch filter.Type { @@ -41,7 +41,9 @@ func (t *translator) generatePluginsFromHTTPRouteFilter(filters []gatewayv1beta1 case gatewayv1beta1.HTTPRouteFilterRequestRedirect: t.generatePluginFromHTTPRequestRedirectFilter(plugins, filter.RequestRedirect) case gatewayv1beta1.HTTPRouteFilterRequestMirror: - // to do + t.generatePluginFromHTTPRequestMirrorFilter(namespace, plugins, filter.RequestMirror) + case gatewayv1beta1.HTTPRouteFilterURLRewrite: + // TODO: It is not yet supported by v1beta1 CRDs. } } return plugins @@ -68,6 +70,30 @@ func (t *translator) generatePluginFromHTTPRequestHeaderFilter(plugins apisixv1. } } +func (t *translator) generatePluginFromHTTPRequestMirrorFilter(namespace string, plugins apisixv1.Plugins, reqMirror *gatewayv1beta1.HTTPRequestMirrorFilter) { + if reqMirror == nil { + return + } + + var ( + port int = 80 + ns string = namespace + ) + if reqMirror.BackendRef.Port != nil { + port = int(*reqMirror.BackendRef.Port) + } + if reqMirror.BackendRef.Namespace != nil { + ns = string(*reqMirror.BackendRef.Namespace) + } + // TODO 1: Need to support https. + // TODO 2: https://github.com/apache/apisix/issues/8351 APISIX 3.0 support {service.namespace} and {service.namespace.svc}, but APISIX <= 2.15 version is not supported. + host := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", reqMirror.BackendRef.Name, ns, port) + + plugins["proxy-mirror"] = apisixv1.RequestMirror{ + Host: host, + } +} + func (t *translator) generatePluginFromHTTPRequestRedirectFilter(plugins apisixv1.Plugins, reqRedirect *gatewayv1beta1.HTTPRequestRedirectFilter) { if reqRedirect == nil { return @@ -210,7 +236,7 @@ func (t *translator) TranslateGatewayHTTPRouteV1beta1(httpRoute *gatewayv1beta1. }, } } - plugins := t.generatePluginsFromHTTPRouteFilter(rule.Filters) + plugins := t.generatePluginsFromHTTPRouteFilter(httpRoute.Namespace, rule.Filters) for j, match := range matches { route, err := t.translateGatewayHTTPRouteMatch(&match) diff --git a/pkg/types/apisix/v1/plugin_types.go b/pkg/types/apisix/v1/plugin_types.go index 8958d28501..37e74b0bc8 100644 --- a/pkg/types/apisix/v1/plugin_types.go +++ b/pkg/types/apisix/v1/plugin_types.go @@ -156,6 +156,12 @@ type BasicAuthConfig struct { type KeyAuthConfig struct { } +// RequestMirror is the rule config for proxy-mirror plugin. +// +k8s:deepcopy-gen=true +type RequestMirror struct { + Host string `json:"host"` +} + type Headers map[string]any func (p *Headers) DeepCopyInto(out *Headers) { diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go b/pkg/types/apisix/v1/zz_generated.deepcopy.go index 205436eb3f..f09f3097ee 100644 --- a/pkg/types/apisix/v1/zz_generated.deepcopy.go +++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go @@ -340,6 +340,22 @@ func (in *RedirectConfig) DeepCopy() *RedirectConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestMirror) DeepCopyInto(out *RequestMirror) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestMirror. +func (in *RequestMirror) DeepCopy() *RequestMirror { + if in == nil { + return nil + } + out := new(RequestMirror) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RewriteConfig) DeepCopyInto(out *RewriteConfig) { *out = *in diff --git a/test/e2e/suite-gateway/gateway_httproute.go b/test/e2e/suite-gateway/gateway_httproute.go index bc80940754..29d273994b 100644 --- a/test/e2e/suite-gateway/gateway_httproute.go +++ b/test/e2e/suite-gateway/gateway_httproute.go @@ -34,7 +34,7 @@ var _ = ginkgo.Describe("suite-gateway: HTTPRoute", func() { backendSvc, backendPorts := s.DefaultHTTPBackend() time.Sleep(time.Second * 15) route := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -69,7 +69,7 @@ spec: backendSvc, backendPorts := s.DefaultHTTPBackend() time.Sleep(time.Second * 15) route := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -107,7 +107,7 @@ spec: ginkgo.It("Basic HTTPRoute with 1 Hosts 1 Rule 2 Match 1 BackendRef", func() { backendSvc, backendPorts := s.DefaultHTTPBackend() route := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -148,7 +148,7 @@ spec: ginkgo.It("Update HTTPRoute", func() { backendSvc, backendPorts := s.DefaultHTTPBackend() route := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -169,7 +169,7 @@ spec: assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams") route = fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -202,7 +202,7 @@ spec: ginkgo.It("Delete HTTPRoute", func() { backendSvc, backendPorts := s.DefaultHTTPBackend() route := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: basic-http-route @@ -243,7 +243,7 @@ var _ = ginkgo.Describe("suite-gateway: HTTPRoute with filter", func() { backendSvc, backendPorts := s.DefaultHTTPBackend() time.Sleep(time.Second * 15) httproute := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: http-route @@ -295,7 +295,7 @@ spec: backendSvc, backendPorts := s.DefaultHTTPBackend() httproute := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: http-route @@ -327,7 +327,7 @@ spec: Header("Location").Equal("https://httpbin.org:9443/headers") httproute2 := fmt.Sprintf(` -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 kind: HTTPRoute metadata: name: http-route2 @@ -358,4 +358,79 @@ spec: Status(http.StatusMovedPermanently). Header("Location").Equal("http://httpbin.org/ip") }) + + ginkgo.It("HTTPRoute with RequestMirror", func() { + backendSvc, backendPorts := s.DefaultHTTPBackend() + + echo := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: echo +spec: + selector: + matchLabels: + app: echo + replicas: 1 + template: + metadata: + labels: + app: echo + spec: + containers: + - name: echo + image: localhost:5000/jmalloc/echo-server:dev + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: echo-service +spec: + selector: + app: echo + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 8080 +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(echo), "creating echo server") + + httproute := fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + hostnames: ["httpbin.org"] + rules: + - matches: + - path: + type: PathPrefix + value: /headers + filters: + - type: RequestMirror + requestMirror: + backendRef: + name: echo-service + port: 80 + backendRefs: + - name: %s + port: %d +`, backendSvc, backendPorts[0]) + + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(httproute), "creating HTTPRoute") + time.Sleep(time.Second * 6) + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1), "Checking number of routes") + + _ = s.NewAPISIXClient().GET("/headers"). + WithHeader("Host", "httpbin.org"). + Expect(). + Status(http.StatusOK) + + echoLogs := s.GetDeploymentLogs("echo") + assert.Contains(ginkgo.GinkgoT(), echoLogs, "GET /headers") + }) })