This is an automated email from the ASF dual-hosted git repository.

alinsran 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 be91920e feat(apisixupstream): support discovery (#2577)
be91920e is described below

commit be91920e3a6a4c2d0cace198284debddab4299ea
Author: AlinsRan <[email protected]>
AuthorDate: Fri Sep 26 20:42:18 2025 +0800

    feat(apisixupstream): support discovery (#2577)
---
 api/adc/types.go                                   | 35 ++++++++++------
 api/v2/apisixupstream_types.go                     |  1 -
 .../bases/apisix.apache.org_apisixupstreams.yaml   |  9 ++---
 docs/en/latest/reference/api-reference.md          |  6 +--
 docs/en/latest/upgrade-guide.md                    |  8 ----
 internal/adc/translator/apisixroute.go             |  2 +-
 internal/adc/translator/apisixupstream.go          | 16 ++++++++
 test/e2e/crds/v2/upstream.go                       | 47 ++++++++++++++++++++++
 .../e2e/framework/manifests/apisix-standalone.yaml |  5 +++
 test/e2e/framework/manifests/apisix.yaml           |  5 +++
 test/e2e/scaffold/httpbin.go                       |  2 +
 11 files changed, 104 insertions(+), 32 deletions(-)

diff --git a/api/adc/types.go b/api/adc/types.go
index b5b67410..6b2bec95 100644
--- a/api/adc/types.go
+++ b/api/adc/types.go
@@ -449,18 +449,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
@@ -572,8 +582,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 aa052e61..72327cb5 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 cae2ab96..45023e7c 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 6a891cb8..8e225bcb 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`:<br /> • `pass`: preserve the original Host 
header<br /> • `node`: use the upstream node’s host<br /> • `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`:<br /> • `pass`: preserve the original Host 
header<br /> • `node`: use the upstream node’s host<br /> • `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`:<br /> • `pass`: preserve the original Host 
header<br /> • `node`: use the upstream node’s host<br /> • `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 068ef6a7..9e017a3f 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 76888dd4..d73f9981 100644
--- a/internal/adc/translator/apisixroute.go
+++ b/internal/adc/translator/apisixroute.go
@@ -269,7 +269,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..674abf51 100644
--- a/test/e2e/crds/v2/upstream.go
+++ b/test/e2e/crds/v2/upstream.go
@@ -135,4 +135,51 @@ 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() 
{
+                       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

Reply via email to