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 7510e5c3 feat(apisixupstream): support healthcheck (#2574)
7510e5c3 is described below

commit 7510e5c377a3ca95d2fbcad19814cfc590685117
Author: AlinsRan <[email protected]>
AuthorDate: Thu Sep 25 15:31:57 2025 +0800

    feat(apisixupstream): support healthcheck (#2574)
---
 api/adc/types.go                                   |   2 +-
 api/v2/apisixupstream_types.go                     |  23 +++-
 .../bases/apisix.apache.org_apisixupstreams.yaml   |  56 ++++++---
 docs/en/latest/reference/api-reference.md          |  10 +-
 docs/en/latest/upgrade-guide.md                    |   1 -
 internal/adc/translator/apisixupstream.go          |  88 +++++++++++++
 internal/adc/translator/translator.go              |   1 -
 test/e2e/crds/v2/upstream.go                       | 138 +++++++++++++++++++++
 test/e2e/scaffold/adc.go                           |  45 +++++--
 9 files changed, 330 insertions(+), 34 deletions(-)

diff --git a/api/adc/types.go b/api/adc/types.go
index 5a8062db..b5b67410 100644
--- a/api/adc/types.go
+++ b/api/adc/types.go
@@ -205,7 +205,7 @@ type UpstreamActiveHealthCheck struct {
        Host               string                             
`json:"host,omitempty" yaml:"host,omitempty"`
        Port               int32                              
`json:"port,omitempty" yaml:"port,omitempty"`
        HTTPPath           string                             
`json:"http_path,omitempty" yaml:"http_path,omitempty"`
-       HTTPSVerifyCert    bool                               
`json:"https_verify_certificate,omitempty" 
yaml:"https_verify_certificate,omitempty"`
+       HTTPSVerifyCert    bool                               
`json:"https_verify_cert,omitempty" yaml:"https_verify_cert,omitempty"`
        HTTPRequestHeaders []string                           
`json:"req_headers,omitempty" yaml:"req_headers,omitempty"`
        Healthy            UpstreamActiveHealthCheckHealthy   
`json:"healthy,omitempty" yaml:"healthy,omitempty"`
        Unhealthy          UpstreamActiveHealthCheckUnhealthy 
`json:"unhealthy,omitempty" yaml:"unhealthy,omitempty"`
diff --git a/api/v2/apisixupstream_types.go b/api/v2/apisixupstream_types.go
index c6578f91..aa052e61 100644
--- a/api/v2/apisixupstream_types.go
+++ b/api/v2/apisixupstream_types.go
@@ -91,7 +91,10 @@ type ApisixUpstreamExternalNode struct {
        Weight *int `json:"weight,omitempty" yaml:"weight"`
 
        // Port specifies the port number on which the external node is 
accepting traffic.
+       //
        // +kubebuilder:validation:Optional
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=65535
        Port *int `json:"port,omitempty" yaml:"port"`
 }
 
@@ -118,7 +121,6 @@ type ApisixUpstreamConfig struct {
        Timeout *UpstreamTimeout `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
 
        // HealthCheck defines the active and passive health check 
configuration for the upstream.
-       // Deprecated: no longer supported in standalone mode.
        // +kubebuilder:validation:Optional
        HealthCheck *HealthCheck `json:"healthCheck,omitempty" 
yaml:"healthCheck,omitempty"`
 
@@ -160,6 +162,8 @@ type PortLevelSettings struct {
        ApisixUpstreamConfig `json:",inline" yaml:",inline"`
 
        // Port is a Kubernetes Service port.
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=65535
        Port int32 `json:"port" yaml:"port"`
 }
 
@@ -221,9 +225,11 @@ type Discovery struct {
 
 // ActiveHealthCheck defines the active upstream health check configuration.
 type ActiveHealthCheck struct {
+       // Type is the health check type. Can be `http`, `https`, or `tcp`.
+       //
        // +kubebuilder:validation:Optional
        // +kubebuilder:validation:Enum=http;https;tcp;
-       // Type is the health check type. Can be `http`, `https`, or `tcp`.
+       // +kubebuilder:default=http
        Type string `json:"type,omitempty" yaml:"type,omitempty"`
        // Timeout sets health check timeout in seconds.
        Timeout time.Duration `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
@@ -232,9 +238,10 @@ type ActiveHealthCheck struct {
        Concurrency int `json:"concurrency,omitempty" 
yaml:"concurrency,omitempty"`
        // Host sets the upstream host.
        Host string `json:"host,omitempty" yaml:"host,omitempty"`
-       // +kubebuilder:validation:Minimum=0
-       // +kubebuilder:validation:Maximum=65535
        // Port sets the upstream port.
+       //
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=65535
        Port int32 `json:"port,omitempty" yaml:"port,omitempty"`
        // HTTPPath sets the HTTP probe request path.
        HTTPPath string `json:"httpPath,omitempty" yaml:"httpPath,omitempty"`
@@ -254,6 +261,10 @@ type ActiveHealthCheck struct {
 type PassiveHealthCheck struct {
        // Type specifies the type of passive health check.
        // Can be `http`, `https`, or `tcp`.
+       //
+       // +kubebuilder:validation:Optional
+       // +kubebuilder:validation:Enum=http;https;tcp;
+       // +kubebuilder:default=http
        Type string `json:"type,omitempty" yaml:"type,omitempty"`
        // Healthy defines the conditions under which an upstream node is 
considered healthy.
        Healthy *PassiveHealthCheckHealthy `json:"healthy,omitempty" 
yaml:"healthy,omitempty"`
@@ -304,6 +315,10 @@ type PassiveHealthCheckUnhealthy struct {
        // TCPFailures define the number of TCP failures to define an unhealthy 
target.
        TCPFailures int `json:"tcpFailures,omitempty" 
yaml:"tcpFailures,omitempty"`
        // Timeout sets health check timeout in seconds.
+       // 
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
+       //
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=254
        Timeouts int `json:"timeout,omitempty" yaml:"timeout,omitempty"`
 }
 
diff --git a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml 
b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
index 37880aba..cae2ab96 100644
--- a/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
+++ b/config/crd/bases/apisix.apache.org_apisixupstreams.yaml
@@ -81,6 +81,8 @@ spec:
                     port:
                       description: Port specifies the port number on which the 
external
                         node is accepting traffic.
+                      maximum: 65535
+                      minimum: 1
                       type: integer
                     type:
                       description: Type indicates the kind of external node. 
Can be
@@ -95,9 +97,8 @@ spec:
                 minItems: 1
                 type: array
               healthCheck:
-                description: |-
-                  HealthCheck defines the active and passive health check 
configuration for the upstream.
-                  Deprecated: no longer supported in standalone mode.
+                description: HealthCheck defines the active and passive health 
check
+                  configuration for the upstream.
                 properties:
                   active:
                     description: Active health checks proactively send 
requests to
@@ -140,7 +141,7 @@ spec:
                         description: Port sets the upstream port.
                         format: int32
                         maximum: 65535
-                        minimum: 0
+                        minimum: 1
                         type: integer
                       requestHeaders:
                         description: RequestHeaders sets the request headers.
@@ -155,6 +156,7 @@ spec:
                         format: int64
                         type: integer
                       type:
+                        default: http
                         description: Type is the health check type. Can be 
`http`,
                           `https`, or `tcp`.
                         enum:
@@ -190,7 +192,11 @@ spec:
                             minimum: 0
                             type: integer
                           timeout:
-                            description: Timeout sets health check timeout in 
seconds.
+                            description: |-
+                              Timeout sets health check timeout in seconds.
+                              
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
+                            maximum: 254
+                            minimum: 1
                             type: integer
                         type: object
                     type: object
@@ -217,9 +223,14 @@ spec:
                             type: integer
                         type: object
                       type:
+                        default: http
                         description: |-
                           Type specifies the type of passive health check.
                           Can be `http`, `https`, or `tcp`.
+                        enum:
+                        - http
+                        - https
+                        - tcp
                         type: string
                       unhealthy:
                         description: Unhealthy defines the conditions under 
which
@@ -245,7 +256,11 @@ spec:
                             minimum: 0
                             type: integer
                           timeout:
-                            description: Timeout sets health check timeout in 
seconds.
+                            description: |-
+                              Timeout sets health check timeout in seconds.
+                              
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
+                            maximum: 254
+                            minimum: 1
                             type: integer
                         type: object
                     type: object
@@ -344,9 +359,8 @@ spec:
                       - type
                       type: object
                     healthCheck:
-                      description: |-
-                        HealthCheck defines the active and passive health 
check configuration for the upstream.
-                        Deprecated: no longer supported in standalone mode.
+                      description: HealthCheck defines the active and passive 
health
+                        check configuration for the upstream.
                       properties:
                         active:
                           description: Active health checks proactively send 
requests
@@ -389,7 +403,7 @@ spec:
                               description: Port sets the upstream port.
                               format: int32
                               maximum: 65535
-                              minimum: 0
+                              minimum: 1
                               type: integer
                             requestHeaders:
                               description: RequestHeaders sets the request 
headers.
@@ -404,6 +418,7 @@ spec:
                               format: int64
                               type: integer
                             type:
+                              default: http
                               description: Type is the health check type. Can 
be `http`,
                                 `https`, or `tcp`.
                               enum:
@@ -439,8 +454,11 @@ spec:
                                   minimum: 0
                                   type: integer
                                 timeout:
-                                  description: Timeout sets health check 
timeout in
-                                    seconds.
+                                  description: |-
+                                    Timeout sets health check timeout in 
seconds.
+                                    
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
+                                  maximum: 254
+                                  minimum: 1
                                   type: integer
                               type: object
                           type: object
@@ -467,9 +485,14 @@ spec:
                                   type: integer
                               type: object
                             type:
+                              default: http
                               description: |-
                                 Type specifies the type of passive health 
check.
                                 Can be `http`, `https`, or `tcp`.
+                              enum:
+                              - http
+                              - https
+                              - tcp
                               type: string
                             unhealthy:
                               description: Unhealthy defines the conditions 
under
@@ -495,8 +518,11 @@ spec:
                                   minimum: 0
                                   type: integer
                                 timeout:
-                                  description: Timeout sets health check 
timeout in
-                                    seconds.
+                                  description: |-
+                                    Timeout sets health check timeout in 
seconds.
+                                    
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
+                                  maximum: 254
+                                  minimum: 1
                                   type: integer
                               type: object
                           type: object
@@ -559,6 +585,8 @@ spec:
                     port:
                       description: Port is a Kubernetes Service port.
                       format: int32
+                      maximum: 65535
+                      minimum: 1
                       type: integer
                     retries:
                       description: |-
diff --git a/docs/en/latest/reference/api-reference.md 
b/docs/en/latest/reference/api-reference.md
index 26e7183c..6a891cb8 100644
--- a/docs/en/latest/reference/api-reference.md
+++ b/docs/en/latest/reference/api-reference.md
@@ -661,7 +661,7 @@ UpstreamActiveHealthCheckHealthy defines the conditions 
used to actively determi
 | `httpCodes` _integer array_ | HTTPCodes define a list of HTTP status codes 
that are considered unhealthy. |
 | `httpFailures` _integer_ | HTTPFailures define the number of HTTP failures 
to define an unhealthy target. |
 | `tcpFailures` _integer_ | TCPFailures define the number of TCP failures to 
define an unhealthy target. |
-| `timeout` _integer_ | Timeout sets health check timeout in seconds. |
+| `timeout` _integer_ | Timeout sets health check timeout in seconds. 
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
 |
 | `interval` 
_[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#duration-v1-meta)_
 | Interval defines the time interval for checking targets, in seconds. |
 
 
@@ -1320,7 +1320,7 @@ ApisixUpstreamConfig defines configuration for upstream 
services.
 | `scheme` _string_ | Scheme is the protocol used to communicate with the 
upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
 | `retries` _integer_ | Retries defines the number of retry attempts APISIX 
should make when a failure occurs. Failures include timeouts, network errors, 
or 5xx status codes. |
 | `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the 
connection, send, and read timeouts for upstream requests. |
-| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. Deprecated: no longer 
supported in standalone mode. |
+| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. |
 | `tlsSecret` _[ApisixSecret](#apisixsecret)_ | TLSSecret references a 
Kubernetes Secret that contains the client certificate and key for mutual TLS 
when connecting to the upstream. |
 | `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 |
@@ -1380,7 +1380,7 @@ definitions and custom configuration.
 | `scheme` _string_ | Scheme is the protocol used to communicate with the 
upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
 | `retries` _integer_ | Retries defines the number of retry attempts APISIX 
should make when a failure occurs. Failures include timeouts, network errors, 
or 5xx status codes. |
 | `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the 
connection, send, and read timeouts for upstream requests. |
-| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. Deprecated: no longer 
supported in standalone mode. |
+| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. |
 | `tlsSecret` _[ApisixSecret](#apisixsecret)_ | TLSSecret references a 
Kubernetes Secret that contains the client certificate and key for mutual TLS 
when connecting to the upstream. |
 | `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 |
@@ -1528,7 +1528,7 @@ UpstreamPassiveHealthCheckUnhealthy defines the 
conditions used to passively det
 | `httpCodes` _integer array_ | HTTPCodes define a list of HTTP status codes 
that are considered unhealthy. |
 | `httpFailures` _integer_ | HTTPFailures define the number of HTTP failures 
to define an unhealthy target. |
 | `tcpFailures` _integer_ | TCPFailures define the number of TCP failures to 
define an unhealthy target. |
-| `timeout` _integer_ | Timeout sets health check timeout in seconds. |
+| `timeout` _integer_ | Timeout sets health check timeout in seconds. 
https://github.com/apache/apisix/blob/0151d9e35bba63d7c316187272d88e19db0be634/apisix/schema_def.lua#L196
 |
 
 
 _Appears in:_
@@ -1550,7 +1550,7 @@ them if they are set on the port level.
 | `scheme` _string_ | Scheme is the protocol used to communicate with the 
upstream. Default is `http`. Can be `http`, `https`, `grpc`, or `grpcs`. |
 | `retries` _integer_ | Retries defines the number of retry attempts APISIX 
should make when a failure occurs. Failures include timeouts, network errors, 
or 5xx status codes. |
 | `timeout` _[UpstreamTimeout](#upstreamtimeout)_ | Timeout specifies the 
connection, send, and read timeouts for upstream requests. |
-| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. Deprecated: no longer 
supported in standalone mode. |
+| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines the active 
and passive health check configuration for the upstream. |
 | `tlsSecret` _[ApisixSecret](#apisixsecret)_ | TLSSecret references a 
Kubernetes Secret that contains the client certificate and key for mutual TLS 
when connecting to the upstream. |
 | `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 |
diff --git a/docs/en/latest/upgrade-guide.md b/docs/en/latest/upgrade-guide.md
index eac2ebf6..068ef6a7 100644
--- a/docs/en/latest/upgrade-guide.md
+++ b/docs/en/latest/upgrade-guide.md
@@ -143,7 +143,6 @@ spec:
 Due to current limitations in the [ADC](https://github.com/api7/adc) 
component, the following fields are not yet supported:
 
 * `spec.discovery`: Service Discovery
-* `spec.healthCheck`: Health Checking
 
 More details: [ADC Backend 
Differences](https://github.com/api7/adc/blob/2449ca81e3c61169f8c1e59efb4c1173a766bce2/libs/backend-apisix-standalone/README.md#differences-in-upstream)
 
diff --git a/internal/adc/translator/apisixupstream.go 
b/internal/adc/translator/apisixupstream.go
index f4faf298..5a76025e 100644
--- a/internal/adc/translator/apisixupstream.go
+++ b/internal/adc/translator/apisixupstream.go
@@ -39,6 +39,7 @@ func (t *Translator) translateApisixUpstream(tctx 
*provider.TranslateContext, au
                translateApisixUpstreamLoadBalancer,
                translateApisixUpstreamRetriesAndTimeout,
                translateApisixUpstreamPassHost,
+               translateUpstreamHealthCheck,
        } {
                if err = f(au, ups); err != nil {
                        return
@@ -252,3 +253,90 @@ func translateApisixUpstreamExternalNodesService(tctx 
*provider.TranslateContext
 
        return nil
 }
+
+func translateUpstreamHealthCheck(au *apiv2.ApisixUpstream, ups *adc.Upstream) 
error {
+       if au == nil {
+               return nil
+       }
+       healcheck := au.Spec.HealthCheck
+       if healcheck == nil || (healcheck.Passive == nil && healcheck.Active == 
nil) {
+               return nil
+       }
+       var hc adc.UpstreamHealthCheck
+       if healcheck.Passive != nil {
+               hc.Passive = 
translateUpstreamPassiveHealthCheck(healcheck.Passive)
+       }
+
+       if healcheck.Active != nil {
+               active, err := 
translateUpstreamActiveHealthCheck(healcheck.Active)
+               if err != nil {
+                       return err
+               }
+               hc.Active = active
+       }
+
+       ups.Checks = &hc
+       return nil
+}
+
+func translateUpstreamActiveHealthCheck(config *apiv2.ActiveHealthCheck) 
(*adc.UpstreamActiveHealthCheck, error) {
+       var active adc.UpstreamActiveHealthCheck
+       if config.Type == "" {
+               config.Type = apiv2.HealthCheckHTTP
+       }
+
+       active.Timeout = int(config.Timeout.Seconds())
+       active.Port = config.Port
+       active.Concurrency = config.Concurrency
+       active.Host = config.Host
+       active.HTTPPath = config.HTTPPath
+       active.HTTPRequestHeaders = config.RequestHeaders
+
+       if config.StrictTLS == nil || *config.StrictTLS {
+               active.HTTPSVerifyCert = true
+       }
+
+       if config.Healthy != nil {
+               active.Healthy.Successes = config.Healthy.Successes
+               active.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
+
+               if config.Healthy.Interval.Duration < 
apiv2.ActiveHealthCheckMinInterval {
+                       return nil, 
fmt.Errorf(`"healthCheck.active.healthy.interval" has invalid value`)
+               }
+               active.Healthy.Interval = int(config.Healthy.Interval.Seconds())
+       }
+
+       if config.Unhealthy != nil {
+               active.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
+               active.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
+               active.Unhealthy.Timeouts = config.Unhealthy.Timeouts
+               active.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
+
+               if config.Unhealthy.Interval.Duration < 
apiv2.ActiveHealthCheckMinInterval {
+                       return nil, 
fmt.Errorf(`"healthCheck.active.unhealthy.interval" has invalid value`)
+               }
+               active.Unhealthy.Interval = 
int(config.Unhealthy.Interval.Seconds())
+       }
+
+       return &active, nil
+}
+
+func translateUpstreamPassiveHealthCheck(config *apiv2.PassiveHealthCheck) 
*adc.UpstreamPassiveHealthCheck {
+       var passive adc.UpstreamPassiveHealthCheck
+       if config.Type == "" {
+               config.Type = apiv2.HealthCheckHTTP
+       }
+
+       if config.Healthy != nil {
+               passive.Healthy.Successes = config.Healthy.Successes
+               passive.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
+       }
+
+       if config.Unhealthy != nil {
+               passive.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
+               passive.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
+               passive.Unhealthy.Timeouts = config.Unhealthy.Timeouts
+               passive.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
+       }
+       return &passive
+}
diff --git a/internal/adc/translator/translator.go 
b/internal/adc/translator/translator.go
index 0d50661a..4c9bf0d8 100644
--- a/internal/adc/translator/translator.go
+++ b/internal/adc/translator/translator.go
@@ -34,7 +34,6 @@ func NewTranslator(log logr.Logger) *Translator {
 }
 
 type TranslateResult struct {
-       Routes         []*adctypes.Route
        Services       []*adctypes.Service
        SSL            []*adctypes.SSL
        GlobalRules    adctypes.GlobalRule
diff --git a/test/e2e/crds/v2/upstream.go b/test/e2e/crds/v2/upstream.go
new file mode 100644
index 00000000..7d7d9a61
--- /dev/null
+++ b/test/e2e/crds/v2/upstream.go
@@ -0,0 +1,138 @@
+// 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 v2
+
+import (
+       "context"
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+       "k8s.io/apimachinery/pkg/types"
+
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test ApisixUpstream", Label("apisix.apache.org", "v2", 
"apisixupstream"), func() {
+       var (
+               s       = scaffold.NewDefaultScaffold()
+               applier = framework.NewApplier(s.GinkgoT, s.K8sClient, 
s.CreateResourceFromString)
+       )
+       BeforeEach(func() {
+               By("create GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("create IngressClass")
+               err = 
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+               Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
+               time.Sleep(5 * time.Second)
+       })
+
+       Context("Health Check", func() {
+               It("active and passive", func() {
+                       auWithHealthcheck := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: active
+spec:
+  ingressClassName: %s
+  externalNodes:
+  - type: Domain
+    name: httpbin-service-e2e-test
+  - type: Domain
+    name: invalid.httpbin.host
+  - type: Domain
+    name: invalid1.httpbin.host
+  retries: 1
+  healthCheck:
+    active:
+      type: http
+      httpPath: /ip
+      healthy:
+        httpCodes: [200]
+        interval: 1s
+      unhealthy:
+        httpFailures: 2
+        interval: 1s
+    passive:
+      healthy:
+        httpCodes: [200]
+      unhealthy:
+        httpCodes: [502]
+`
+                       applier.MustApplyAPIv2(types.NamespacedName{Namespace: 
s.Namespace(), Name: "active"},
+                               &apiv2.ApisixUpstream{}, 
fmt.Sprintf(auWithHealthcheck, s.Namespace()))
+
+                       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: active
+`
+                       applier.MustApplyAPIv2(types.NamespacedName{Namespace: 
s.Namespace(), Name: "httpbin-route"},
+                               &apiv2.ApisixRoute{}, fmt.Sprintf(ar, 
s.Namespace()))
+
+                       By("triggering the health check")
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/ip",
+                               Host:   "httpbin.org",
+                       })
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := 
s.Deployer.DefaultDataplaneResource().Upstream().List(context.Background())
+                       Expect(err).ToNot(HaveOccurred(), "listing upstreams")
+                       Expect(ups).To(HaveLen(1), "the number of upstreams")
+                       Expect(ups[0].Nodes).To(HaveLen(3), "the number of 
upstream nodes")
+                       Expect(ups[0].Checks).ToNot(BeNil(), "the healthcheck 
configuration")
+                       Expect(ups[0].Checks.Active).ToNot(BeNil(), "the active 
healthcheck configuration")
+                       Expect(ups[0].Checks.Active.Healthy).ToNot(BeNil(), 
"the active healthy configuration")
+                       Expect(ups[0].Checks.Active.Unhealthy).ToNot(BeNil(), 
"the active unhealthy configuration")
+                       
Expect(ups[0].Checks.Active.Healthy.Interval).To(Equal(1), "the healthy 
interval")
+                       
Expect(ups[0].Checks.Active.Healthy.HTTPStatuses).To(Equal([]int{200}), "the 
healthy http status")
+                       
Expect(ups[0].Checks.Active.Unhealthy.Interval).To(Equal(1), "the unhealthy 
interval")
+                       
Expect(ups[0].Checks.Active.Unhealthy.HTTPFailures).To(Equal(2), "the unhealthy 
http failures")
+                       Expect(ups[0].Checks.Passive).ToNot(BeNil(), "the 
passive healthcheck configuration")
+                       Expect(ups[0].Checks.Passive.Healthy).ToNot(BeNil(), 
"the passive healthy configuration")
+                       Expect(ups[0].Checks.Passive.Unhealthy).ToNot(BeNil(), 
"the passive unhealthy configuration")
+                       
Expect(ups[0].Checks.Passive.Healthy.HTTPStatuses).To(Equal([]int{200}), "the 
passive healthy http status")
+                       
Expect(ups[0].Checks.Passive.Unhealthy.HTTPStatuses).To(Equal([]int{502}), "the 
passive unhealthy http status")
+
+                       for range 100 {
+                               
s.NewAPISIXClient().GET("/ip").WithHost("httpbin.org").Expect().Status(200)
+                       }
+               })
+       })
+})
diff --git a/test/e2e/scaffold/adc.go b/test/e2e/scaffold/adc.go
index 0a45cd33..0026e2e1 100644
--- a/test/e2e/scaffold/adc.go
+++ b/test/e2e/scaffold/adc.go
@@ -56,6 +56,7 @@ func init() {
 // DataplaneResource defines the interface for accessing dataplane resources
 type DataplaneResource interface {
        Route() RouteResource
+       Upstream() UpstreamResource
        Service() ServiceResource
        SSL() SSLResource
        Consumer() ConsumerResource
@@ -76,6 +77,11 @@ type SSLResource interface {
        List(ctx context.Context) ([]*adctypes.SSL, error)
 }
 
+// UpstreamResource defines the interface for upstream resources
+type UpstreamResource interface {
+       List(ctx context.Context) ([]*adctypes.Upstream, error)
+}
+
 // ConsumerResource defines the interface for consumer resources
 type ConsumerResource interface {
        List(ctx context.Context) ([]*adctypes.Consumer, error)
@@ -117,6 +123,10 @@ func (a *adcDataplaneResource) Consumer() ConsumerResource 
{
        return &adcConsumerResource{a}
 }
 
+func (a *adcDataplaneResource) Upstream() UpstreamResource {
+       return &adcUpstreamResource{a}
+}
+
 func init() {
        l, err := log.NewLogger(
                log.WithOutputFile("stderr"),
@@ -191,14 +201,7 @@ func (a *adcDataplaneResource) dumpResources(ctx 
context.Context) (*translator.T
                return nil, err
        }
 
-       // Extract routes from services
-       var routes []*adctypes.Route
-       for _, service := range resources.Services {
-               routes = append(routes, service.Routes...)
-       }
-
        result := &translator.TranslateResult{
-               Routes:         routes,
                Services:       resources.Services,
                SSL:            resources.SSLs,
                GlobalRules:    resources.GlobalRules,
@@ -219,7 +222,11 @@ func (r *adcRouteResource) List(ctx context.Context) 
([]*adctypes.Route, error)
        if err != nil {
                return nil, err
        }
-       return result.Routes, nil
+       var routes []*adctypes.Route
+       for _, service := range result.Services {
+               routes = append(routes, service.Routes...)
+       }
+       return routes, nil
 }
 
 // adcServiceResource implements ServiceResource
@@ -260,3 +267,25 @@ func (c *adcConsumerResource) List(ctx context.Context) 
([]*adctypes.Consumer, e
        }
        return result.Consumers, nil
 }
+
+type adcUpstreamResource struct {
+       *adcDataplaneResource
+}
+
+func (r *adcUpstreamResource) List(ctx context.Context) ([]*adctypes.Upstream, 
error) {
+       result, err := r.dumpResources(ctx)
+       if err != nil {
+               return nil, err
+       }
+       upstreams := make([]*adctypes.Upstream, 0, len(result.Services))
+       for _, svc := range result.Services {
+               if svc.Upstream != nil {
+                       upstreams = append(upstreams, svc.Upstream)
+               }
+               if svc.Upstreams != nil {
+                       upstreams = append(upstreams, svc.Upstreams...)
+               }
+       }
+
+       return upstreams, nil
+}

Reply via email to