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 e461eb2e feat: support upstream health checks in BackendTrafficPolicy 
(#2763)
e461eb2e is described below

commit e461eb2e330c44d831b96c59d2c4093653a2b5b8
Author: AlinsRan <[email protected]>
AuthorDate: Mon May 18 09:01:24 2026 +0800

    feat: support upstream health checks in BackendTrafficPolicy (#2763)
---
 api/v1alpha1/backendtrafficpolicy_types.go         | 140 ++++++++++
 api/v1alpha1/zz_generated.deepcopy.go              | 165 +++++++++++
 .../apisix.apache.org_backendtrafficpolicies.yaml  | 175 ++++++++++++
 docs/en/latest/reference/api-reference.md          | 130 +++++++++
 internal/adc/translator/apisixconsumer_test.go     |   2 +-
 internal/adc/translator/httproute_test.go          | 311 +++++++++++++++++++++
 internal/adc/translator/policies.go                |  89 ++++++
 test/e2e/crds/v1alpha1/backendtrafficpolicy.go     | 216 ++++++++++++--
 8 files changed, 1207 insertions(+), 21 deletions(-)

diff --git a/api/v1alpha1/backendtrafficpolicy_types.go 
b/api/v1alpha1/backendtrafficpolicy_types.go
index 44d5239d..826ec854 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -74,6 +74,13 @@ type BackendTrafficPolicySpec struct {
        // UpstreamHost specifies the host of the Upstream request. Used only if
        // passHost is set to `rewrite`.
        Host Hostname `json:"upstreamHost,omitempty" 
yaml:"upstreamHost,omitempty"`
+
+       // HealthCheck defines active and passive health check configuration for
+       // the upstream backends. When configured, APISIX will probe backends
+       // (active) or monitor live traffic (passive) to detect and bypass
+       // unhealthy nodes.
+       // +optional
+       HealthCheck *HealthCheck `json:"healthCheck,omitempty" 
yaml:"healthCheck,omitempty"`
 }
 
 // LoadBalancer describes the load balancing parameters.
@@ -125,6 +132,139 @@ type BackendTrafficPolicyList struct {
        Items           []BackendTrafficPolicy `json:"items"`
 }
 
+// HealthCheck defines the active and passive health check configuration for 
upstream nodes.
+type HealthCheck struct {
+       // Active health checks proactively send requests to upstream nodes to 
determine their availability.
+       // +kubebuilder:validation:Required
+       Active *ActiveHealthCheck `json:"active" yaml:"active"`
+       // Passive health checks evaluate upstream health based on observed 
traffic (timeouts, errors).
+       // +kubebuilder:validation:Optional
+       Passive *PassiveHealthCheck `json:"passive,omitempty" 
yaml:"passive,omitempty"`
+}
+
+// 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:Enum=http;https;tcp;
+       // +kubebuilder:default=http
+       // +optional
+       Type string `json:"type,omitempty" yaml:"type,omitempty"`
+
+       // Timeout sets health check timeout.
+       // +optional
+       Timeout metav1.Duration `json:"timeout,omitempty" 
yaml:"timeout,omitempty"`
+
+       // Concurrency sets the number of targets to be checked at the same 
time.
+       // +kubebuilder:validation:Minimum=0
+       // +optional
+       Concurrency int `json:"concurrency,omitempty" 
yaml:"concurrency,omitempty"`
+
+       // Host sets the upstream host used in the health check request.
+       // +optional
+       Host string `json:"host,omitempty" yaml:"host,omitempty"`
+
+       // Port sets the port on the upstream node to probe.
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=65535
+       // +optional
+       Port int32 `json:"port,omitempty" yaml:"port,omitempty"`
+
+       // HTTPPath sets the HTTP path for the probe request.
+       // +optional
+       HTTPPath string `json:"httpPath,omitempty" yaml:"httpPath,omitempty"`
+
+       // StrictTLS controls whether TLS certificate validation is enforced.
+       // +optional
+       StrictTLS *bool `json:"strictTLS,omitempty" yaml:"strictTLS,omitempty"`
+
+       // RequestHeaders sets additional HTTP request headers for the probe.
+       // +optional
+       RequestHeaders []string `json:"requestHeaders,omitempty" 
yaml:"requestHeaders,omitempty"`
+
+       // Healthy configures the thresholds for marking a node healthy.
+       // +optional
+       Healthy *ActiveHealthCheckHealthy `json:"healthy,omitempty" 
yaml:"healthy,omitempty"`
+
+       // Unhealthy configures the thresholds for marking a node unhealthy.
+       // +optional
+       Unhealthy *ActiveHealthCheckUnhealthy `json:"unhealthy,omitempty" 
yaml:"unhealthy,omitempty"`
+}
+
+// PassiveHealthCheck defines passive health check configuration based on 
observed traffic.
+type PassiveHealthCheck struct {
+       // Type is the passive health check type. Can be `http`, `https`, or 
`tcp`.
+       // +kubebuilder:validation:Enum=http;https;tcp;
+       // +kubebuilder:default=http
+       // +optional
+       Type string `json:"type,omitempty" yaml:"type,omitempty"`
+
+       // Healthy defines conditions under which a node is considered healthy.
+       // +optional
+       Healthy *PassiveHealthCheckHealthy `json:"healthy,omitempty" 
yaml:"healthy,omitempty"`
+
+       // Unhealthy defines conditions under which a node is considered 
unhealthy.
+       // +optional
+       Unhealthy *PassiveHealthCheckUnhealthy `json:"unhealthy,omitempty" 
yaml:"unhealthy,omitempty"`
+}
+
+// ActiveHealthCheckHealthy defines thresholds for actively marking an 
upstream node healthy.
+type ActiveHealthCheckHealthy struct {
+       PassiveHealthCheckHealthy `json:",inline" yaml:",inline"`
+
+       // Interval defines the time between health check probes.
+       // Minimum is 1s.
+       Interval metav1.Duration `json:"interval,omitempty" 
yaml:"interval,omitempty"`
+}
+
+// ActiveHealthCheckUnhealthy defines thresholds for actively marking an 
upstream node unhealthy.
+type ActiveHealthCheckUnhealthy struct {
+       PassiveHealthCheckUnhealthy `json:",inline" yaml:",inline"`
+
+       // Interval defines the time between health check probes.
+       // Minimum is 1s.
+       Interval metav1.Duration `json:"interval,omitempty" 
yaml:"interval,omitempty"`
+}
+
+// PassiveHealthCheckHealthy defines conditions for passively marking a node 
healthy.
+type PassiveHealthCheckHealthy struct {
+       // HTTPCodes is the list of HTTP status codes considered healthy.
+       // +kubebuilder:validation:MinItems=1
+       // +optional
+       HTTPCodes []int `json:"httpCodes,omitempty" yaml:"httpCodes,omitempty"`
+
+       // Successes is the number of consecutive successful responses required 
to mark a node healthy.
+       // +kubebuilder:validation:Minimum=0
+       // +kubebuilder:validation:Maximum=254
+       // +optional
+       Successes int `json:"successes,omitempty" yaml:"successes,omitempty"`
+}
+
+// PassiveHealthCheckUnhealthy defines conditions for passively marking a node 
unhealthy.
+type PassiveHealthCheckUnhealthy struct {
+       // HTTPCodes is the list of HTTP status codes considered unhealthy.
+       // +kubebuilder:validation:MinItems=1
+       // +optional
+       HTTPCodes []int `json:"httpCodes,omitempty" yaml:"httpCodes,omitempty"`
+
+       // HTTPFailures is the number of HTTP failures to mark a node unhealthy.
+       // +kubebuilder:validation:Minimum=0
+       // +kubebuilder:validation:Maximum=254
+       // +optional
+       HTTPFailures int `json:"httpFailures,omitempty" 
yaml:"httpFailures,omitempty"`
+
+       // TCPFailures is the number of TCP failures to mark a node unhealthy.
+       // +kubebuilder:validation:Minimum=0
+       // +kubebuilder:validation:Maximum=254
+       // +optional
+       TCPFailures int `json:"tcpFailures,omitempty" 
yaml:"tcpFailures,omitempty"`
+
+       // Timeouts is the number of timeouts to mark a node unhealthy.
+       // +kubebuilder:validation:Minimum=1
+       // +kubebuilder:validation:Maximum=254
+       // +optional
+       Timeouts int `json:"timeouts,omitempty" yaml:"timeouts,omitempty"`
+}
+
 func init() {
        SchemeBuilder.Register(&BackendTrafficPolicy{}, 
&BackendTrafficPolicyList{})
 }
diff --git a/api/v1alpha1/zz_generated.deepcopy.go 
b/api/v1alpha1/zz_generated.deepcopy.go
index cc03a549..9e1c02ff 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -26,6 +26,76 @@ import (
        "sigs.k8s.io/gateway-api/apis/v1alpha2"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ActiveHealthCheck) DeepCopyInto(out *ActiveHealthCheck) {
+       *out = *in
+       out.Timeout = in.Timeout
+       if in.StrictTLS != nil {
+               in, out := &in.StrictTLS, &out.StrictTLS
+               *out = new(bool)
+               **out = **in
+       }
+       if in.RequestHeaders != nil {
+               in, out := &in.RequestHeaders, &out.RequestHeaders
+               *out = make([]string, len(*in))
+               copy(*out, *in)
+       }
+       if in.Healthy != nil {
+               in, out := &in.Healthy, &out.Healthy
+               *out = new(ActiveHealthCheckHealthy)
+               (*in).DeepCopyInto(*out)
+       }
+       if in.Unhealthy != nil {
+               in, out := &in.Unhealthy, &out.Unhealthy
+               *out = new(ActiveHealthCheckUnhealthy)
+               (*in).DeepCopyInto(*out)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ActiveHealthCheck.
+func (in *ActiveHealthCheck) DeepCopy() *ActiveHealthCheck {
+       if in == nil {
+               return nil
+       }
+       out := new(ActiveHealthCheck)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ActiveHealthCheckHealthy) DeepCopyInto(out 
*ActiveHealthCheckHealthy) {
+       *out = *in
+       
in.PassiveHealthCheckHealthy.DeepCopyInto(&out.PassiveHealthCheckHealthy)
+       out.Interval = in.Interval
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ActiveHealthCheckHealthy.
+func (in *ActiveHealthCheckHealthy) DeepCopy() *ActiveHealthCheckHealthy {
+       if in == nil {
+               return nil
+       }
+       out := new(ActiveHealthCheckHealthy)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ActiveHealthCheckUnhealthy) DeepCopyInto(out 
*ActiveHealthCheckUnhealthy) {
+       *out = *in
+       
in.PassiveHealthCheckUnhealthy.DeepCopyInto(&out.PassiveHealthCheckUnhealthy)
+       out.Interval = in.Interval
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ActiveHealthCheckUnhealthy.
+func (in *ActiveHealthCheckUnhealthy) DeepCopy() *ActiveHealthCheckUnhealthy {
+       if in == nil {
+               return nil
+       }
+       out := new(ActiveHealthCheckUnhealthy)
+       in.DeepCopyInto(out)
+       return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *AdminKeyAuth) DeepCopyInto(out *AdminKeyAuth) {
        *out = *in
@@ -171,6 +241,11 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out 
*BackendTrafficPolicySpec)
                *out = new(Timeout)
                **out = **in
        }
+       if in.HealthCheck != nil {
+               in, out := &in.HealthCheck, &out.HealthCheck
+               *out = new(HealthCheck)
+               (*in).DeepCopyInto(*out)
+       }
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new BackendTrafficPolicySpec.
@@ -616,6 +691,31 @@ func (in *HTTPRoutePolicySpec) DeepCopy() 
*HTTPRoutePolicySpec {
        return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *HealthCheck) DeepCopyInto(out *HealthCheck) {
+       *out = *in
+       if in.Active != nil {
+               in, out := &in.Active, &out.Active
+               *out = new(ActiveHealthCheck)
+               (*in).DeepCopyInto(*out)
+       }
+       if in.Passive != nil {
+               in, out := &in.Passive, &out.Passive
+               *out = new(PassiveHealthCheck)
+               (*in).DeepCopyInto(*out)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new HealthCheck.
+func (in *HealthCheck) DeepCopy() *HealthCheck {
+       if in == nil {
+               return nil
+       }
+       out := new(HealthCheck)
+       in.DeepCopyInto(out)
+       return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) {
        *out = *in
@@ -631,6 +731,71 @@ func (in *LoadBalancer) DeepCopy() *LoadBalancer {
        return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) {
+       *out = *in
+       if in.Healthy != nil {
+               in, out := &in.Healthy, &out.Healthy
+               *out = new(PassiveHealthCheckHealthy)
+               (*in).DeepCopyInto(*out)
+       }
+       if in.Unhealthy != nil {
+               in, out := &in.Unhealthy, &out.Unhealthy
+               *out = new(PassiveHealthCheckUnhealthy)
+               (*in).DeepCopyInto(*out)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new PassiveHealthCheck.
+func (in *PassiveHealthCheck) DeepCopy() *PassiveHealthCheck {
+       if in == nil {
+               return nil
+       }
+       out := new(PassiveHealthCheck)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *PassiveHealthCheckHealthy) DeepCopyInto(out 
*PassiveHealthCheckHealthy) {
+       *out = *in
+       if in.HTTPCodes != nil {
+               in, out := &in.HTTPCodes, &out.HTTPCodes
+               *out = make([]int, len(*in))
+               copy(*out, *in)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new PassiveHealthCheckHealthy.
+func (in *PassiveHealthCheckHealthy) DeepCopy() *PassiveHealthCheckHealthy {
+       if in == nil {
+               return nil
+       }
+       out := new(PassiveHealthCheckHealthy)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *PassiveHealthCheckUnhealthy) DeepCopyInto(out 
*PassiveHealthCheckUnhealthy) {
+       *out = *in
+       if in.HTTPCodes != nil {
+               in, out := &in.HTTPCodes, &out.HTTPCodes
+               *out = make([]int, len(*in))
+               copy(*out, *in)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new PassiveHealthCheckUnhealthy.
+func (in *PassiveHealthCheckUnhealthy) DeepCopy() *PassiveHealthCheckUnhealthy 
{
+       if in == nil {
+               return nil
+       }
+       out := new(PassiveHealthCheckUnhealthy)
+       in.DeepCopyInto(out)
+       return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *Plugin) DeepCopyInto(out *Plugin) {
        *out = *in
diff --git a/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml 
b/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
index ed2cad68..c286a934 100644
--- a/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
+++ b/config/crd/bases/apisix.apache.org_backendtrafficpolicies.yaml
@@ -42,6 +42,181 @@ spec:
               BackendTrafficPolicySpec defines traffic handling policies 
applied to backend services,
               such as load balancing strategy, connection settings, and 
failover behavior.
             properties:
+              healthCheck:
+                description: |-
+                  HealthCheck defines active and passive health check 
configuration for
+                  the upstream backends. When configured, APISIX will probe 
backends
+                  (active) or monitor live traffic (passive) to detect and 
bypass
+                  unhealthy nodes.
+                properties:
+                  active:
+                    description: Active health checks proactively send 
requests to
+                      upstream nodes to determine their availability.
+                    properties:
+                      concurrency:
+                        description: Concurrency sets the number of targets to 
be
+                          checked at the same time.
+                        minimum: 0
+                        type: integer
+                      healthy:
+                        description: Healthy configures the thresholds for 
marking
+                          a node healthy.
+                        properties:
+                          httpCodes:
+                            description: HTTPCodes is the list of HTTP status 
codes
+                              considered healthy.
+                            items:
+                              type: integer
+                            minItems: 1
+                            type: array
+                          interval:
+                            description: |-
+                              Interval defines the time between health check 
probes.
+                              Minimum is 1s.
+                            type: string
+                          successes:
+                            description: Successes is the number of 
consecutive successful
+                              responses required to mark a node healthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                        type: object
+                      host:
+                        description: Host sets the upstream host used in the 
health
+                          check request.
+                        type: string
+                      httpPath:
+                        description: HTTPPath sets the HTTP path for the probe 
request.
+                        type: string
+                      port:
+                        description: Port sets the port on the upstream node 
to probe.
+                        format: int32
+                        maximum: 65535
+                        minimum: 1
+                        type: integer
+                      requestHeaders:
+                        description: RequestHeaders sets additional HTTP 
request headers
+                          for the probe.
+                        items:
+                          type: string
+                        type: array
+                      strictTLS:
+                        description: StrictTLS controls whether TLS 
certificate validation
+                          is enforced.
+                        type: boolean
+                      timeout:
+                        description: Timeout sets health check timeout.
+                        type: string
+                      type:
+                        default: http
+                        description: Type is the health check type. Can be 
`http`,
+                          `https`, or `tcp`.
+                        enum:
+                        - http
+                        - https
+                        - tcp
+                        type: string
+                      unhealthy:
+                        description: Unhealthy configures the thresholds for 
marking
+                          a node unhealthy.
+                        properties:
+                          httpCodes:
+                            description: HTTPCodes is the list of HTTP status 
codes
+                              considered unhealthy.
+                            items:
+                              type: integer
+                            minItems: 1
+                            type: array
+                          httpFailures:
+                            description: HTTPFailures is the number of HTTP 
failures
+                              to mark a node unhealthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                          interval:
+                            description: |-
+                              Interval defines the time between health check 
probes.
+                              Minimum is 1s.
+                            type: string
+                          tcpFailures:
+                            description: TCPFailures is the number of TCP 
failures
+                              to mark a node unhealthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                          timeouts:
+                            description: Timeouts is the number of timeouts to 
mark
+                              a node unhealthy.
+                            maximum: 254
+                            minimum: 1
+                            type: integer
+                        type: object
+                    type: object
+                  passive:
+                    description: Passive health checks evaluate upstream 
health based
+                      on observed traffic (timeouts, errors).
+                    properties:
+                      healthy:
+                        description: Healthy defines conditions under which a 
node
+                          is considered healthy.
+                        properties:
+                          httpCodes:
+                            description: HTTPCodes is the list of HTTP status 
codes
+                              considered healthy.
+                            items:
+                              type: integer
+                            minItems: 1
+                            type: array
+                          successes:
+                            description: Successes is the number of 
consecutive successful
+                              responses required to mark a node healthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                        type: object
+                      type:
+                        default: http
+                        description: Type is the passive health check type. 
Can be
+                          `http`, `https`, or `tcp`.
+                        enum:
+                        - http
+                        - https
+                        - tcp
+                        type: string
+                      unhealthy:
+                        description: Unhealthy defines conditions under which 
a node
+                          is considered unhealthy.
+                        properties:
+                          httpCodes:
+                            description: HTTPCodes is the list of HTTP status 
codes
+                              considered unhealthy.
+                            items:
+                              type: integer
+                            minItems: 1
+                            type: array
+                          httpFailures:
+                            description: HTTPFailures is the number of HTTP 
failures
+                              to mark a node unhealthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                          tcpFailures:
+                            description: TCPFailures is the number of TCP 
failures
+                              to mark a node unhealthy.
+                            maximum: 254
+                            minimum: 0
+                            type: integer
+                          timeouts:
+                            description: Timeouts is the number of timeouts to 
mark
+                              a node unhealthy.
+                            maximum: 254
+                            minimum: 1
+                            type: integer
+                        type: object
+                    type: object
+                required:
+                - active
+                type: object
               loadbalancer:
                 description: |-
                   LoadBalancer represents the load balancer configuration for 
Kubernetes Service.
diff --git a/docs/en/latest/reference/api-reference.md 
b/docs/en/latest/reference/api-reference.md
index 11357fb3..3d8987ac 100644
--- a/docs/en/latest/reference/api-reference.md
+++ b/docs/en/latest/reference/api-reference.md
@@ -103,6 +103,66 @@ PluginConfig defines plugin configuration.
 ### Types
 
 This section describes the types used by the CRDs.
+#### ActiveHealthCheck
+
+
+ActiveHealthCheck defines the active upstream health check configuration.
+
+
+
+| Field | Description |
+| --- | --- |
+| `type` _string_ | Type is the health check type. Can be `http`, `https`, or 
`tcp`. |
+| `timeout` 
_[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#duration-v1-meta)_
 | Timeout sets health check timeout. |
+| `concurrency` _integer_ | Concurrency sets the number of targets to be 
checked at the same time. |
+| `host` _string_ | Host sets the upstream host used in the health check 
request. |
+| `port` _integer_ | Port sets the port on the upstream node to probe. |
+| `httpPath` _string_ | HTTPPath sets the HTTP path for the probe request. |
+| `strictTLS` _boolean_ | StrictTLS controls whether TLS certificate 
validation is enforced. |
+| `requestHeaders` _string array_ | RequestHeaders sets additional HTTP 
request headers for the probe. |
+| `healthy` _[ActiveHealthCheckHealthy](#activehealthcheckhealthy)_ | Healthy 
configures the thresholds for marking a node healthy. |
+| `unhealthy` _[ActiveHealthCheckUnhealthy](#activehealthcheckunhealthy)_ | 
Unhealthy configures the thresholds for marking a node unhealthy. |
+
+
+_Appears in:_
+- [HealthCheck](#healthcheck)
+
+#### ActiveHealthCheckHealthy
+
+
+ActiveHealthCheckHealthy defines thresholds for actively marking an upstream 
node healthy.
+
+
+
+| Field | Description |
+| --- | --- |
+| `httpCodes` _integer array_ | HTTPCodes is the list of HTTP status codes 
considered healthy. |
+| `successes` _integer_ | Successes is the number of consecutive successful 
responses required to mark a node healthy. |
+| `interval` 
_[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#duration-v1-meta)_
 | Interval defines the time between health check probes. Minimum is 1s. |
+
+
+_Appears in:_
+- [ActiveHealthCheck](#activehealthcheck)
+
+#### ActiveHealthCheckUnhealthy
+
+
+ActiveHealthCheckUnhealthy defines thresholds for actively marking an upstream 
node unhealthy.
+
+
+
+| Field | Description |
+| --- | --- |
+| `httpCodes` _integer array_ | HTTPCodes is the list of HTTP status codes 
considered unhealthy. |
+| `httpFailures` _integer_ | HTTPFailures is the number of HTTP failures to 
mark a node unhealthy. |
+| `tcpFailures` _integer_ | TCPFailures is the number of TCP failures to mark 
a node unhealthy. |
+| `timeouts` _integer_ | Timeouts is the number of timeouts to mark a node 
unhealthy. |
+| `interval` 
_[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#duration-v1-meta)_
 | Interval defines the time between health check probes. Minimum is 1s. |
+
+
+_Appears in:_
+- [ActiveHealthCheck](#activehealthcheck)
+
 #### AdminKeyAuth
 
 
@@ -180,6 +240,7 @@ _Appears in:_
 | `timeout` _[Timeout](#timeout)_ | Timeout sets the read, send, and connect 
timeouts to the upstream. |
 | `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` _[Hostname](#hostname)_ | UpstreamHost specifies the host of 
the Upstream request. Used only if passHost is set to `rewrite`. |
+| `healthCheck` _[HealthCheck](#healthcheck)_ | HealthCheck defines active and 
passive health check configuration for the upstream backends. When configured, 
APISIX will probe backends (active) or monitor live traffic (passive) to detect 
and bypass unhealthy nodes. |
 
 
 _Appears in:_
@@ -344,6 +405,22 @@ HTTPRoutePolicySpec defines the desired state of 
HTTPRoutePolicy.
 _Appears in:_
 - [HTTPRoutePolicy](#httproutepolicy)
 
+#### HealthCheck
+
+
+HealthCheck defines the active and passive health check configuration for 
upstream nodes.
+
+
+
+| Field | Description |
+| --- | --- |
+| `active` _[ActiveHealthCheck](#activehealthcheck)_ | Active health checks 
proactively send requests to upstream nodes to determine their availability. |
+| `passive` _[PassiveHealthCheck](#passivehealthcheck)_ | Passive health 
checks evaluate upstream health based on observed traffic (timeouts, errors). |
+
+
+_Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
+
 #### Hostname
 _Base type:_ `string`
 
@@ -373,6 +450,59 @@ LoadBalancer describes the load balancing parameters.
 _Appears in:_
 - [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
 
+#### PassiveHealthCheck
+
+
+PassiveHealthCheck defines passive health check configuration based on 
observed traffic.
+
+
+
+| Field | Description |
+| --- | --- |
+| `type` _string_ | Type is the passive health check type. Can be `http`, 
`https`, or `tcp`. |
+| `healthy` _[PassiveHealthCheckHealthy](#passivehealthcheckhealthy)_ | 
Healthy defines conditions under which a node is considered healthy. |
+| `unhealthy` _[PassiveHealthCheckUnhealthy](#passivehealthcheckunhealthy)_ | 
Unhealthy defines conditions under which a node is considered unhealthy. |
+
+
+_Appears in:_
+- [HealthCheck](#healthcheck)
+
+#### PassiveHealthCheckHealthy
+
+
+PassiveHealthCheckHealthy defines conditions for passively marking a node 
healthy.
+
+
+
+| Field | Description |
+| --- | --- |
+| `httpCodes` _integer array_ | HTTPCodes is the list of HTTP status codes 
considered healthy. |
+| `successes` _integer_ | Successes is the number of consecutive successful 
responses required to mark a node healthy. |
+
+
+_Appears in:_
+- [ActiveHealthCheckHealthy](#activehealthcheckhealthy)
+- [PassiveHealthCheck](#passivehealthcheck)
+
+#### PassiveHealthCheckUnhealthy
+
+
+PassiveHealthCheckUnhealthy defines conditions for passively marking a node 
unhealthy.
+
+
+
+| Field | Description |
+| --- | --- |
+| `httpCodes` _integer array_ | HTTPCodes is the list of HTTP status codes 
considered unhealthy. |
+| `httpFailures` _integer_ | HTTPFailures is the number of HTTP failures to 
mark a node unhealthy. |
+| `tcpFailures` _integer_ | TCPFailures is the number of TCP failures to mark 
a node unhealthy. |
+| `timeouts` _integer_ | Timeouts is the number of timeouts to mark a node 
unhealthy. |
+
+
+_Appears in:_
+- [ActiveHealthCheckUnhealthy](#activehealthcheckunhealthy)
+- [PassiveHealthCheck](#passivehealthcheck)
+
 #### Plugin
 
 
diff --git a/internal/adc/translator/apisixconsumer_test.go 
b/internal/adc/translator/apisixconsumer_test.go
index 7dbe131c..fe56b1ec 100644
--- a/internal/adc/translator/apisixconsumer_test.go
+++ b/internal/adc/translator/apisixconsumer_test.go
@@ -49,7 +49,7 @@ func 
TestTranslateApisixConsumer_UsesMetadataLabelsWithoutOverwritingControllerL
                        },
                },
                Spec: apiv2.ApisixConsumerSpec{
-                       AuthParameter: apiv2.ApisixConsumerAuthParameter{
+                       AuthParameter: &apiv2.ApisixConsumerAuthParameter{
                                BasicAuth: &apiv2.ApisixConsumerBasicAuth{
                                        Value: 
&apiv2.ApisixConsumerBasicAuthValue{
                                                Username: "demo",
diff --git a/internal/adc/translator/httproute_test.go 
b/internal/adc/translator/httproute_test.go
new file mode 100644
index 00000000..7b11e129
--- /dev/null
+++ b/internal/adc/translator/httproute_test.go
@@ -0,0 +1,311 @@
+// 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 translator
+
+import (
+       "context"
+       "testing"
+       "time"
+
+       "github.com/go-logr/logr"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       corev1 "k8s.io/api/core/v1"
+       discoveryv1 "k8s.io/api/discovery/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/types"
+       "k8s.io/utils/ptr"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+       gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+       "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/internal/provider"
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
+)
+
+func TestTranslateHTTPRouteUpstreamScheme(t *testing.T) {
+       tests := []struct {
+               name         string
+               appProtocol  string
+               policyScheme string
+               wantScheme   string
+       }{
+               {
+                       name:         "preserves backend traffic policy scheme",
+                       appProtocol:  internaltypes.AppProtocolHTTP,
+                       policyScheme: apiv2.SchemeHTTPS,
+                       wantScheme:   apiv2.SchemeHTTPS,
+               },
+               {
+                       name:        "falls back to app protocol when scheme is 
unset",
+                       appProtocol: internaltypes.AppProtocolWSS,
+                       wantScheme:  apiv2.SchemeHTTPS,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       translator := NewTranslator(logr.Discard())
+                       tctx := 
provider.NewDefaultTranslateContext(context.Background())
+
+                       const (
+                               namespace   = "default"
+                               serviceName = "backend"
+                               portName    = "web"
+                               portNumber  = int32(8443)
+                       )
+
+                       serviceKey := types.NamespacedName{Namespace: 
namespace, Name: serviceName}
+                       tctx.Services[serviceKey] = &corev1.Service{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      serviceName,
+                                       Namespace: namespace,
+                               },
+                               Spec: corev1.ServiceSpec{
+                                       Ports: []corev1.ServicePort{{
+                                               Name:        portName,
+                                               Port:        portNumber,
+                                               AppProtocol: 
ptr.To(tt.appProtocol),
+                                       }},
+                               },
+                       }
+                       tctx.EndpointSlices[serviceKey] = 
[]discoveryv1.EndpointSlice{{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      "backend-1",
+                                       Namespace: namespace,
+                               },
+                               Ports: []discoveryv1.EndpointPort{{
+                                       Name: ptr.To(portName),
+                                       Port: ptr.To(portNumber),
+                               }},
+                               Endpoints: []discoveryv1.Endpoint{{
+                                       Addresses: []string{"10.0.0.1"},
+                                       Conditions: 
discoveryv1.EndpointConditions{
+                                               Ready: ptr.To(true),
+                                       },
+                               }},
+                       }}
+
+                       if tt.policyScheme != "" {
+                               tctx.BackendTrafficPolicies[serviceKey] = 
&v1alpha1.BackendTrafficPolicy{
+                                       ObjectMeta: metav1.ObjectMeta{
+                                               Name:      "backend-policy",
+                                               Namespace: namespace,
+                                       },
+                                       Spec: v1alpha1.BackendTrafficPolicySpec{
+                                               TargetRefs: 
[]v1alpha1.BackendPolicyTargetReferenceWithSectionName{{
+                                                       
LocalPolicyTargetReference: gatewayv1alpha2.LocalPolicyTargetReference{
+                                                               Name: 
gatewayv1alpha2.ObjectName(serviceName),
+                                                               Kind: 
gatewayv1alpha2.Kind(internaltypes.KindService),
+                                                       },
+                                               }},
+                                               Scheme: tt.policyScheme,
+                                       },
+                               }
+                       }
+
+                       route := &gatewayv1.HTTPRoute{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      "demo",
+                                       Namespace: namespace,
+                               },
+                               Spec: gatewayv1.HTTPRouteSpec{
+                                       Rules: []gatewayv1.HTTPRouteRule{{
+                                               BackendRefs: 
[]gatewayv1.HTTPBackendRef{{
+                                                       BackendRef: 
gatewayv1.BackendRef{
+                                                               
BackendObjectReference: gatewayv1.BackendObjectReference{
+                                                                       Name: 
gatewayv1.ObjectName(serviceName),
+                                                                       Port: 
ptr.To(gatewayv1.PortNumber(portNumber)),
+                                                               },
+                                                       },
+                                               }},
+                                       }},
+                               },
+                       }
+
+                       result, err := translator.TranslateHTTPRoute(tctx, 
route)
+                       require.NoError(t, err)
+                       require.Len(t, result.Services, 1)
+                       require.NotNil(t, result.Services[0].Upstream)
+
+                       assert.Equal(t, tt.wantScheme, 
result.Services[0].Upstream.Scheme)
+                       assert.Equal(t, "10.0.0.1", 
result.Services[0].Upstream.Nodes[0].Host)
+               })
+       }
+}
+
+func TestAttachBackendTrafficPolicyHealthCheck(t *testing.T) {
+       trueVal := true
+       falseVal := false
+
+       tests := []struct {
+               name       string
+               policy     *v1alpha1.BackendTrafficPolicy
+               wantChecks *adctypes.UpstreamHealthCheck
+       }{
+               {
+                       name:       "nil health check produces no checks",
+                       policy:     &v1alpha1.BackendTrafficPolicy{},
+                       wantChecks: nil,
+               },
+               {
+                       name: "active health check with all fields",
+                       policy: &v1alpha1.BackendTrafficPolicy{
+                               Spec: v1alpha1.BackendTrafficPolicySpec{
+                                       HealthCheck: &v1alpha1.HealthCheck{
+                                               Active: 
&v1alpha1.ActiveHealthCheck{
+                                                       Type:           "http",
+                                                       Timeout:        
metav1.Duration{Duration: 3 * time.Second},
+                                                       HTTPPath:       
"/healthz",
+                                                       Concurrency:    10,
+                                                       Host:           
"example.com",
+                                                       Port:           8080,
+                                                       StrictTLS:      
&trueVal,
+                                                       RequestHeaders: 
[]string{"X-Custom: value"},
+                                                       Healthy: 
&v1alpha1.ActiveHealthCheckHealthy{
+                                                               Interval: 
metav1.Duration{Duration: 5 * time.Second},
+                                                               
PassiveHealthCheckHealthy: v1alpha1.PassiveHealthCheckHealthy{
+                                                                       
HTTPCodes: []int{200, 201},
+                                                                       
Successes: 3,
+                                                               },
+                                                       },
+                                                       Unhealthy: 
&v1alpha1.ActiveHealthCheckUnhealthy{
+                                                               Interval: 
metav1.Duration{Duration: 2 * time.Second},
+                                                               
PassiveHealthCheckUnhealthy: v1alpha1.PassiveHealthCheckUnhealthy{
+                                                                       
HTTPCodes:    []int{500, 503},
+                                                                       
HTTPFailures: 5,
+                                                                       
TCPFailures:  2,
+                                                                       
Timeouts:     3,
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       wantChecks: &adctypes.UpstreamHealthCheck{
+                               Active: &adctypes.UpstreamActiveHealthCheck{
+                                       Type:                   "http",
+                                       Timeout:                3,
+                                       HTTPPath:               "/healthz",
+                                       Concurrency:            10,
+                                       Host:                   "example.com",
+                                       Port:                   8080,
+                                       HTTPSVerifyCertificate: true,
+                                       HTTPRequestHeaders:     
[]string{"X-Custom: value"},
+                                       Healthy: 
adctypes.UpstreamActiveHealthCheckHealthy{
+                                               Interval: 5,
+                                               
UpstreamPassiveHealthCheckHealthy: adctypes.UpstreamPassiveHealthCheckHealthy{
+                                                       HTTPStatuses: 
[]int{200, 201},
+                                                       Successes:    3,
+                                               },
+                                       },
+                                       Unhealthy: 
adctypes.UpstreamActiveHealthCheckUnhealthy{
+                                               Interval: 2,
+                                               
UpstreamPassiveHealthCheckUnhealthy: 
adctypes.UpstreamPassiveHealthCheckUnhealthy{
+                                                       HTTPStatuses: 
[]int{500, 503},
+                                                       HTTPFailures: 5,
+                                                       TCPFailures:  2,
+                                                       Timeouts:     3,
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       name: "strictTLS false disables certificate 
verification",
+                       policy: &v1alpha1.BackendTrafficPolicy{
+                               Spec: v1alpha1.BackendTrafficPolicySpec{
+                                       HealthCheck: &v1alpha1.HealthCheck{
+                                               Active: 
&v1alpha1.ActiveHealthCheck{
+                                                       StrictTLS: &falseVal,
+                                                       Healthy: 
&v1alpha1.ActiveHealthCheckHealthy{
+                                                               Interval: 
metav1.Duration{Duration: 1 * time.Second},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       wantChecks: &adctypes.UpstreamHealthCheck{
+                               Active: &adctypes.UpstreamActiveHealthCheck{
+                                       Type:                   "http",
+                                       HTTPSVerifyCertificate: false,
+                                       Healthy: 
adctypes.UpstreamActiveHealthCheckHealthy{
+                                               Interval: 1,
+                                       },
+                               },
+                       },
+               },
+               {
+                       name: "active and passive health checks together",
+                       policy: &v1alpha1.BackendTrafficPolicy{
+                               Spec: v1alpha1.BackendTrafficPolicySpec{
+                                       HealthCheck: &v1alpha1.HealthCheck{
+                                               Active: 
&v1alpha1.ActiveHealthCheck{
+                                                       Type: "tcp",
+                                                       Healthy: 
&v1alpha1.ActiveHealthCheckHealthy{
+                                                               Interval: 
metav1.Duration{Duration: 1 * time.Second},
+                                                       },
+                                               },
+                                               Passive: 
&v1alpha1.PassiveHealthCheck{
+                                                       Type: "http",
+                                                       Healthy: 
&v1alpha1.PassiveHealthCheckHealthy{
+                                                               HTTPCodes: 
[]int{200},
+                                                               Successes: 2,
+                                                       },
+                                                       Unhealthy: 
&v1alpha1.PassiveHealthCheckUnhealthy{
+                                                               HTTPCodes:    
[]int{500},
+                                                               HTTPFailures: 3,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       wantChecks: &adctypes.UpstreamHealthCheck{
+                               Active: &adctypes.UpstreamActiveHealthCheck{
+                                       Type:                   "tcp",
+                                       HTTPSVerifyCertificate: true,
+                                       Healthy: 
adctypes.UpstreamActiveHealthCheckHealthy{
+                                               Interval: 1,
+                                       },
+                               },
+                               Passive: &adctypes.UpstreamPassiveHealthCheck{
+                                       Type: "http",
+                                       Healthy: 
adctypes.UpstreamPassiveHealthCheckHealthy{
+                                               HTTPStatuses: []int{200},
+                                               Successes:    2,
+                                       },
+                                       Unhealthy: 
adctypes.UpstreamPassiveHealthCheckUnhealthy{
+                                               HTTPStatuses: []int{500},
+                                               HTTPFailures: 3,
+                                       },
+                               },
+                       },
+               },
+       }
+
+       translator := &Translator{Log: logr.Discard()}
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       ups := adctypes.NewDefaultUpstream()
+                       
translator.attachBackendTrafficPolicyToUpstream(tt.policy, ups)
+                       assert.Equal(t, tt.wantChecks, ups.Checks)
+               })
+       }
+}
diff --git a/internal/adc/translator/policies.go 
b/internal/adc/translator/policies.go
index 41706964..726fbfac 100644
--- a/internal/adc/translator/policies.go
+++ b/internal/adc/translator/policies.go
@@ -24,6 +24,7 @@ import (
 
        adctypes "github.com/apache/apisix-ingress-controller/api/adc"
        "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
 )
 
 func convertBackendRef(namespace, name, kind string) gatewayv1.BackendRef {
@@ -79,4 +80,92 @@ func (t *Translator) 
attachBackendTrafficPolicyToUpstream(policy *v1alpha1.Backe
                upstream.HashOn = policy.Spec.LoadBalancer.HashOn
                upstream.Key = policy.Spec.LoadBalancer.Key
        }
+       if policy.Spec.HealthCheck != nil {
+               upstream.Checks = 
translateBTPHealthCheck(policy.Spec.HealthCheck)
+       }
+}
+
+func translateBTPHealthCheck(hc *v1alpha1.HealthCheck) 
*adctypes.UpstreamHealthCheck {
+       if hc == nil || (hc.Active == nil && hc.Passive == nil) {
+               return nil
+       }
+       result := &adctypes.UpstreamHealthCheck{}
+       if hc.Active != nil {
+               result.Active = translateBTPActiveHealthCheck(hc.Active)
+       }
+       if hc.Passive != nil {
+               result.Passive = translateBTPPassiveHealthCheck(hc.Passive)
+       }
+       return result
+}
+
+func translateBTPActiveHealthCheck(config *v1alpha1.ActiveHealthCheck) 
*adctypes.UpstreamActiveHealthCheck {
+       t := config.Type
+       if t == "" {
+               t = "http"
+       }
+       active := &adctypes.UpstreamActiveHealthCheck{
+               Type:                   t,
+               Timeout:                int(config.Timeout.Seconds()),
+               Concurrency:            config.Concurrency,
+               Host:                   config.Host,
+               Port:                   config.Port,
+               HTTPPath:               config.HTTPPath,
+               HTTPSVerifyCertificate: config.StrictTLS == nil || 
*config.StrictTLS,
+               HTTPRequestHeaders:     config.RequestHeaders,
+       }
+       if config.Healthy != nil {
+               interval := config.Healthy.Interval.Duration
+               if interval < apiv2.ActiveHealthCheckMinInterval {
+                       interval = apiv2.ActiveHealthCheckMinInterval
+               }
+               active.Healthy = adctypes.UpstreamActiveHealthCheckHealthy{
+                       Interval: int(interval.Seconds()),
+                       UpstreamPassiveHealthCheckHealthy: 
adctypes.UpstreamPassiveHealthCheckHealthy{
+                               HTTPStatuses: config.Healthy.HTTPCodes,
+                               Successes:    config.Healthy.Successes,
+                       },
+               }
+       }
+       if config.Unhealthy != nil {
+               interval := config.Unhealthy.Interval.Duration
+               if interval < apiv2.ActiveHealthCheckMinInterval {
+                       interval = apiv2.ActiveHealthCheckMinInterval
+               }
+               active.Unhealthy = adctypes.UpstreamActiveHealthCheckUnhealthy{
+                       Interval: int(interval.Seconds()),
+                       UpstreamPassiveHealthCheckUnhealthy: 
adctypes.UpstreamPassiveHealthCheckUnhealthy{
+                               HTTPStatuses: config.Unhealthy.HTTPCodes,
+                               HTTPFailures: config.Unhealthy.HTTPFailures,
+                               TCPFailures:  config.Unhealthy.TCPFailures,
+                               Timeouts:     config.Unhealthy.Timeouts,
+                       },
+               }
+       }
+       return active
+}
+
+func translateBTPPassiveHealthCheck(config *v1alpha1.PassiveHealthCheck) 
*adctypes.UpstreamPassiveHealthCheck {
+       t := config.Type
+       if t == "" {
+               t = "http"
+       }
+       passive := &adctypes.UpstreamPassiveHealthCheck{
+               Type: t,
+       }
+       if config.Healthy != nil {
+               passive.Healthy = adctypes.UpstreamPassiveHealthCheckHealthy{
+                       HTTPStatuses: config.Healthy.HTTPCodes,
+                       Successes:    config.Healthy.Successes,
+               }
+       }
+       if config.Unhealthy != nil {
+               passive.Unhealthy = 
adctypes.UpstreamPassiveHealthCheckUnhealthy{
+                       HTTPStatuses: config.Unhealthy.HTTPCodes,
+                       HTTPFailures: config.Unhealthy.HTTPFailures,
+                       TCPFailures:  config.Unhealthy.TCPFailures,
+                       Timeouts:     config.Unhealthy.Timeouts,
+               }
+       }
+       return passive
 }
diff --git a/test/e2e/crds/v1alpha1/backendtrafficpolicy.go 
b/test/e2e/crds/v1alpha1/backendtrafficpolicy.go
index d26b6630..5704757e 100644
--- a/test/e2e/crds/v1alpha1/backendtrafficpolicy.go
+++ b/test/e2e/crds/v1alpha1/backendtrafficpolicy.go
@@ -18,6 +18,7 @@
 package v1alpha1
 
 import (
+       "context"
        "fmt"
        "time"
 
@@ -25,6 +26,7 @@ import (
        . "github.com/onsi/gomega"
        "k8s.io/apimachinery/pkg/types"
 
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
 )
 
@@ -57,6 +59,26 @@ spec:
     - name: httpbin-service-e2e-test
       port: 80
 `
+       var gatewayBeforeEach = func() {
+               By("create GatewayProxy")
+               err = s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("create GatewayClass")
+               err = s.CreateResourceFromString(s.GetGatewayClassYaml())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayClass")
+               time.Sleep(5 * time.Second)
+
+               By("create Gateway")
+               err = s.CreateResourceFromString(s.GetGatewayYaml())
+               Expect(err).NotTo(HaveOccurred(), "creating Gateway")
+               time.Sleep(5 * time.Second)
+
+               By("create HTTPRoute")
+               s.ApplyHTTPRoute(types.NamespacedName{Namespace: s.Namespace(), 
Name: "httpbin"}, fmt.Sprintf(defaultHTTPRoute, s.Namespace(), s.Namespace()))
+       }
+
        Context("Rewrite Upstream Host", func() {
                var createUpstreamHost = `
 apiVersion: apisix.apache.org/v1alpha1
@@ -86,26 +108,8 @@ spec:
   upstreamHost: httpbin.update.example.com
 `
 
-               BeforeEach(func() {
-                       gatewayName := s.Namespace()
-                       By("create GatewayProxy")
-                       err = 
s.CreateResourceFromString(s.GetGatewayProxySpec())
-                       Expect(err).NotTo(HaveOccurred(), "creating 
GatewayProxy")
-                       time.Sleep(time.Second)
-
-                       By("create GatewayClass")
-                       err = 
s.CreateResourceFromString(s.GetGatewayClassYaml())
-                       Expect(err).NotTo(HaveOccurred(), "creating 
GatewayClass")
-                       time.Sleep(time.Second)
-
-                       By("create Gateway")
-                       err = s.CreateResourceFromString(s.GetGatewayYaml())
-                       Expect(err).NotTo(HaveOccurred(), "creating Gateway")
-                       time.Sleep(time.Second)
-
-                       By("create HTTPRoute")
-                       s.ApplyHTTPRoute(types.NamespacedName{Namespace: 
s.Namespace(), Name: "httpbin"}, fmt.Sprintf(defaultHTTPRoute, gatewayName, 
s.Namespace()))
-               })
+               BeforeEach(gatewayBeforeEach)
+
                It("should rewrite upstream host", func() {
                        s.ResourceApplied("BackendTrafficPolicy", "httpbin", 
createUpstreamHost, 1)
                        s.RequestAssert(&scaffold.RequestAssert{
@@ -159,6 +163,178 @@ spec:
                        })
                })
        })
+
+       Context("Health Check", func() {
+               var policyWithActiveHealthCheck = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: httpbin
+spec:
+  targetRefs:
+  - name: httpbin-service-e2e-test
+    kind: Service
+    group: ""
+  healthCheck:
+    active:
+      type: http
+      httpPath: /get
+      healthy:
+        httpCodes: [200]
+        interval: 1s
+      unhealthy:
+        httpCodes: [500]
+        httpFailures: 2
+        interval: 1s
+`
+
+               var policyWithActiveAndPassiveHealthCheck = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+  name: httpbin
+spec:
+  targetRefs:
+  - name: httpbin-service-e2e-test
+    kind: Service
+    group: ""
+  healthCheck:
+    active:
+      type: http
+      httpPath: /get
+      healthy:
+        httpCodes: [200]
+        interval: 1s
+      unhealthy:
+        httpCodes: [500]
+        httpFailures: 2
+        interval: 1s
+    passive:
+      type: http
+      healthy:
+        httpCodes: [200]
+      unhealthy:
+        httpCodes: [502, 503]
+        httpFailures: 3
+`
+
+               BeforeEach(gatewayBeforeEach)
+
+               It("should configure active health check on upstream", func() {
+                       s.ResourceApplied("BackendTrafficPolicy", "httpbin", 
policyWithActiveHealthCheck, 1)
+
+                       // Trigger some traffic so APISIX registers the upstream
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/get",
+                               Host:   "httpbin.org",
+                               Checks: []scaffold.ResponseCheckFunc{
+                                       scaffold.WithExpectedStatus(200),
+                               },
+                       })
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := 
s.DefaultDataplaneResource().Upstream().List(context.Background())
+                       Expect(err).ToNot(HaveOccurred(), "listing upstreams")
+                       Expect(ups).NotTo(BeEmpty(), "upstreams should not be 
empty")
+
+                       var target *adctypes.Upstream
+                       for _, u := range ups {
+                               if u.Checks != nil {
+                                       target = u
+                                       break
+                               }
+                       }
+                       Expect(target).NotTo(BeNil(), "upstream with health 
check should exist")
+                       Expect(target.Checks.Active).NotTo(BeNil(), "active 
health check should be configured")
+                       Expect(target.Checks.Active.HTTPPath).To(Equal("/get"), 
"active health check http path")
+                       
Expect(target.Checks.Active.Healthy.Interval).To(Equal(1), "active healthy 
interval")
+                       
Expect(target.Checks.Active.Healthy.HTTPStatuses).To(Equal([]int{200}), "active 
healthy http codes")
+                       
Expect(target.Checks.Active.Unhealthy.Interval).To(Equal(1), "active unhealthy 
interval")
+                       
Expect(target.Checks.Active.Unhealthy.HTTPFailures).To(Equal(2), "active 
unhealthy http failures")
+                       
Expect(target.Checks.Active.Unhealthy.HTTPStatuses).To(Equal([]int{500}), 
"active unhealthy http codes")
+                       Expect(target.Checks.Passive).To(BeNil(), "passive 
health check should not be configured")
+               })
+
+               It("should configure active and passive health checks on 
upstream", func() {
+                       s.ResourceApplied("BackendTrafficPolicy", "httpbin", 
policyWithActiveAndPassiveHealthCheck, 1)
+
+                       // Trigger some traffic so APISIX registers the upstream
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/get",
+                               Host:   "httpbin.org",
+                               Checks: []scaffold.ResponseCheckFunc{
+                                       scaffold.WithExpectedStatus(200),
+                               },
+                       })
+                       time.Sleep(2 * time.Second)
+
+                       ups, err := 
s.DefaultDataplaneResource().Upstream().List(context.Background())
+                       Expect(err).ToNot(HaveOccurred(), "listing upstreams")
+                       Expect(ups).NotTo(BeEmpty(), "upstreams should not be 
empty")
+
+                       var target *adctypes.Upstream
+                       for _, u := range ups {
+                               if u.Checks != nil && u.Checks.Passive != nil {
+                                       target = u
+                                       break
+                               }
+                       }
+                       Expect(target).NotTo(BeNil(), "upstream with active and 
passive health check should exist")
+
+                       // Verify active health check
+                       Expect(target.Checks.Active).NotTo(BeNil(), "active 
health check should be configured")
+                       Expect(target.Checks.Active.HTTPPath).To(Equal("/get"), 
"active health check http path")
+                       
Expect(target.Checks.Active.Healthy.HTTPStatuses).To(Equal([]int{200}), "active 
healthy http codes")
+                       
Expect(target.Checks.Active.Unhealthy.HTTPFailures).To(Equal(2), "active 
unhealthy http failures")
+
+                       // Verify passive health check
+                       
Expect(target.Checks.Passive.Healthy.HTTPStatuses).To(Equal([]int{200}), 
"passive healthy http codes")
+                       
Expect(target.Checks.Passive.Unhealthy.HTTPStatuses).To(Equal([]int{502, 503}), 
"passive unhealthy http codes")
+                       
Expect(target.Checks.Passive.Unhealthy.HTTPFailures).To(Equal(3), "passive 
unhealthy http failures")
+               })
+
+               It("should remove health check when policy is deleted", func() {
+                       s.ResourceApplied("BackendTrafficPolicy", "httpbin", 
policyWithActiveHealthCheck, 1)
+
+                       // Trigger traffic to establish upstream
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/get",
+                               Host:   "httpbin.org",
+                               Checks: []scaffold.ResponseCheckFunc{
+                                       scaffold.WithExpectedStatus(200),
+                               },
+                       })
+                       time.Sleep(2 * time.Second)
+
+                       // Verify health check is present on the target upstream
+                       ups, err := 
s.DefaultDataplaneResource().Upstream().List(context.Background())
+                       Expect(err).ToNot(HaveOccurred())
+                       hasHealthCheck := false
+                       for _, u := range ups {
+                               if u.Checks != nil {
+                                       hasHealthCheck = true
+                                       break
+                               }
+                       }
+                       Expect(hasHealthCheck).To(BeTrue(), "upstream should 
have health check before policy deletion")
+
+                       // Delete the policy
+                       err = 
s.DeleteResourceFromString(policyWithActiveHealthCheck)
+                       Expect(err).NotTo(HaveOccurred(), "deleting 
BackendTrafficPolicy")
+                       time.Sleep(3 * time.Second)
+
+                       // Verify health check is removed from the target 
upstream
+                       ups, err = 
s.DefaultDataplaneResource().Upstream().List(context.Background())
+                       Expect(err).ToNot(HaveOccurred())
+                       Expect(ups).NotTo(BeEmpty(), "upstreams should still 
exist after policy deletion")
+                       for _, u := range ups {
+                               Expect(u.Checks).To(BeNil(), "upstream should 
not have health check after policy deletion")
+                       }
+               })
+       })
 })
 
 var _ = Describe("Test BackendTrafficPolicy base on Ingress", 
Label("apisix.apache.org", "v1alpha1", "backendtrafficpolicy"), func() {

Reply via email to