diff --git a/api/adc/types.go b/api/adc/types.go index 124094c5..472a12a1 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -448,18 +448,28 @@ func (n *UpstreamNodes) UnmarshalJSON(p []byte) error { return nil } -// MarshalJSON implements the json.Marshaler interface for UpstreamNodes. -// By default, Go serializes a nil slice as JSON null. However, for compatibility -// with APISIX semantics, we want a nil UpstreamNodes to be encoded as an empty -// array ([]) instead of null. Non-nil slices are marshaled as usual. -// -// See APISIX upstream nodes schema definition for details: -// https://github.com/apache/apisix/blob/77dacda31277a31d6014b4970e36bae2a5c30907/apisix/schema_def.lua#L295-L338 -func (n UpstreamNodes) MarshalJSON() ([]byte, error) { - if n == nil { - return []byte("[]"), nil +func (n Upstream) MarshalJSON() ([]byte, error) { + type Alias Upstream + // APISIX does not allow discovery_type and nodes to exist at the same time. + // https://github.com/apache/apisix/blob/01b4b49eb2ba642b337f7a1fbe1894a77942910b/apisix/schema_def.lua#L501-L504 + if n.DiscoveryType != "" { + aux := struct { + Alias + Nodes UpstreamNodes `json:"nodes,omitempty" yaml:"nodes,omitempty"` + }{ + Alias: (Alias)(n), + } + aux.Nodes = nil + return json.Marshal(&aux) + } + + // By default Go serializes a nil slice as JSON null. + // For APISIX compatibility, nil UpstreamNodes should be encoded as [] instead. + // https://github.com/apache/apisix/blob/77dacda31277a31d6014b4970e36bae2a5c30907/apisix/schema_def.lua#L295-L338 + if n.Nodes == nil { + n.Nodes = UpstreamNodes{} } - return json.Marshal([]UpstreamNode(n)) + return json.Marshal((Alias)(n)) } // ComposeRouteName uses namespace, name and rule name to compose @@ -571,8 +581,7 @@ func NewDefaultUpstream() *Upstream { "managed-by": "apisix-ingress-controller", }, }, - Nodes: make(UpstreamNodes, 0), - Type: Roundrobin, + Type: Roundrobin, } } diff --git a/api/v2/apisixupstream_types.go b/api/v2/apisixupstream_types.go index a1562618..73a925be 100644 --- a/api/v2/apisixupstream_types.go +++ b/api/v2/apisixupstream_types.go @@ -150,7 +150,6 @@ type ApisixUpstreamConfig struct { UpstreamHost string `json:"upstreamHost,omitempty" yaml:"upstreamHost,omitempty"` // Discovery configures service discovery for the upstream. - // Deprecated: no longer supported in standalone mode. // +kubebuilder:validation:Optional Discovery *Discovery `json:"discovery,omitempty" yaml:"discovery,omitempty"` } diff --git a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml index 2dcb2b63..fdded278 100644 --- a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml +++ b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml @@ -42,9 +42,7 @@ spec: description: ApisixUpstreamSpec defines the upstream configuration. properties: discovery: - description: |- - Discovery configures service discovery for the upstream. - Deprecated: no longer supported in standalone mode. + description: Discovery configures service discovery for the upstream. properties: args: additionalProperties: @@ -337,9 +335,8 @@ spec: them if they are set on the port level. properties: discovery: - description: |- - Discovery configures service discovery for the upstream. - Deprecated: no longer supported in standalone mode. + description: Discovery configures service discovery for the + upstream. properties: args: additionalProperties: diff --git a/docs/en/latest/reference/api-reference.md b/docs/en/latest/reference/api-reference.md index 06203d6f..7bfeb239 100644 --- a/docs/en/latest/reference/api-reference.md +++ b/docs/en/latest/reference/api-reference.md @@ -1325,7 +1325,7 @@ ApisixUpstreamConfig defines configuration for upstream services. | `subsets` _[ApisixUpstreamSubset](#apisixupstreamsubset) array_ | Subsets defines labeled subsets of service endpoints, typically used for service versioning or canary deployments. | | `passHost` _string_ | PassHost configures how the host header should be determined when a request is forwarded to the upstream. Default is `pass`. Can be `pass`, `node` or `rewrite`:
• `pass`: preserve the original Host header
• `node`: use the upstream node’s host
• `rewrite`: set to a custom host via upstreamHost | | `upstreamHost` _string_ | UpstreamHost sets a custom Host header when passHost is set to `rewrite`. | -| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. Deprecated: no longer supported in standalone mode. | +| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. | _Appears in:_ @@ -1385,7 +1385,7 @@ definitions and custom configuration. | `subsets` _[ApisixUpstreamSubset](#apisixupstreamsubset) array_ | Subsets defines labeled subsets of service endpoints, typically used for service versioning or canary deployments. | | `passHost` _string_ | PassHost configures how the host header should be determined when a request is forwarded to the upstream. Default is `pass`. Can be `pass`, `node` or `rewrite`:
• `pass`: preserve the original Host header
• `node`: use the upstream node’s host
• `rewrite`: set to a custom host via upstreamHost | | `upstreamHost` _string_ | UpstreamHost sets a custom Host header when passHost is set to `rewrite`. | -| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. Deprecated: no longer supported in standalone mode. | +| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. | | `portLevelSettings` _[PortLevelSettings](#portlevelsettings) array_ | PortLevelSettings allows fine-grained upstream configuration for specific ports, useful when a backend service exposes multiple ports with different behaviors or protocols. | @@ -1555,7 +1555,7 @@ them if they are set on the port level. | `subsets` _[ApisixUpstreamSubset](#apisixupstreamsubset) array_ | Subsets defines labeled subsets of service endpoints, typically used for service versioning or canary deployments. | | `passHost` _string_ | PassHost configures how the host header should be determined when a request is forwarded to the upstream. Default is `pass`. Can be `pass`, `node` or `rewrite`:
• `pass`: preserve the original Host header
• `node`: use the upstream node’s host
• `rewrite`: set to a custom host via upstreamHost | | `upstreamHost` _string_ | UpstreamHost sets a custom Host header when passHost is set to `rewrite`. | -| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. Deprecated: no longer supported in standalone mode. | +| `discovery` _[Discovery](#discovery)_ | Discovery configures service discovery for the upstream. | | `port` _integer_ | Port is a Kubernetes Service port. | diff --git a/docs/en/latest/upgrade-guide.md b/docs/en/latest/upgrade-guide.md index a8b3a6d2..7d738edf 100644 --- a/docs/en/latest/upgrade-guide.md +++ b/docs/en/latest/upgrade-guide.md @@ -138,14 +138,6 @@ spec: ### API Changes -#### `ApisixUpstream` - -Due to current limitations in the [ADC](https://github.com/api7/adc) component, the following fields are not yet supported: - -* `spec.discovery`: Service Discovery - -More details: [ADC Backend Differences](https://github.com/api7/adc/blob/2449ca81e3c61169f8c1e59efb4c1173a766bce2/libs/backend-apisix-standalone/README.md#differences-in-upstream) - #### `ApisixClusterConfig` The `ApisixClusterConfig` CRD has been removed in 2.0.0. global rules and configurations should now be managed through the `ApisixGlobalRule` CRDs. diff --git a/internal/adc/translator/apisixroute.go b/internal/adc/translator/apisixroute.go index 87cbe3ee..505ba35f 100644 --- a/internal/adc/translator/apisixroute.go +++ b/internal/adc/translator/apisixroute.go @@ -271,7 +271,7 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc } // no valid upstream - if len(upstreams) == 0 || len(upstreams[0].Nodes) == 0 { + if len(upstreams) == 0 { return } diff --git a/internal/adc/translator/apisixupstream.go b/internal/adc/translator/apisixupstream.go index 5a76025e..b56791dc 100644 --- a/internal/adc/translator/apisixupstream.go +++ b/internal/adc/translator/apisixupstream.go @@ -21,7 +21,9 @@ import ( "cmp" "fmt" + "github.com/api7/gopkg/pkg/log" "github.com/pkg/errors" + "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -40,6 +42,7 @@ func (t *Translator) translateApisixUpstream(tctx *provider.TranslateContext, au translateApisixUpstreamRetriesAndTimeout, translateApisixUpstreamPassHost, translateUpstreamHealthCheck, + translateUpstreamDiscovery, } { if err = f(au, ups); err != nil { return @@ -54,6 +57,8 @@ func (t *Translator) translateApisixUpstream(tctx *provider.TranslateContext, au } } + log.Debugw("translated ApisixUpstream", zap.Any("upstream", ups), + zap.String("namespace", au.Namespace), zap.String("name", au.Name)) return } @@ -340,3 +345,14 @@ func translateUpstreamPassiveHealthCheck(config *apiv2.PassiveHealthCheck) *adc. } return &passive } + +func translateUpstreamDiscovery(au *apiv2.ApisixUpstream, ups *adc.Upstream) error { + discovery := au.Spec.Discovery + if discovery == nil { + return nil + } + ups.ServiceName = discovery.ServiceName + ups.DiscoveryType = discovery.Type + ups.DiscoveryArgs = discovery.Args + return nil +} diff --git a/test/e2e/crds/v2/upstream.go b/test/e2e/crds/v2/upstream.go index 7d7d9a61..62392344 100644 --- a/test/e2e/crds/v2/upstream.go +++ b/test/e2e/crds/v2/upstream.go @@ -135,4 +135,54 @@ spec: } }) }) + + Context("external service discovery", func() { + ar := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: httpbin-route +spec: + ingressClassName: %s + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /* + upstreams: + - name: httpbin-dns +` + + au := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixUpstream +metadata: + name: httpbin-dns +spec: + ingressClassName: %s + discovery: + type: dns + serviceName: %s +` + + It("should be able to access through service discovery", func() { + if framework.ProviderType == framework.ProviderTypeAPI7EE { + Skip("api7ee does not support DNS service discovery") + } + svcName := fmt.Sprintf("httpbin-service-e2e-test.%s.svc.cluster.local", s.Namespace()) + applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "httpbin-dns"}, + &apiv2.ApisixUpstream{}, fmt.Sprintf(au, s.Namespace(), svcName)) + applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "httpbin-route"}, + &apiv2.ApisixRoute{}, fmt.Sprintf(ar, s.Namespace())) + + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/ip", + Host: "httpbin.org", + Check: scaffold.WithExpectedStatus(200), + }) + }) + }) }) diff --git a/test/e2e/framework/manifests/apisix-standalone.yaml b/test/e2e/framework/manifests/apisix-standalone.yaml index 8c0a91fd..4b7adfe9 100644 --- a/test/e2e/framework/manifests/apisix-standalone.yaml +++ b/test/e2e/framework/manifests/apisix-standalone.yaml @@ -42,6 +42,11 @@ data: - 9100 udp: # UDP proxy port list - 9200 + discovery: + dns: + servers: + - "10.96.0.10:53" # use the real address of your dns server. + # currently we use KIND as the standard test environment, so here we can hard-code the default DNS address first. --- apiVersion: apps/v1 kind: Deployment diff --git a/test/e2e/framework/manifests/apisix.yaml b/test/e2e/framework/manifests/apisix.yaml index 88b83637..ae8a1396 100644 --- a/test/e2e/framework/manifests/apisix.yaml +++ b/test/e2e/framework/manifests/apisix.yaml @@ -49,6 +49,11 @@ data: - 9100 udp: # UDP proxy port list - 9200 + discovery: + dns: + servers: + - "10.96.0.10:53" # use the real address of your dns server. + # currently we use KIND as the standard test environment, so here we can hard-code the default DNS address first. --- apiVersion: apps/v1 kind: Deployment diff --git a/test/e2e/scaffold/httpbin.go b/test/e2e/scaffold/httpbin.go index 884e5ac3..03a81007 100644 --- a/test/e2e/scaffold/httpbin.go +++ b/test/e2e/scaffold/httpbin.go @@ -28,6 +28,8 @@ import ( ) var ( + HTTPBinServiceName = "httpbin-service-e2e-test" + _httpbinDeploymentTemplate = ` apiVersion: apps/v1 kind: Deployment