This is an automated email from the ASF dual-hosted git repository.
zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new 7b3deb5e feat: add support for host pass in upstream crd (#1889)
7b3deb5e is described below
commit 7b3deb5e84d257f46ce5c513d72b22a24af1be67
Author: ikatlinsky <[email protected]>
AuthorDate: Sat Aug 26 16:23:33 2023 +0200
feat: add support for host pass in upstream crd (#1889)
Co-authored-by: Katlinsky, Ilya <[email protected]>
---
docs/en/latest/references/apisix_upstream.md | 2 +
pkg/kube/apisix/apis/config/v2/types.go | 10 ++
pkg/providers/translation/apisix_upstream.go | 21 +++
pkg/providers/translation/apisix_upstream_test.go | 47 +++++++
pkg/types/apisix/v1/types.go | 87 ++++++++-----
samples/deploy/crd/v1/ApisixUpstream.yaml | 9 ++
test/e2e/suite-features/upstream_pass_host.go | 151 ++++++++++++++++++++++
7 files changed, 292 insertions(+), 35 deletions(-)
diff --git a/docs/en/latest/references/apisix_upstream.md
b/docs/en/latest/references/apisix_upstream.md
index e9544298..3eefea27 100644
--- a/docs/en/latest/references/apisix_upstream.md
+++ b/docs/en/latest/references/apisix_upstream.md
@@ -83,3 +83,5 @@ See the
[definition](https://github.com/apache/apisix-ingress-controller/blob/ma
| discovery.serviceName | string | Name of the
upstream service.
|
| discovery.type | string | Types of
Service Discovery, which indicates what registry in APISIX the discovery uses.
Should match the entry in APISIX's config. Can refer to the
[doc](https://apisix.apache.org/docs/apisix/discovery/)
|
| discovery.args | object | Args map
for discovery-spcefic parameters. Also can refer to the
[doc](https://apisix.apache.org/docs/apisix/discovery/)
|
+| passHost | string | Configures
the host when the request is forwarded to the upstream. Can be one of pass,
node or rewrite. Defaults to pass if not specified: pass - transparently passes
the client's host to the Upstream, node - uses the host configured in the node
of the Upstream, rewrite - uses the value configured in upstreamHost.
+| upstreamHost | string | Specifies
the host of the Upstream request. This is only valid if the passHost is set to
rewrite.
diff --git a/pkg/kube/apisix/apis/config/v2/types.go
b/pkg/kube/apisix/apis/config/v2/types.go
index 59569cae..3e2daf88 100644
--- a/pkg/kube/apisix/apis/config/v2/types.go
+++ b/pkg/kube/apisix/apis/config/v2/types.go
@@ -543,6 +543,16 @@ type ApisixUpstreamConfig struct {
// +optional
Subsets []ApisixUpstreamSubset `json:"subsets,omitempty"
yaml:"subsets,omitempty"`
+ // Configures the host when the request is forwarded to the upstream.
+ // Can be one of pass, node or rewrite.
+ // +optional
+ PassHost string `json:"passHost,omitempty" yaml:"passHost,omitempty"`
+
+ // Specifies the host of the Upstream request. This is only valid if
+ // the pass_host is set to rewrite
+ // +optional
+ UpstreamHost string `json:"upstreamHost,omitempty"
yaml:"upstreamHost,omitempty"`
+
// Discovery is used to configure service discovery for upstream.
// +optional
Discovery *Discovery `json:"discovery,omitempty"
yaml:"discovery,omitempty"`
diff --git a/pkg/providers/translation/apisix_upstream.go
b/pkg/providers/translation/apisix_upstream.go
index 162dd984..44aca7ba 100644
--- a/pkg/providers/translation/apisix_upstream.go
+++ b/pkg/providers/translation/apisix_upstream.go
@@ -21,6 +21,11 @@ import (
apisixv1
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
+type passHostConfig struct {
+ passHost string
+ upstreamHost string
+}
+
func (t *translator) TranslateUpstreamConfigV2(au
*configv2.ApisixUpstreamConfig) (*apisixv1.Upstream, error) {
ups := apisixv1.NewDefaultUpstream()
if err := t.translateUpstreamScheme(au.Scheme, ups); err != nil {
@@ -38,6 +43,9 @@ func (t *translator) TranslateUpstreamConfigV2(au
*configv2.ApisixUpstreamConfig
if err := t.translateClientTLSV2(au.TLSSecret, ups); err != nil {
return nil, err
}
+ if err := t.translatePassHost(&passHostConfig{au.PassHost,
au.UpstreamHost}, ups); err != nil {
+ return nil, err
+ }
if err := t.translateUpstreamDiscovery(au.Discovery, ups); err != nil {
return nil, err
}
@@ -368,3 +376,16 @@ func (t *translator)
translateUpstreamPassiveHealthCheckV2(config *configv2.Pass
}
return &passive, nil
}
+
+func (t *translator) translatePassHost(ph *passHostConfig, ups
*apisixv1.Upstream) error {
+ switch ph.passHost {
+ case "", apisixv1.PassHostPass, apisixv1.PassHostNode,
apisixv1.PassHostRewrite:
+ ups.PassHost = ph.passHost
+ default:
+ return &TranslateError{Field: "passHost", Reason: "invalid
value"}
+ }
+
+ ups.UpstreamHost = ph.upstreamHost
+
+ return nil
+}
diff --git a/pkg/providers/translation/apisix_upstream_test.go
b/pkg/providers/translation/apisix_upstream_test.go
index 92d8081c..6723535e 100644
--- a/pkg/providers/translation/apisix_upstream_test.go
+++ b/pkg/providers/translation/apisix_upstream_test.go
@@ -409,3 +409,50 @@ func TestUpstreamRetriesAndTimeoutV2(t *testing.T) {
Read: 15,
}, ups.Timeout)
}
+
+func TestUpstreamPassHost(t *testing.T) {
+ tr := &translator{}
+ tests := []struct {
+ name string
+ phc *passHostConfig
+ wantFunc func(t *testing.T, err error, ups *apisixv1.Upstream,
phc *passHostConfig)
+ }{
+ {
+ name: "should be empty when settings not set
explicitly",
+ phc: &passHostConfig{},
+ wantFunc: func(t *testing.T, err error, ups
*apisixv1.Upstream, phc *passHostConfig) {
+ assert.Nil(t, err)
+ assert.Empty(t, ups.PassHost)
+ assert.Empty(t, ups.UpstreamHost)
+ },
+ },
+ {
+ name: "should set passHost to pass",
+ phc: &passHostConfig{passHost: apisixv1.PassHostPass},
+ wantFunc: func(t *testing.T, err error, ups
*apisixv1.Upstream, phc *passHostConfig) {
+ assert.Nil(t, err)
+ assert.Equal(t, phc.passHost, ups.PassHost)
+ assert.Empty(t, ups.UpstreamHost)
+ },
+ },
+ {
+ name: "should fail when passHost set to invalid value",
+ phc: &passHostConfig{passHost: "unknown"},
+ wantFunc: func(t *testing.T, err error, ups
*apisixv1.Upstream, phc *passHostConfig) {
+ assert.Equal(t, &TranslateError{
+ Field: "passHost",
+ Reason: "invalid value",
+ }, err)
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ups := apisixv1.NewDefaultUpstream()
+ err := tr.translatePassHost(tt.phc, ups)
+
+ tt.wantFunc(t, err, ups, tt.phc)
+ })
+ }
+}
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 04ed3be5..d85c386d 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -79,6 +79,13 @@ const (
// DefaultUpstreamTimeout represents the default connect,
// read and send timeout (in seconds) with upstreams.
DefaultUpstreamTimeout = 60
+
+ // PassHostPass represents pass option for pass_host Upstream settings.
+ PassHostPass = "pass"
+ // PassHostPass represents node option for pass_host Upstream settings.
+ PassHostNode = "node"
+ // PassHostPass represents rewrite option for pass_host Upstream
settings.
+ PassHostRewrite = "rewrite"
)
var ValidSchemes map[string]struct{} = map[string]struct{}{
@@ -196,15 +203,17 @@ func (p *Plugins) DeepCopy() *Plugins {
type Upstream struct {
Metadata `json:",inline" yaml:",inline"`
- Type string `json:"type,omitempty"
yaml:"type,omitempty"`
- HashOn string `json:"hash_on,omitempty"
yaml:"hash_on,omitempty"`
- Key string `json:"key,omitempty" yaml:"key,omitempty"`
- Checks *UpstreamHealthCheck `json:"checks,omitempty"
yaml:"checks,omitempty"`
- Nodes UpstreamNodes `json:"nodes" yaml:"nodes"`
- Scheme string `json:"scheme,omitempty"
yaml:"scheme,omitempty"`
- Retries *int `json:"retries,omitempty"
yaml:"retries,omitempty"`
- Timeout *UpstreamTimeout `json:"timeout,omitempty"
yaml:"timeout,omitempty"`
- TLS *ClientTLS `json:"tls,omitempty" yaml:"tls,omitempty"`
+ Type string `json:"type,omitempty"
yaml:"type,omitempty"`
+ HashOn string `json:"hash_on,omitempty"
yaml:"hash_on,omitempty"`
+ Key string `json:"key,omitempty"
yaml:"key,omitempty"`
+ Checks *UpstreamHealthCheck `json:"checks,omitempty"
yaml:"checks,omitempty"`
+ Nodes UpstreamNodes `json:"nodes" yaml:"nodes"`
+ Scheme string `json:"scheme,omitempty"
yaml:"scheme,omitempty"`
+ Retries *int `json:"retries,omitempty"
yaml:"retries,omitempty"`
+ Timeout *UpstreamTimeout `json:"timeout,omitempty"
yaml:"timeout,omitempty"`
+ TLS *ClientTLS `json:"tls,omitempty"
yaml:"tls,omitempty"`
+ PassHost string `json:"pass_host,omitempty"
yaml:"pass_host,omitempty"`
+ UpstreamHost string `json:"upstream_host,omitempty"
yaml:"upstream_host,omitempty"`
// for Service Discovery
ServiceName string `json:"service_name,omitempty"
yaml:"service_name,omitempty"`
@@ -271,10 +280,12 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
Key string `json:"key,omitempty"
yaml:"key,omitempty"`
Checks *UpstreamHealthCheck `json:"checks,omitempty"
yaml:"checks,omitempty"`
//Nodes UpstreamNodes `json:"nodes"
yaml:"nodes"`
- Scheme string `json:"scheme,omitempty"
yaml:"scheme,omitempty"`
- Retries *int `json:"retries,omitempty"
yaml:"retries,omitempty"`
- Timeout *UpstreamTimeout `json:"timeout,omitempty"
yaml:"timeout,omitempty"`
- TLS *ClientTLS `json:"tls,omitempty"
yaml:"tls,omitempty"`
+ Scheme string `json:"scheme,omitempty"
yaml:"scheme,omitempty"`
+ Retries *int `json:"retries,omitempty"
yaml:"retries,omitempty"`
+ Timeout *UpstreamTimeout `json:"timeout,omitempty"
yaml:"timeout,omitempty"`
+ HostPass string
`json:"pass_host,omitempty" yaml:"pass_host,omitempty"`
+ UpstreamHost string
`json:"upstream_host,omitempty" yaml:"upstream_host,omitempty"`
+ TLS *ClientTLS `json:"tls,omitempty"
yaml:"tls,omitempty"`
// for Service Discovery
ServiceName string
`json:"service_name,omitempty" yaml:"service_name,omitempty"`
@@ -288,10 +299,12 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
Key: up.Key,
Checks: up.Checks,
//Nodes: up.Nodes,
- Scheme: up.Scheme,
- Retries: up.Retries,
- Timeout: up.Timeout,
- TLS: up.TLS,
+ Scheme: up.Scheme,
+ Retries: up.Retries,
+ Timeout: up.Timeout,
+ HostPass: up.PassHost,
+ UpstreamHost: up.UpstreamHost,
+ TLS: up.TLS,
ServiceName: up.ServiceName,
DiscoveryType: up.DiscoveryType,
@@ -301,15 +314,17 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Metadata `json:",inline" yaml:",inline"`
- Type string `json:"type,omitempty"
yaml:"type,omitempty"`
- HashOn string `json:"hash_on,omitempty"
yaml:"hash_on,omitempty"`
- Key string `json:"key,omitempty"
yaml:"key,omitempty"`
- Checks *UpstreamHealthCheck `json:"checks,omitempty"
yaml:"checks,omitempty"`
- Nodes UpstreamNodes `json:"nodes" yaml:"nodes"`
- Scheme string `json:"scheme,omitempty"
yaml:"scheme,omitempty"`
- Retries *int `json:"retries,omitempty"
yaml:"retries,omitempty"`
- Timeout *UpstreamTimeout `json:"timeout,omitempty"
yaml:"timeout,omitempty"`
- TLS *ClientTLS `json:"tls,omitempty"
yaml:"tls,omitempty"`
+ Type string
`json:"type,omitempty" yaml:"type,omitempty"`
+ HashOn string
`json:"hash_on,omitempty" yaml:"hash_on,omitempty"`
+ Key string `json:"key,omitempty"
yaml:"key,omitempty"`
+ Checks *UpstreamHealthCheck
`json:"checks,omitempty" yaml:"checks,omitempty"`
+ Nodes UpstreamNodes `json:"nodes"
yaml:"nodes"`
+ Scheme string
`json:"scheme,omitempty" yaml:"scheme,omitempty"`
+ Retries *int
`json:"retries,omitempty" yaml:"retries,omitempty"`
+ Timeout *UpstreamTimeout
`json:"timeout,omitempty" yaml:"timeout,omitempty"`
+ HostPass string
`json:"pass_host,omitempty" yaml:"pass_host,omitempty"`
+ UpstreamHost string
`json:"upstream_host,omitempty" yaml:"upstream_host,omitempty"`
+ TLS *ClientTLS `json:"tls,omitempty"
yaml:"tls,omitempty"`
// for Service Discovery
//ServiceName string
`json:"service_name,omitempty" yaml:"service_name,omitempty"`
@@ -318,15 +333,17 @@ func (up Upstream) MarshalJSON() ([]byte, error) {
}{
Metadata: up.Metadata,
- Type: up.Type,
- HashOn: up.HashOn,
- Key: up.Key,
- Checks: up.Checks,
- Nodes: up.Nodes,
- Scheme: up.Scheme,
- Retries: up.Retries,
- Timeout: up.Timeout,
- TLS: up.TLS,
+ Type: up.Type,
+ HashOn: up.HashOn,
+ Key: up.Key,
+ Checks: up.Checks,
+ Nodes: up.Nodes,
+ Scheme: up.Scheme,
+ Retries: up.Retries,
+ Timeout: up.Timeout,
+ HostPass: up.PassHost,
+ UpstreamHost: up.UpstreamHost,
+ TLS: up.TLS,
//ServiceName: up.ServiceName,
//DiscoveryType: up.DiscoveryType,
diff --git a/samples/deploy/crd/v1/ApisixUpstream.yaml
b/samples/deploy/crd/v1/ApisixUpstream.yaml
index 3bfcd5ac..ac0b67d6 100644
--- a/samples/deploy/crd/v1/ApisixUpstream.yaml
+++ b/samples/deploy/crd/v1/ApisixUpstream.yaml
@@ -516,6 +516,15 @@ spec:
type: string
send:
type: string
+ passHost:
+ type: string
+ enum:
+ - pass
+ - node
+ - rewrite
+ upstreamHost:
+ type: string
+ pattern: "^\\*?[0-9a-zA-Z-._]+$"
tlsSecret:
description: ApisixSecret describes the Kubernetes Secret
name and
namespace.
diff --git a/test/e2e/suite-features/upstream_pass_host.go
b/test/e2e/suite-features/upstream_pass_host.go
new file mode 100644
index 00000000..37fd1216
--- /dev/null
+++ b/test/e2e/suite-features/upstream_pass_host.go
@@ -0,0 +1,151 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package features
+
+import (
+ "fmt"
+ "time"
+
+ ginkgo "github.com/onsi/ginkgo/v2"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("suite-features: upstream pass host", func() {
+ suites := func(scaffoldFunc func() *scaffold.Scaffold) {
+ s := scaffoldFunc()
+
+ routeTpl := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /*
+ backends:
+ - serviceName: %s
+ servicePort: %d
+`
+
+ ginkgo.It("is set to node", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+ err := s.CreateVersionedApisixResource(ar)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ name: %s
+spec:
+ passHost: node
+`, backendSvc)
+ err = s.CreateVersionedApisixResource(au)
+ assert.Nil(ginkgo.GinkgoT(), err, "create
ApisixUpstream")
+ time.Sleep(2 * time.Second)
+
+ ups, err := s.ListApisixUpstreams()
+ assert.Nil(ginkgo.GinkgoT(), err)
+ assert.Len(ginkgo.GinkgoT(), ups, 1)
+ assert.Equal(ginkgo.GinkgoT(), "node", ups[0].PassHost)
+ })
+
+ ginkgo.It("is set to rewrite with upstream host", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+ err := s.CreateVersionedApisixResource(ar)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ name: %s
+spec:
+ passHost: rewrite
+ upstreamHost: host
+`, backendSvc)
+ err = s.CreateVersionedApisixResource(au)
+ assert.Nil(ginkgo.GinkgoT(), err, "create
ApisixUpstream")
+ time.Sleep(2 * time.Second)
+
+ ups, err := s.ListApisixUpstreams()
+ assert.Nil(ginkgo.GinkgoT(), err)
+ assert.Len(ginkgo.GinkgoT(), ups, 1)
+ assert.Equal(ginkgo.GinkgoT(), "rewrite",
ups[0].PassHost)
+ assert.Equal(ginkgo.GinkgoT(), "host",
ups[0].UpstreamHost)
+ })
+
+ ginkgo.It("is set to node with upstream host", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+ err := s.CreateVersionedApisixResource(ar)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ name: %s
+spec:
+ passHost: node
+ upstreamHost: host
+`, backendSvc)
+ err = s.CreateVersionedApisixResource(au)
+ assert.Nil(ginkgo.GinkgoT(), err, "create
ApisixUpstream")
+ time.Sleep(2 * time.Second)
+
+ ups, err := s.ListApisixUpstreams()
+ assert.Nil(ginkgo.GinkgoT(), err)
+ assert.Len(ginkgo.GinkgoT(), ups, 1)
+ assert.Equal(ginkgo.GinkgoT(), "node", ups[0].PassHost)
+ assert.Equal(ginkgo.GinkgoT(), "host",
ups[0].UpstreamHost)
+ })
+
+ ginkgo.It("is set to invalid value", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(routeTpl, backendSvc, backendPorts[0])
+ err := s.CreateVersionedApisixResource(ar)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ au := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+ name: %s
+spec:
+ passHost: invalid
+`, backendSvc)
+ err = s.CreateVersionedApisixResource(au)
+ assert.NotNil(ginkgo.GinkgoT(), err)
+ })
+ }
+
+ ginkgo.Describe("suite-features: scaffold v2", func() {
+ suites(scaffold.NewDefaultV2Scaffold)
+ })
+})