Repository: envoyproxy/gateway
Pull request #5374 (2025-02-28 07:47:43)

feat: implement HTTPUpgrade in BTP
xref: https://github.com/envoyproxy/gateway/pull/5300 
fixes: https://github.com/envoyproxy/gateway/issues/4859 https://github.com/envoyproxy/gateway/issues/5276

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5374?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `98.98990%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 65.11%. Comparing base [(`12c4390`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/12c4390bdeb4f92a331ac3d2ec11d13d84c6e4e7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`a41b12a`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/a41b12a191c40cded21a58b5ea77994e50baf995?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5374?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/gatewayapi/backendtrafficpolicy.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5374?src=pr&el=tree&filepath=internal%2Fgatewayapi%2Fbackendtrafficpolicy.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvZ2F0ZXdheWFwaS9iYWNrZW5kdHJhZmZpY3BvbGljeS5nbw==) | 98.63% | [1 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5374?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #5374   +/-   ##
=======================================
  Coverage   65.11%   65.11%           
=======================================
  Files         213      213           
  Lines       33586    33615   +29     
=======================================
+ Hits        21869    21890   +21     
- Misses      10392    10399    +7     
- Partials     1325     1326    +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/5374?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

zhaohuabing: Does this PR also address the HTTP CONNECT use case? Or HTTP CONNECT will be supported in a follow-up PR?
arkodg: > Does this PR also address the HTTP CONNECT use case? Or HTTP CONNECT will be supported in a follow-up PR?

hey @zhaohuabing I plan on addressing that in a follow up PR

Patch:
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index d230e2a4c..2d4c4ab5b 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -77,7 +77,6 @@ type BackendTrafficPolicySpec struct {
 	// If not specified, the default upgrade configuration(websocket) will be used.
 	//
 	// +optional
-	// +notImplementedHide
 	HTTPUpgrade []*ProtocolUpgradeConfig `json:"httpUpgrade,omitempty"`
 }
 
@@ -87,9 +86,8 @@ type ProtocolUpgradeConfig struct {
 	//
 	// +kubebuilder:validation:Required
 	Type string `json:"type"`
-	// Disabled indicates whether the upgrade is disabled.
-	// +optional
-	Disabled *bool `json:"disabled"`
+
+	// TODO: support more options for CONNECT
 }
 
 // BackendTrafficPolicyList contains a list of BackendTrafficPolicy resources.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 771ef318b..f2e87c3e3 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -633,7 +633,7 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
 			if (*in)[i] != nil {
 				in, out := &(*in)[i], &(*out)[i]
 				*out = new(ProtocolUpgradeConfig)
-				(*in).DeepCopyInto(*out)
+				**out = **in
 			}
 		}
 	}
@@ -4484,11 +4484,6 @@ func (in *ProcessingModeOptions) DeepCopy() *ProcessingModeOptions {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ProtocolUpgradeConfig) DeepCopyInto(out *ProtocolUpgradeConfig) {
 	*out = *in
-	if in.Disabled != nil {
-		in, out := &in.Disabled, &out.Disabled
-		*out = new(bool)
-		**out = **in
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProtocolUpgradeConfig.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 025e18e2f..f972d5860 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -538,9 +538,6 @@ spec:
                   If not specified, the default upgrade configuration(websocket) will be used.
                 items:
                   properties:
-                    disabled:
-                      description: Disabled indicates whether the upgrade is disabled.
-                      type: boolean
                     type:
                       description: |-
                         Type is the case-insensitive type of protocol upgrade.
diff --git a/examples/extension-server/go.mod b/examples/extension-server/go.mod
index 0db3fe9ed..bce1f25d1 100644
--- a/examples/extension-server/go.mod
+++ b/examples/extension-server/go.mod
@@ -10,7 +10,7 @@ require (
 	google.golang.org/grpc v1.71.0
 	google.golang.org/protobuf v1.36.5
 	k8s.io/apimachinery v0.33.0-alpha.3
-	sigs.k8s.io/controller-runtime v0.20.2
+	sigs.k8s.io/controller-runtime v0.20.3
 	sigs.k8s.io/gateway-api v1.2.1
 )
 
diff --git a/examples/extension-server/go.sum b/examples/extension-server/go.sum
index c90c926ef..60d0450b0 100644
--- a/examples/extension-server/go.sum
+++ b/examples/extension-server/go.sum
@@ -179,8 +179,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
 k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc=
-sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
+sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
+sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/controller-tools v0.17.1 h1:bQ+dKCS7jY9AgpefenBDtm6geJZCHVKbegpLynxgyus=
 sigs.k8s.io/controller-tools v0.17.1/go.mod h1:3QXAdrmdxYuQ4MifvbCAFD9wLXn7jylnfBPYS4yVDdc=
 sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM=
diff --git a/go.mod b/go.mod
index a46401598..6ed9fc590 100644
--- a/go.mod
+++ b/go.mod
@@ -75,7 +75,7 @@ require (
 	k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7
 	k8s.io/kubectl v0.32.2
 	k8s.io/utils v0.0.0-20241210054802-24370beab758
-	sigs.k8s.io/controller-runtime v0.20.2
+	sigs.k8s.io/controller-runtime v0.20.3
 	sigs.k8s.io/gateway-api v1.2.1
 	sigs.k8s.io/kubectl-validate v0.0.5-0.20241223122011-eb064d2f92d5
 	sigs.k8s.io/mcs-api v0.1.0
diff --git a/go.sum b/go.sum
index 9ebdfd47c..aa8a09fb7 100644
--- a/go.sum
+++ b/go.sum
@@ -1791,8 +1791,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 h1:uOuSLOMBWkJH0TWa9X6l+mj5nZdm6Ay6Bli8HL8rNfk=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
 sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
-sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc=
-sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
+sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI=
+sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250217160221-5e8256e05002 h1:vl1ohLP3ehNsJc/6X21vexTkPsH2jFHq1sPCATw15IU=
 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20250217160221-5e8256e05002/go.mod h1:QXw4XLB4ayZHsgXTf7cdyGzacNz9KQsdiI6apU+K07E=
 sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 1ef3d41d8..f2b395b65 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -63,7 +63,7 @@ func server(ctx context.Context, logOut io.Writer) error {
 		return nil
 	}
 	l := loader.New(cfgPath, cfg, hook)
-	if err := l.Start(ctx); err != nil {
+	if err := l.Start(ctx, logOut); err != nil {
 		return err
 	}
 
diff --git a/internal/envoygateway/config/loader/configloader.go b/internal/envoygateway/config/loader/configloader.go
index 5c381127a..e2e4e2d79 100644
--- a/internal/envoygateway/config/loader/configloader.go
+++ b/internal/envoygateway/config/loader/configloader.go
@@ -7,7 +7,7 @@ package loader
 
 import (
 	"context"
-	"os"
+	"io"
 	"time"
 
 	"github.com/envoyproxy/gateway/internal/envoygateway/config"
@@ -37,7 +37,7 @@ func New(cfgPath string, cfg *config.Server, f HookFunc) *Loader {
 	}
 }
 
-func (r *Loader) Start(ctx context.Context) error {
+func (r *Loader) Start(ctx context.Context, logOut io.Writer) error {
 	r.runHook()
 
 	if r.cfgPath == "" {
@@ -72,7 +72,7 @@ func (r *Loader) Start(ctx context.Context) error {
 				r.cfg.EnvoyGateway = eg
 				// update cfg logger
 				eg.Logging.SetEnvoyGatewayLoggingDefaults()
-				r.cfg.Logger = logging.NewLogger(os.Stdout, eg.Logging)
+				r.cfg.Logger = logging.NewLogger(logOut, eg.Logging)
 
 				// cancel last
 				if r.cancel != nil {
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 4d3c95c49..ca72ac828 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -292,21 +292,22 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 	resources *resource.Resources,
 ) error {
 	var (
-		rl        *ir.RateLimit
-		lb        *ir.LoadBalancer
-		pp        *ir.ProxyProtocol
-		hc        *ir.HealthCheck
-		cb        *ir.CircuitBreaker
-		fi        *ir.FaultInjection
-		to        *ir.Timeout
-		ka        *ir.TCPKeepalive
-		rt        *ir.Retry
-		bc        *ir.BackendConnection
-		ds        *ir.DNS
-		h2        *ir.HTTP2Settings
-		ro        *ir.ResponseOverride
-		cp        []*ir.Compression
-		err, errs error
+		rl          *ir.RateLimit
+		lb          *ir.LoadBalancer
+		pp          *ir.ProxyProtocol
+		hc          *ir.HealthCheck
+		cb          *ir.CircuitBreaker
+		fi          *ir.FaultInjection
+		to          *ir.Timeout
+		ka          *ir.TCPKeepalive
+		rt          *ir.Retry
+		bc          *ir.BackendConnection
+		ds          *ir.DNS
+		h2          *ir.HTTP2Settings
+		ro          *ir.ResponseOverride
+		cp          []*ir.Compression
+		httpUpgrade []string
+		err, errs   error
 	)
 
 	// Build IR
@@ -359,6 +360,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 		errs = errors.Join(errs, err)
 	}
 	cp = buildCompression(policy.Spec.Compression)
+	httpUpgrade = buildHTTPProtocolUpgradeConfig(policy.Spec.HTTPUpgrade)
 
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
@@ -423,6 +425,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 						Timeout:           to,
 						ResponseOverride:  ro,
 						Compression:       cp,
+						HTTPUpgrade:       httpUpgrade,
 					}
 
 					// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -440,27 +443,25 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(
 }
 
 func (t *Translator) translateBackendTrafficPolicyForGateway(
-	policy *egv1a1.BackendTrafficPolicy,
-	target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
-	gateway *GatewayContext,
-	xdsIR resource.XdsIRMap,
-	resources *resource.Resources,
+	policy *egv1a1.BackendTrafficPolicy, target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
+	gateway *GatewayContext, xdsIR resource.XdsIRMap, resources *resource.Resources,
 ) error {
 	var (
-		rl        *ir.RateLimit
-		lb        *ir.LoadBalancer
-		pp        *ir.ProxyProtocol
-		hc        *ir.HealthCheck
-		cb        *ir.CircuitBreaker
-		fi        *ir.FaultInjection
-		ct        *ir.Timeout
-		ka        *ir.TCPKeepalive
-		rt        *ir.Retry
-		ds        *ir.DNS
-		h2        *ir.HTTP2Settings
-		ro        *ir.ResponseOverride
-		cp        []*ir.Compression
-		err, errs error
+		rl          *ir.RateLimit
+		lb          *ir.LoadBalancer
+		pp          *ir.ProxyProtocol
+		hc          *ir.HealthCheck
+		cb          *ir.CircuitBreaker
+		fi          *ir.FaultInjection
+		ct          *ir.Timeout
+		ka          *ir.TCPKeepalive
+		rt          *ir.Retry
+		ds          *ir.DNS
+		h2          *ir.HTTP2Settings
+		ro          *ir.ResponseOverride
+		cp          []*ir.Compression
+		httpUpgrade []string
+		err, errs   error
 	)
 
 	// Build IR
@@ -506,6 +507,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(
 		errs = errors.Join(errs, err)
 	}
 	cp = buildCompression(policy.Spec.Compression)
+	httpUpgrade = buildHTTPProtocolUpgradeConfig(policy.Spec.HTTPUpgrade)
 
 	ds = translateDNS(policy.Spec.ClusterSettings)
 
@@ -591,6 +593,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(
 				DNS:              ds,
 				ResponseOverride: ro,
 				Compression:      cp,
+				HTTPUpgrade:      httpUpgrade,
 			}
 
 			// Update the Host field in HealthCheck, now that we have access to the Route Hostname.
@@ -971,3 +974,16 @@ func buildCompression(compression []*egv1a1.Compression) []*ir.Compression {
 
 	return irCompression
 }
+
+func buildHTTPProtocolUpgradeConfig(cfgs []*egv1a1.ProtocolUpgradeConfig) []string {
+	if len(cfgs) == 0 {
+		return nil
+	}
+
+	result := make([]string, 0, len(cfgs))
+	for _, cfg := range cfgs {
+		result = append(result, cfg.Type)
+	}
+
+	return result
+}
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index 15a8e28c8..e37a9e82c 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -828,6 +828,8 @@ type TrafficFeatures struct {
 	ResponseOverride *ResponseOverride `json:"responseOverride,omitempty" yaml:"responseOverride,omitempty"`
 	// Compression settings for HTTP Response
 	Compression []*Compression `json:"compression,omitempty" yaml:"compression,omitempty"`
+	// HTTPUpgrade defines the schema for upgrading the HTTP protocol.
+	HTTPUpgrade []string `json:"httpUpgrade,omitempty" yaml:"httpUpgrade,omitempty"`
 }
 
 func (b *TrafficFeatures) Validate() error {
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index c2ae3f040..656141da0 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -3658,6 +3658,11 @@ func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
 			}
 		}
 	}
+	if in.HTTPUpgrade != nil {
+		in, out := &in.HTTPUpgrade, &out.HTTPUpgrade
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficFeatures.
diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go
index 7afe71278..ad698ba79 100644
--- a/internal/provider/kubernetes/indexers.go
+++ b/internal/provider/kubernetes/indexers.go
@@ -111,7 +111,7 @@ func backendHTTPRouteIndexFunc(rawObj client.Object) []string {
 	var backendRefs []string
 	for _, rule := range httproute.Spec.Rules {
 		for _, backend := range rule.BackendRefs {
-			if backend.Kind == nil || string(*backend.Kind) == resource.KindService || string(*backend.Kind) == egv1a1.KindBackend {
+			if backend.Kind == nil || string(*backend.Kind) == resource.KindService || string(*backend.Kind) == resource.KindServiceImport || string(*backend.Kind) == egv1a1.KindBackend {
 				backendRefs = append(backendRefs,
 					types.NamespacedName{
 						Namespace: gatewayapi.NamespaceDerefOr(backend.Namespace, httproute.Namespace),
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 2bff31761..722e1aa91 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -26,8 +26,18 @@ const (
 	retryDefaultRetryOn             = "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes"
 	retryDefaultRetriableStatusCode = 503
 	retryDefaultNumRetries          = 2
+
+	websocketUpgradeType = "websocket"
 )
 
+// Allow websocket upgrades for HTTP 1.1
+// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
+var defaultUpgradeConfig = []*routev3.RouteAction_UpgradeConfig{
+	{
+		UpgradeType: websocketUpgradeType,
+	},
+}
+
 func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 	router := &routev3.Route{
 		Name:     httpRoute.Name,
@@ -61,13 +71,7 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 		}
 
 		if !httpRoute.IsHTTP2 {
-			// Allow websocket upgrades for HTTP 1.1
-			// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
-			routeAction.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{
-				{
-					UpgradeType: "websocket",
-				},
-			}
+			routeAction.UpgradeConfigs = buildUpgradeConfig(httpRoute.Traffic)
 		}
 
 		router.Action = &routev3.Route_Route{Route: routeAction}
@@ -80,13 +84,7 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 			routeAction.RequestMirrorPolicies = buildXdsRequestMirrorPolicies(httpRoute.Mirrors)
 		}
 		if !httpRoute.IsHTTP2 {
-			// Allow websocket upgrades for HTTP 1.1
-			// Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
-			routeAction.UpgradeConfigs = []*routev3.RouteAction_UpgradeConfig{
-				{
-					UpgradeType: "websocket",
-				},
-			}
+			routeAction.UpgradeConfigs = buildUpgradeConfig(httpRoute.Traffic)
 		}
 		router.Action = &routev3.Route_Route{Route: routeAction}
 	}
@@ -122,6 +120,21 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) (*routev3.Route, error) {
 	return router, nil
 }
 
+func buildUpgradeConfig(trafficFeatures *ir.TrafficFeatures) []*routev3.RouteAction_UpgradeConfig {
+	if trafficFeatures == nil || trafficFeatures.HTTPUpgrade == nil {
+		return defaultUpgradeConfig
+	}
+
+	upgradeConfigs := make([]*routev3.RouteAction_UpgradeConfig, 0, len(trafficFeatures.HTTPUpgrade))
+	for _, protocol := range trafficFeatures.HTTPUpgrade {
+		upgradeConfigs = append(upgradeConfigs, &routev3.RouteAction_UpgradeConfig{
+			UpgradeType: protocol,
+		})
+	}
+
+	return upgradeConfigs
+}
+
 func buildXdsRouteMatch(pathMatch *ir.StringMatch, headerMatches []*ir.StringMatch, queryParamMatches []*ir.StringMatch) *routev3.RouteMatch {
 	outMatch := &routev3.RouteMatch{}
 
@@ -236,7 +249,7 @@ func buildXdsRouteAction(backendWeights *ir.BackendWeights, settings []*ir.Desti
 }
 
 func buildXdsWeightedRouteAction(backendWeights *ir.BackendWeights, settings []*ir.DestinationSetting) *routev3.RouteAction {
-	weightedClusters := []*routev3.WeightedCluster_ClusterWeight{}
+	weightedClusters := make([]*routev3.WeightedCluster_ClusterWeight, 0, len(settings))
 	if backendWeights.Invalid > 0 {
 		invalidCluster := &routev3.WeightedCluster_ClusterWeight{
 			Name:   "invalid-backend-cluster",
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index 5a42fb7da..b7c4d81af 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -30,6 +30,7 @@ bug fixes: |
   Fix translating backendSettings for extAuth
   Fix an issue that stats compressor was not working.
   Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode.
+  Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated.
 
 # Enhancements that improve performance.
 performance improvements: |


Test patch:
diff --git a/internal/envoygateway/config/loader/configloader_test.go b/internal/envoygateway/config/loader/configloader_test.go
index d0420df0f..b5aa30bf1 100644
--- a/internal/envoygateway/config/loader/configloader_test.go
+++ b/internal/envoygateway/config/loader/configloader_test.go
@@ -50,7 +50,7 @@ func TestConfigLoader(t *testing.T) {
 		return nil
 	})
 
-	require.NoError(t, loader.Start(ctx))
+	require.NoError(t, loader.Start(ctx, os.Stdout))
 	go func() {
 		_ = os.WriteFile(cfgPath, []byte(redisConfig), 0o600)
 	}()
diff --git a/internal/gatewayapi/backendtrafficpolicy_test.go b/internal/gatewayapi/backendtrafficpolicy_test.go
index e104f3b0b..c03796f57 100644
--- a/internal/gatewayapi/backendtrafficpolicy_test.go
+++ b/internal/gatewayapi/backendtrafficpolicy_test.go
@@ -132,3 +132,57 @@ func Test_translateRateLimitCost(t *testing.T) {
 		})
 	}
 }
+
+func TestBuildHTTPProtocolUpgradeConfig(t *testing.T) {
+	cases := []struct {
+		name     string
+		cfgs     []*egv1a1.ProtocolUpgradeConfig
+		expected []string
+	}{
+		{
+			name:     "empty",
+			cfgs:     nil,
+			expected: nil,
+		},
+		{
+			name: "spdy",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "spdy/3.1",
+				},
+			},
+			expected: []string{"spdy/3.1"},
+		},
+		{
+			name: "websockets-spdy",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "websockets",
+				},
+				{
+					Type: "spdy/3.1",
+				},
+			},
+			expected: []string{"websockets", "spdy/3.1"},
+		},
+		{
+			name: "spdy-websockets",
+			cfgs: []*egv1a1.ProtocolUpgradeConfig{
+				{
+					Type: "spdy/3.1",
+				},
+				{
+					Type: "websockets",
+				},
+			},
+			expected: []string{"spdy/3.1", "websockets"},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := buildHTTPProtocolUpgradeConfig(tc.cfgs)
+			require.Equal(t, tc.expected, got)
+		})
+	}
+}
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml
new file mode 100644
index 000000000..72650c5dc
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.in.yaml
@@ -0,0 +1,48 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      httpUpgrade:
+        - type: "spdy/3.1"
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml
new file mode 100644
index 000000000..dcb4b20e0
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml
@@ -0,0 +1,175 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    httpUpgrade:
+    - type: spdy/3.1
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            name: httproute/default/httproute-1/rule/0/backend/0
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+          - spdy/3.1
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml
new file mode 100644
index 000000000..230d2358d
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.in.yaml
@@ -0,0 +1,49 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-1
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      httpUpgrade:
+        - type: websocket
+        - type: spdy/3.1
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml
new file mode 100644
index 000000000..ea1d4d6a6
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml
@@ -0,0 +1,177 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    httpUpgrade:
+    - type: websocket
+    - type: spdy/3.1
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      json:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            name: httproute/default/httproute-1/rule/0/backend/0
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+          - websocket
+          - spdy/3.1
+    readyListener:
+      address: 0.0.0.0
+      ipFamily: IPv4
+      path: /ready
+      port: 19003
diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go
index 541237c2f..4665f510e 100644
--- a/internal/provider/kubernetes/kubernetes_test.go
+++ b/internal/provider/kubernetes/kubernetes_test.go
@@ -1429,20 +1429,22 @@ func TestNamespaceSelectorProvider(t *testing.T) {
 		require.NoError(t, cli.Delete(ctx, nonWatchedSvc))
 	}()
 
+	watchedServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: watchedSvc.Name}, 80)
 	watchedHTTPRoute := test.GetHTTPRoute(types.NamespacedName{
 		Namespace: watchedNS.Name,
 		Name:      "watched-http-route",
-	}, watchedGateway.Name, types.NamespacedName{Name: watchedSvc.Name}, 80, "")
+	}, watchedGateway.Name, watchedServiceBackendRef, "")
 
 	require.NoError(t, cli.Create(ctx, watchedHTTPRoute))
 	defer func() {
 		require.NoError(t, cli.Delete(ctx, watchedHTTPRoute))
 	}()
 
+	nonWatchedServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: nonWatchedSvc.Name}, 8001)
 	nonWatchedHTTPRoute := test.GetHTTPRoute(types.NamespacedName{
 		Namespace: nonWatchedNS.Name,
 		Name:      "non-watched-http-route",
-	}, nonWatchedGateway.Name, types.NamespacedName{Name: nonWatchedSvc.Name}, 8001, "")
+	}, nonWatchedGateway.Name, nonWatchedServiceBackendRef, "")
 	require.NoError(t, cli.Create(ctx, nonWatchedHTTPRoute))
 	defer func() {
 		require.NoError(t, cli.Delete(ctx, nonWatchedHTTPRoute))
diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go
index dc0d70cdc..61a564957 100644
--- a/internal/provider/kubernetes/predicates_test.go
+++ b/internal/provider/kubernetes/predicates_test.go
@@ -123,11 +123,12 @@ func TestGatewayClassHasMatchingNamespaceLabels(t *testing.T) {
 				Build(),
 		}
 		t.Run(tc.name, func(t *testing.T) {
+			sampleServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: "service"}, 80)
 			res := r.hasMatchingNamespaceLabels(
 				test.GetHTTPRoute(types.NamespacedName{
 					Namespace: ns,
 					Name:      "httproute-test",
-				}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""))
+				}, "scheduled-status-test", sampleServiceBackendRef, ""))
 			require.Equal(t, tc.expect, res)
 		})
 	}
@@ -412,7 +413,10 @@ func TestValidateSecretForReconcile(t *testing.T) {
 // TestValidateEndpointSliceForReconcile tests the validateEndpointSliceForReconcile
 // predicate function.
 func TestValidateEndpointSliceForReconcile(t *testing.T) {
+	sampleGatewayClass := test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil)
 	sampleGateway := test.GetGateway(types.NamespacedName{Namespace: "default", Name: "scheduled-status-test"}, "test-gc", 8080)
+	sampleServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: "service"}, 80)
+	sampleServiceImportBackendRef := test.GetServiceImportBackendRef(types.NamespacedName{Name: "imported-service"}, 80)
 
 	testCases := []struct {
 		name          string
@@ -423,30 +427,40 @@ func TestValidateEndpointSliceForReconcile(t *testing.T) {
 		{
 			name: "route service but no routes exist",
 			configs: []client.Object{
-				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
+				sampleGatewayClass,
 				sampleGateway,
 			},
-			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service"),
+			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service", false),
 			expect:        false,
 		},
 		{
 			name: "http route service routes exist, but endpointslice is associated with another service",
 			configs: []client.Object{
-				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
+				sampleGatewayClass,
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceBackendRef, ""),
 			},
-			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "other-service"),
+			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "other-service", false),
 			expect:        false,
 		},
 		{
 			name: "http route service routes exist",
 			configs: []client.Object{
-				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
+				sampleGatewayClass,
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceBackendRef, ""),
 			},
-			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service"),
+			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "service", false),
+			expect:        true,
+		},
+		{
+			name: "http route serviceimport routes exist",
+			configs: []client.Object{
+				sampleGatewayClass,
+				sampleGateway,
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceImportBackendRef, ""),
+			},
+			endpointSlice: test.GetEndpointSlice(types.NamespacedName{Name: "endpointslice"}, "imported-service", true),
 			expect:        true,
 		},
 	}
@@ -551,6 +565,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			},
 		},
 	}
+	sampleServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: "service"}, 80)
 
 	testCases := []struct {
 		name    string
@@ -614,7 +629,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
 				sampleGateway,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceBackendRef, ""),
 			},
 			service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
 			expect:  true,
@@ -626,7 +641,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 			name: "route service routes exist but with non-existing gateway reference",
 			configs: []client.Object{
 				test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, ""),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceBackendRef, ""),
 			},
 			service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
 			expect:  true,
@@ -1036,13 +1051,17 @@ func TestCheckObjectNamespaceLabels(t *testing.T) {
 	}{
 		{
 			name: "matching labels of namespace of the object is a subset of namespaceLabels",
-			object: test.GetHTTPRoute(types.NamespacedName{
-				Name:      "foo-route",
-				Namespace: "foo",
-			}, "eg", types.NamespacedName{
-				Name:      "foo-svc",
-				Namespace: "foo",
-			}, 8080, ""),
+			object: test.GetHTTPRoute(
+				types.NamespacedName{
+					Name:      "foo-route",
+					Namespace: "foo",
+				},
+				"eg",
+				test.GetServiceBackendRef(types.NamespacedName{
+					Name:      "foo-svc",
+					Namespace: "foo",
+				}, 8080),
+				""),
 			ns: &corev1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: "foo",
@@ -1056,13 +1075,17 @@ func TestCheckObjectNamespaceLabels(t *testing.T) {
 		},
 		{
 			name: "non-matching labels of namespace of the object is a subset of namespaceLabels",
-			object: test.GetHTTPRoute(types.NamespacedName{
-				Name:      "bar-route",
-				Namespace: "bar",
-			}, "eg", types.NamespacedName{
-				Name:      "bar-svc",
-				Namespace: "bar",
-			}, 8080, ""),
+			object: test.GetHTTPRoute(
+				types.NamespacedName{
+					Name:      "bar-route",
+					Namespace: "bar",
+				},
+				"eg",
+				test.GetServiceBackendRef(types.NamespacedName{
+					Name:      "bar-svc",
+					Namespace: "bar",
+				}, 8080),
+				""),
 			ns: &corev1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: "bar",
@@ -1216,6 +1239,7 @@ func TestValidateHTTPRouteFilerForReconcile(t *testing.T) {
 	sampleGWC := test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil)
 	sampleGateway := test.GetGateway(types.NamespacedName{Namespace: "default", Name: "scheduled-status-test"}, "test-gc", 8080)
 	sampleService := test.GetService(types.NamespacedName{Name: "service"}, nil, nil)
+	sampleServiceBackendRef := test.GetServiceBackendRef(types.NamespacedName{Name: "service"}, 80)
 	sampleHTTPRouteFilter := test.GetHTTPRouteFilter(types.NamespacedName{Name: "httproutefilter"})
 
 	testCases := []struct {
@@ -1242,7 +1266,7 @@ func TestValidateHTTPRouteFilerForReconcile(t *testing.T) {
 				sampleGateway,
 				sampleService,
 				sampleHTTPRouteFilter,
-				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", types.NamespacedName{Name: "service"}, 80, "httproutefilter"),
+				test.GetHTTPRoute(types.NamespacedName{Name: "httproute-test"}, "scheduled-status-test", sampleServiceBackendRef, "httproutefilter"),
 			},
 			httpRouteFilter: sampleHTTPRouteFilter,
 			expect:          true,
diff --git a/internal/provider/kubernetes/test/utils.go b/internal/provider/kubernetes/test/utils.go
index 77bc50c5e..a6b2ef325 100644
--- a/internal/provider/kubernetes/test/utils.go
+++ b/internal/provider/kubernetes/test/utils.go
@@ -15,8 +15,11 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	mcsapiv1a1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/gatewayapi"
+	"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
 )
 
 type GroupKindNamespacedName struct {
@@ -107,8 +110,24 @@ func GetSecret(nsName types.NamespacedName) *corev1.Secret {
 	}
 }
 
+func GetServiceBackendRef(name types.NamespacedName, port int32) gwapiv1.BackendObjectReference {
+	return gwapiv1.BackendObjectReference{
+		Name: gwapiv1.ObjectName(name.Name),
+		Port: ptr.To(gwapiv1.PortNumber(port)),
+	}
+}
+
+func GetServiceImportBackendRef(name types.NamespacedName, port int32) gwapiv1.BackendObjectReference {
+	return gwapiv1.BackendObjectReference{
+		Name:  gwapiv1.ObjectName(name.Name),
+		Port:  ptr.To(gwapiv1.PortNumber(port)),
+		Kind:  gatewayapi.KindPtr(resource.KindServiceImport),
+		Group: gatewayapi.GroupPtr(mcsapiv1a1.GroupName),
+	}
+}
+
 // GetHTTPRoute returns a sample HTTPRoute with a parent reference.
-func GetHTTPRoute(nsName types.NamespacedName, parent string, serviceName types.NamespacedName, port int32, httpRouteFilterName string) *gwapiv1.HTTPRoute {
+func GetHTTPRoute(nsName types.NamespacedName, parent string, backendRef gwapiv1.BackendObjectReference, httpRouteFilterName string) *gwapiv1.HTTPRoute {
 	httpRoute := &gwapiv1.HTTPRoute{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: nsName.Namespace,
@@ -125,10 +144,7 @@ func GetHTTPRoute(nsName types.NamespacedName, parent string, serviceName types.
 					BackendRefs: []gwapiv1.HTTPBackendRef{
 						{
 							BackendRef: gwapiv1.BackendRef{
-								BackendObjectReference: gwapiv1.BackendObjectReference{
-									Name: gwapiv1.ObjectName(serviceName.Name),
-									Port: ptr.To(gwapiv1.PortNumber(port)),
-								},
+								BackendObjectReference: backendRef,
 							},
 						},
 					},
@@ -349,12 +365,19 @@ func GetService(nsName types.NamespacedName, labels map[string]string, ports map
 }
 
 // GetEndpointSlice returns a sample EndpointSlice.
-func GetEndpointSlice(nsName types.NamespacedName, svcName string) *discoveryv1.EndpointSlice {
+func GetEndpointSlice(nsName types.NamespacedName, svcName string, isServiceImport bool) *discoveryv1.EndpointSlice {
+	var labels map[string]string
+	if isServiceImport {
+		labels = map[string]string{mcsapiv1a1.LabelServiceName: svcName}
+	} else {
+		labels = map[string]string{discoveryv1.LabelServiceName: svcName}
+	}
+
 	return &discoveryv1.EndpointSlice{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      nsName.Name,
 			Namespace: nsName.Namespace,
-			Labels:    map[string]string{discoveryv1.LabelServiceName: svcName},
+			Labels:    labels,
 		},
 		Endpoints: []discoveryv1.Endpoint{
 			{
diff --git a/internal/xds/translator/route_test.go b/internal/xds/translator/route_test.go
index 3d7d6a972..817426c97 100644
--- a/internal/xds/translator/route_test.go
+++ b/internal/xds/translator/route_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/ir"
 )
 
-func Test_buildHashPolicy(t *testing.T) {
+func TestBuildHashPolicy(t *testing.T) {
 	tests := []struct {
 		name      string
 		httpRoute *ir.HTTPRoute
@@ -102,3 +102,72 @@ func Test_buildHashPolicy(t *testing.T) {
 		})
 	}
 }
+
+func TestBuildUpgradeConfig(t *testing.T) {
+	cases := []struct {
+		name           string
+		trafficFeature *ir.TrafficFeatures
+		expected       []*routev3.RouteAction_UpgradeConfig
+	}{
+		{
+			name:           "default",
+			trafficFeature: nil,
+			expected:       defaultUpgradeConfig,
+		},
+		{
+			name: "empty",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: nil,
+			},
+			expected: defaultUpgradeConfig,
+		},
+		{
+			name: "spdy",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"spdy/3.1"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "spdy/3.1",
+				},
+			},
+		},
+		{
+			name: "spdy-websocket",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"spdy/3.1", "websocket"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "spdy/3.1",
+				},
+				{
+					UpgradeType: "websocket",
+				},
+			},
+		},
+		{
+			name: "websocket-spdy",
+			trafficFeature: &ir.TrafficFeatures{
+				HTTPUpgrade: []string{"websocket", "spdy/3.1"},
+			},
+			expected: []*routev3.RouteAction_UpgradeConfig{
+				{
+					UpgradeType: "websocket",
+				},
+				{
+					UpgradeType: "spdy/3.1",
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := buildUpgradeConfig(tc.trafficFeature)
+			if !reflect.DeepEqual(got, tc.expected) {
+				t.Errorf("buildUpgradeConfig() got = %v, want %v", got, tc.expected)
+			}
+		})
+	}
+}
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml
new file mode 100644
index 000000000..ce44231bd
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-spdy.yaml
@@ -0,0 +1,40 @@
+http:
+  - address: 0.0.0.0
+    hostnames:
+      - "*"
+    isHTTP2: false
+    metadata:
+      kind: Gateway
+      name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    name: envoy-gateway/gateway-1/http
+    path:
+      escapedSlashesAction: UnescapeAndRedirect
+      mergeSlashes: true
+    port: 10080
+    routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+            - addressType: IP
+              endpoints:
+                - host: 7.7.7.7
+                  port: 8080
+              protocol: HTTP
+              weight: 1
+              name: httproute/default/httproute-1/rule/0/backend/0
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+            - "spdy/3.1"
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml
new file mode 100644
index 000000000..8bac62745
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-upgrade-websocket-spdy.yaml
@@ -0,0 +1,41 @@
+http:
+  - address: 0.0.0.0
+    hostnames:
+      - "*"
+    isHTTP2: false
+    metadata:
+      kind: Gateway
+      name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    name: envoy-gateway/gateway-1/http
+    path:
+      escapedSlashesAction: UnescapeAndRedirect
+      mergeSlashes: true
+    port: 10080
+    routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+            - addressType: IP
+              endpoints:
+                - host: 7.7.7.7
+                  port: 8080
+              protocol: HTTP
+              weight: 1
+              name: httproute/default/httproute-1/rule/0/backend/0
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          httpUpgrade:
+            - "websocket"
+            - "spdy/3.1"
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml
new file mode 100644
index 000000000..c24d059ee
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml
new file mode 100644
index 000000000..09426a317
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.listeners.yaml
@@ -0,0 +1,34 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml
new file mode 100644
index 000000000..f447ef4e5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-spdy.routes.yaml
@@ -0,0 +1,29 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: spdy/3.1
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml
new file mode 100644
index 000000000..c24d059ee
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_PREFERRED
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: httproute/default/httproute-1/rule/0
+  ignoreHealthOnHostRemoval: true
+  lbPolicy: LEAST_REQUEST
+  name: httproute/default/httproute-1/rule/0
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml
new file mode 100644
index 000000000..29bb6b4e4
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: httproute/default/httproute-1/rule/0
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 7.7.7.7
+            portValue: 8080
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: httproute/default/httproute-1/rule/0/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml
new file mode 100644
index 000000000..09426a317
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.listeners.yaml
@@ -0,0 +1,34 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: envoy-gateway/gateway-1/http
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: envoy-gateway/gateway-1/http
+  name: envoy-gateway/gateway-1/http
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml
new file mode 100644
index 000000000..20c2467f5
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-upgrade-websocket-spdy.routes.yaml
@@ -0,0 +1,30 @@
+- ignorePortInHostMatching: true
+  name: envoy-gateway/gateway-1/http
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    metadata:
+      filterMetadata:
+        envoy-gateway:
+          resources:
+          - kind: Gateway
+            name: gateway-1
+            namespace: envoy-gateway
+            sectionName: http
+    name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io
+    routes:
+    - match:
+        prefix: /
+      metadata:
+        filterMetadata:
+          envoy-gateway:
+            resources:
+            - kind: HTTPRoute
+              name: httproute-1
+              namespace: default
+      name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+      route:
+        cluster: httproute/default/httproute-1/rule/0
+        upgradeConfigs:
+        - upgradeType: websocket
+        - upgradeType: spdy/3.1
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index e5889126e..81cb93e69 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -487,6 +487,7 @@ _Appears in:_
 | `useClientProtocol` | _boolean_ |  false  |  | UseClientProtocol configures Envoy to prefer sending requests to backends using<br />the same HTTP protocol that the incoming request used. Defaults to false, which means<br />that Envoy will use the protocol indicated by the attached BackendRef. |
 | `compression` | _[Compression](#compression) array_ |  false  |  | The compression config for the http streams. |
 | `responseOverride` | _[ResponseOverride](#responseoverride) array_ |  false  |  | ResponseOverride defines the configuration to override specific responses with a custom one.<br />If multiple configurations are specified, the first one to match wins. |
+| `httpUpgrade` | _[ProtocolUpgradeConfig](#protocolupgradeconfig) array_ |  false  |  | HTTPUpgrade defines the configuration for HTTP protocol upgrades.<br />If not specified, the default upgrade configuration(websocket) will be used. |
 
 
 #### BasicAuth
@@ -3221,7 +3222,6 @@ _Appears in:_
 | Field | Type | Required | Default | Description |
 | ---   | ---  | ---      | ---     | ---         |
 | `type` | _string_ |  true  |  | Type is the case-insensitive type of protocol upgrade.<br />e.g. `websocket`, `CONNECT`, `spdy/3.1` etc. |
-| `disabled` | _boolean_ |  false  |  | Disabled indicates whether the upgrade is disabled. |
 
 
 #### ProviderType