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 51077751 feat(gateway-api): support GRPCRoute (#2570)
51077751 is described below

commit 51077751aab206f8719031dab7bc6dd75b959941
Author: AlinsRan <[email protected]>
AuthorDate: Tue Sep 23 16:03:16 2025 +0800

    feat(gateway-api): support GRPCRoute (#2570)
---
 Makefile                                           |   2 +-
 api/adc/types.go                                   |  26 +-
 config/rbac/role.yaml                              |  21 +-
 docs/en/latest/concepts/gateway-api.md             |   2 +-
 go.mod                                             |   2 +-
 internal/adc/translator/grpcroute.go               | 372 +++++++++++++++++++++
 internal/adc/translator/httproute.go               |  74 ++--
 internal/controller/context.go                     |   4 +-
 ...route_controller.go => grpcroute_controller.go} | 372 ++++++++-------------
 internal/controller/httproute_controller.go        |   6 +-
 internal/controller/indexer/grpcroute.go           | 106 ++++++
 internal/controller/indexer/indexer.go             |   1 +
 internal/controller/utils.go                       |   9 +-
 internal/manager/controllers.go                    |  17 +-
 internal/provider/apisix/provider.go               |   5 +-
 internal/provider/apisix/status.go                 |  35 ++
 internal/provider/provider.go                      |   1 +
 internal/types/k8s.go                              |   8 +-
 test/e2e/framework/grpc.go                         |  58 ++++
 test/e2e/framework/manifests/grpc-backend.yaml     |  63 ++++
 test/e2e/framework/manifests/ingress.yaml          |  86 +----
 test/e2e/gatewayapi/grpcroute.go                   | 289 ++++++++++++++++
 test/e2e/scaffold/grpc.go                          | 159 +++++++++
 23 files changed, 1337 insertions(+), 381 deletions(-)

diff --git a/Makefile b/Makefile
index 59f1d7b6..e82b876a 100644
--- a/Makefile
+++ b/Makefile
@@ -55,7 +55,7 @@ GATEAY_API_VERSION ?= v1.3.0
 SUPPORTED_EXTENDED_FEATURES = 
"HTTPRouteDestinationPortMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteRequestMirror,HTTPRouteSchemeRedirect,GatewayAddressEmpty,HTTPRouteResponseHeaderModification,GatewayPort8080"
 CONFORMANCE_TEST_REPORT_OUTPUT ?= 
$(DIR)/apisix-ingress-controller-conformance-report.yaml
 ## 
https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/conformance/utils/suite/profiles.go
-CONFORMANCE_PROFILES ?= GATEWAY-HTTP
+CONFORMANCE_PROFILES ?= GATEWAY-HTTP,GATEWAY-GRPC
 
 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is 
set)
 ifeq (,$(shell go env GOBIN))
diff --git a/api/adc/types.go b/api/adc/types.go
index 2133d731..5a8062db 100644
--- a/api/adc/types.go
+++ b/api/adc/types.go
@@ -344,10 +344,6 @@ const (
 
 type Scheme string
 
-const (
-       SchemeHTTP = "http"
-)
-
 type UpstreamType string
 
 const (
@@ -519,11 +515,13 @@ func ComposeServiceNameWithRule(namespace, name string, 
rule string) string {
        return buf.String()
 }
 
-func ComposeServiceNameWithStream(namespace, name string, rule string) string {
-       // FIXME Use sync.Pool to reuse this buffer if the upstream
-       // name composing code path is hot.
+func ComposeGRPCServiceNameWithRule(namespace, name string, rule string) 
string {
+       return ComposeServicesNameWithScheme(namespace, name, rule, "grpc")
+}
+
+func ComposeServicesNameWithScheme(namespace, name string, rule string, scheme 
string) string {
        var p []byte
-       plen := len(namespace) + len(name) + 6
+       plen := len(namespace) + len(name) + len(rule) + len(scheme) + 3
 
        p = make([]byte, 0, plen)
        buf := bytes.NewBuffer(p)
@@ -532,11 +530,16 @@ func ComposeServiceNameWithStream(namespace, name string, 
rule string) string {
        buf.WriteString(name)
        buf.WriteByte('_')
        buf.WriteString(rule)
-       buf.WriteString("_stream")
+       buf.WriteByte('_')
+       buf.WriteString(scheme)
 
        return buf.String()
 }
 
+func ComposeServiceNameWithStream(namespace, name string, rule string) string {
+       return ComposeServicesNameWithScheme(namespace, name, rule, "stream")
+}
+
 func ComposeConsumerName(namespace, name string) string {
        // FIXME Use sync.Pool to reuse this buffer if the upstream
        // name composing code path is hot.
@@ -569,9 +572,8 @@ func NewDefaultUpstream() *Upstream {
                                "managed-by": "apisix-ingress-controller",
                        },
                },
-               Nodes:  make(UpstreamNodes, 0),
-               Scheme: SchemeHTTP,
-               Type:   Roundrobin,
+               Nodes: make(UpstreamNodes, 0),
+               Type:  Roundrobin,
        }
 }
 
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 388c7cd2..3a69f5ec 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -79,7 +79,6 @@ rules:
   - gateway.networking.k8s.io
   resources:
   - gatewayclasses
-  - gateways
   verbs:
   - get
   - list
@@ -90,6 +89,7 @@ rules:
   resources:
   - gatewayclasses/status
   - gateways/status
+  - grpcroutes/status
   - httproutes/status
   - referencegrants/status
   verbs:
@@ -98,35 +98,22 @@ rules:
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
+  - gateways
+  - grpcroutes
   - httproutes
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
   - referencegrants
   verbs:
-  - list
-  - update
-  - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingressclasses
-  verbs:
   - get
   - list
   - watch
 - apiGroups:
   - networking.k8s.io
   resources:
+  - ingressclasses
   - ingresses
   verbs:
   - get
   - list
-  - update
   - watch
 - apiGroups:
   - networking.k8s.io
diff --git a/docs/en/latest/concepts/gateway-api.md 
b/docs/en/latest/concepts/gateway-api.md
index ce431a71..c0fcbb75 100644
--- a/docs/en/latest/concepts/gateway-api.md
+++ b/docs/en/latest/concepts/gateway-api.md
@@ -48,7 +48,7 @@ By supporting Gateway API, the APISIX Ingress controller can 
realize richer func
 | GatewayClass     | Supported           | N/A                    | Not 
supported                         | v1          |
 | Gateway          | Partially supported | Partially supported    | Not 
supported                         | v1          |
 | HTTPRoute        | Supported           | Partially supported    | Not 
supported                         | v1          |
-| GRPCRoute        | Not supported       | Not supported          | Not 
supported                         | v1          |
+| GRPCRoute        | Supported           | Supported              | Not 
supported                         | v1          |
 | ReferenceGrant   | Supported           | Not supported          | Not 
supported                         | v1beta1     |
 | TLSRoute         | Not supported       | Not supported          | Not 
supported                         | v1alpha2    |
 | TCPRoute         | Not supported       | Not supported          | Not 
supported                         | v1alpha2    |
diff --git a/go.mod b/go.mod
index 81e8b7aa..6dbbaa30 100644
--- a/go.mod
+++ b/go.mod
@@ -26,6 +26,7 @@ require (
        github.com/stretchr/testify v1.10.0
        go.uber.org/zap v1.27.0
        golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
+       google.golang.org/grpc v1.71.1
        gopkg.in/yaml.v3 v3.0.1
        k8s.io/api v0.32.3
        k8s.io/apiextensions-apiserver v0.32.3
@@ -195,7 +196,6 @@ require (
        gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
        google.golang.org/genproto/googleapis/api 
v0.0.0-20250106144421-5f5ef82da422 // indirect
        google.golang.org/genproto/googleapis/rpc 
v0.0.0-20250115164207-1a7da9e5054f // indirect
-       google.golang.org/grpc v1.71.1 // indirect
        google.golang.org/protobuf v1.36.6 // indirect
        gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
        gopkg.in/fsnotify.v1 v1.4.7 // indirect
diff --git a/internal/adc/translator/grpcroute.go 
b/internal/adc/translator/grpcroute.go
new file mode 100644
index 00000000..d24fc8bc
--- /dev/null
+++ b/internal/adc/translator/grpcroute.go
@@ -0,0 +1,372 @@
+// 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 (
+       "cmp"
+       "fmt"
+       "strings"
+
+       "k8s.io/utils/ptr"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+       adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
+       "github.com/apache/apisix-ingress-controller/internal/controller/label"
+       "github.com/apache/apisix-ingress-controller/internal/id"
+       "github.com/apache/apisix-ingress-controller/internal/provider"
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
+)
+
+func (t *Translator) fillPluginsFromGRPCRouteFilters(
+       plugins adctypes.Plugins,
+       namespace string,
+       filters []gatewayv1.GRPCRouteFilter,
+       tctx *provider.TranslateContext,
+) {
+       for _, filter := range filters {
+               switch filter.Type {
+               case gatewayv1.GRPCRouteFilterRequestHeaderModifier:
+                       t.fillPluginFromHTTPRequestHeaderFilter(plugins, 
filter.RequestHeaderModifier)
+               case gatewayv1.GRPCRouteFilterRequestMirror:
+                       t.fillPluginFromHTTPRequestMirrorFilter(plugins, 
namespace, filter.RequestMirror, apiv2.SchemeGRPC)
+               case gatewayv1.GRPCRouteFilterResponseHeaderModifier:
+                       t.fillPluginFromHTTPResponseHeaderFilter(plugins, 
filter.ResponseHeaderModifier)
+               case gatewayv1.GRPCRouteFilterExtensionRef:
+                       t.fillPluginFromExtensionRef(plugins, namespace, 
filter.ExtensionRef, tctx)
+               }
+       }
+}
+
+func calculateGRPCRoutePriority(match *gatewayv1.GRPCRouteMatch, ruleIndex 
int, hosts []string) uint64 {
+       const (
+               // PreciseHostnameShiftBits assigns bit 31-38 for the length of 
hostname(max length=253).
+               // which has 8 bits, so the max length of hostname is 2^8-1 = 
255.
+               PreciseHostnameShiftBits = 31
+
+               // HostnameLengthShiftBits assigns bits 23-30 for the length of 
hostname(max length=253).
+               // which has 8 bits, so the max length of hostname is 2^8-1 = 
255.
+               HostnameLengthShiftBits = 23
+
+               // ServiceMatchShiftBits assigns bits 19-22 for the length of 
service name.
+               ServiceMatchShiftBits = 19
+
+               // MethodMatchShiftBits assigns bits 15-18 for the length of 
method name.
+               MethodMatchShiftBits = 15
+
+               // HeaderNumberShiftBits assign bits 10-14 to number of 
headers. (max number of headers = 16)
+               HeaderNumberShiftBits = 10
+
+               // RuleIndexShiftBits assigns bits 5-9 to rule index. (max 
number of rules = 16)
+               RuleIndexShiftBits = 5
+       )
+
+       var (
+               priority uint64 = 0
+               // Handle hostname priority
+               // 1. Non-wildcard hostname priority
+               // 2. Hostname length priority
+               maxNonWildcardLength = 0
+               maxHostnameLength    = 0
+       )
+
+       for _, host := range hosts {
+               isNonWildcard := !strings.Contains(host, "*")
+
+               if isNonWildcard && len(host) > maxNonWildcardLength {
+                       maxNonWildcardLength = len(host)
+               }
+
+               if len(host) > maxHostnameLength {
+                       maxHostnameLength = len(host)
+               }
+       }
+
+       // If there is a non-wildcard hostname, set the 
PreciseHostnameShiftBits bit
+       if maxNonWildcardLength > 0 {
+               priority |= (uint64(maxNonWildcardLength) << 
PreciseHostnameShiftBits)
+       }
+
+       if maxHostnameLength > 0 {
+               priority |= (uint64(maxHostnameLength) << 
HostnameLengthShiftBits)
+       }
+
+       // Service and Method matching - this is the key difference from 
HTTPRoute
+       serviceLength := 0
+       methodLength := 0
+
+       if match.Method != nil {
+               // Service matching
+               if match.Method.Service != nil {
+                       serviceLength = len(*match.Method.Service)
+                       priority |= (uint64(serviceLength) << 
ServiceMatchShiftBits)
+               }
+
+               // Method matching
+               if match.Method.Method != nil {
+                       methodLength = len(*match.Method.Method)
+                       priority |= (uint64(methodLength) << 
MethodMatchShiftBits)
+               }
+       }
+
+       // HeaderNumberShiftBits - GRPCRoute also supports header matching
+       headerCount := 0
+       if match.Headers != nil {
+               headerCount = len(match.Headers)
+       }
+       priority |= (uint64(headerCount) << HeaderNumberShiftBits)
+
+       // RuleIndexShiftBits - lower index has higher priority
+       // We invert the index so that rule 0 gets highest priority (16), rule 
1 gets 15, etc.
+       index := 16 - ruleIndex
+       if index < 0 {
+               index = 0
+       }
+       if index > 16 {
+               index = 16
+       }
+       priority |= (uint64(index) << RuleIndexShiftBits)
+
+       return priority
+}
+
+func (t *Translator) TranslateGRPCRoute(tctx *provider.TranslateContext, 
grpcRoute *gatewayv1.GRPCRoute) (*TranslateResult, error) {
+       result := &TranslateResult{}
+
+       hosts := make([]string, 0, len(grpcRoute.Spec.Hostnames))
+       for _, hostname := range grpcRoute.Spec.Hostnames {
+               hosts = append(hosts, string(hostname))
+       }
+
+       for _, listener := range tctx.Listeners {
+               if listener.Hostname != nil {
+                       hosts = append(hosts, string(*listener.Hostname))
+               }
+       }
+
+       rules := grpcRoute.Spec.Rules
+
+       labels := label.GenLabel(grpcRoute)
+
+       for ruleIndex, rule := range rules {
+               service := adctypes.NewDefaultService()
+               service.Labels = labels
+
+               service.Name = 
adctypes.ComposeGRPCServiceNameWithRule(grpcRoute.Namespace, grpcRoute.Name, 
fmt.Sprintf("%d", ruleIndex))
+               service.ID = id.GenID(service.Name)
+               service.Hosts = hosts
+
+               var (
+                       upstreams         = make([]*adctypes.Upstream, 0)
+                       weightedUpstreams = 
make([]adctypes.TrafficSplitConfigRuleWeightedUpstream, 0)
+                       backendErr        error
+               )
+
+               for _, backend := range rule.BackendRefs {
+                       if backend.Namespace == nil {
+                               namespace := 
gatewayv1.Namespace(grpcRoute.Namespace)
+                               backend.Namespace = &namespace
+                       }
+                       upstream := adctypes.NewDefaultUpstream()
+                       upNodes, err := t.translateBackendRef(tctx, 
backend.BackendRef, DefaultEndpointFilter)
+                       if err != nil {
+                               backendErr = err
+                               continue
+                       }
+                       if len(upNodes) == 0 {
+                               continue
+                       }
+
+                       
t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, 
tctx.BackendTrafficPolicies, upstream)
+                       upstream.Nodes = upNodes
+
+                       var (
+                               kind string
+                               port int32
+                       )
+                       if backend.Kind == nil {
+                               kind = internaltypes.KindService
+                       } else {
+                               kind = string(*backend.Kind)
+                       }
+                       if backend.Port != nil {
+                               port = int32(*backend.Port)
+                       }
+                       namespace := string(*backend.Namespace)
+                       name := string(backend.Name)
+                       upstreamName := 
adctypes.ComposeUpstreamNameForBackendRef(kind, namespace, name, port)
+                       upstream.Name = upstreamName
+                       upstream.ID = id.GenID(upstreamName)
+                       upstream.Scheme = cmp.Or(upstream.Scheme, 
apiv2.SchemeGRPC)
+                       upstreams = append(upstreams, upstream)
+               }
+
+               // Handle multiple backends with traffic-split plugin
+               if len(upstreams) == 0 {
+                       // Create a default upstream if no valid backends
+                       upstream := adctypes.NewDefaultUpstream()
+                       upstream.Scheme = apiv2.SchemeGRPC
+                       service.Upstream = upstream
+               } else {
+                       // Multiple backends - use traffic-split plugin
+                       service.Upstream = upstreams[0]
+                       // remove the id and name of the service.upstream, adc 
schema does not need id and name for it
+                       service.Upstream.ID = ""
+                       service.Upstream.Name = ""
+
+                       upstreams = upstreams[1:]
+
+                       if len(upstreams) > 0 {
+                               service.Upstreams = upstreams
+
+                               // Set weight in traffic-split for the default 
upstream
+                               weight := apiv2.DefaultWeight
+                               if rule.BackendRefs[0].Weight != nil {
+                                       weight = 
int(*rule.BackendRefs[0].Weight)
+                               }
+                               weightedUpstreams = append(weightedUpstreams, 
adctypes.TrafficSplitConfigRuleWeightedUpstream{
+                                       Weight: weight,
+                               })
+
+                               // Set other upstreams in traffic-split using 
upstream_id
+                               for i, upstream := range upstreams {
+                                       weight := apiv2.DefaultWeight
+                                       // get weight from the backend refs 
starting from the second backend
+                                       if i+1 < len(rule.BackendRefs) && 
rule.BackendRefs[i+1].Weight != nil {
+                                               weight = 
int(*rule.BackendRefs[i+1].Weight)
+                                       }
+                                       weightedUpstreams = 
append(weightedUpstreams, adctypes.TrafficSplitConfigRuleWeightedUpstream{
+                                               UpstreamID: upstream.ID,
+                                               Weight:     weight,
+                                       })
+                               }
+
+                               if len(weightedUpstreams) > 0 {
+                                       if service.Plugins == nil {
+                                               service.Plugins = 
make(map[string]any)
+                                       }
+                                       service.Plugins["traffic-split"] = 
&adctypes.TrafficSplitConfig{
+                                               Rules: 
[]adctypes.TrafficSplitConfigRule{
+                                                       {
+                                                               
WeightedUpstreams: weightedUpstreams,
+                                                       },
+                                               },
+                                       }
+                               }
+                       }
+               }
+
+               if backendErr != nil && (service.Upstream == nil || 
len(service.Upstream.Nodes) == 0) {
+                       if service.Plugins == nil {
+                               service.Plugins = make(map[string]any)
+                       }
+                       service.Plugins["fault-injection"] = map[string]any{
+                               "abort": map[string]any{
+                                       "http_status": 500,
+                                       "body":        "No existing backendRef 
provided",
+                               },
+                       }
+               }
+
+               t.fillPluginsFromGRPCRouteFilters(service.Plugins, 
grpcRoute.GetNamespace(), rule.Filters, tctx)
+
+               matches := rule.Matches
+               if len(matches) == 0 {
+                       matches = []gatewayv1.GRPCRouteMatch{{}}
+               }
+
+               routes := []*adctypes.Route{}
+               for j, match := range matches {
+                       route, err := t.translateGatewayGRPCRouteMatch(&match)
+                       if err != nil {
+                               return nil, err
+                       }
+
+                       name := adctypes.ComposeRouteName(grpcRoute.Namespace, 
grpcRoute.Name, fmt.Sprintf("%d-%d", ruleIndex, j))
+                       route.Name = name
+                       route.ID = id.GenID(name)
+                       route.Labels = labels
+
+                       // Set the route priority
+                       priority := calculateGRPCRoutePriority(&match, 
ruleIndex, hosts)
+                       route.Priority = ptr.To(int64(priority))
+
+                       routes = append(routes, route)
+               }
+               service.Routes = routes
+
+               result.Services = append(result.Services, service)
+       }
+
+       return result, nil
+}
+
+func (t *Translator) translateGatewayGRPCRouteMatch(match 
*gatewayv1.GRPCRouteMatch) (*adctypes.Route, error) {
+       route := &adctypes.Route{}
+
+       var (
+               service string
+               method  string
+       )
+       if match.Method != nil {
+               service = ptr.Deref(match.Method.Service, "")
+               method = ptr.Deref(match.Method.Method, "")
+               matchType := ptr.Deref(match.Method.Type, 
gatewayv1.GRPCMethodMatchExact)
+               if matchType == gatewayv1.GRPCMethodMatchExact &&
+                       service == "" && method == "" {
+                       return nil, fmt.Errorf("service and method cannot both 
be empty for exact match type")
+               }
+       }
+
+       uri := t.translateGRPCURI(service, method)
+       route.Uris = append(route.Uris, uri)
+
+       if match.Headers != nil {
+               for _, header := range match.Headers {
+                       this, err := 
t.translateGRPCRouteHeaderMatchToVars(header)
+                       if err != nil {
+                               return nil, err
+                       }
+                       route.Vars = append(route.Vars, this)
+               }
+       }
+       return route, nil
+}
+
+func (t *Translator) translateGRPCURI(service, method string) string {
+       var uri string
+       if service == "" {
+               uri = "/*"
+       } else {
+               uri = fmt.Sprintf("/%s", service)
+       }
+       if method != "" {
+               uri = uri + fmt.Sprintf("/%s", method)
+       } else if service != "" {
+               uri = uri + "/*"
+       }
+       return uri
+}
+
+func (t *Translator) translateGRPCRouteHeaderMatchToVars(header 
gatewayv1.GRPCHeaderMatch) ([]adctypes.StringOrSlice, error) {
+       var matchType string
+       if header.Type != nil {
+               matchType = string(*header.Type)
+       }
+       return HeaderMatchToVars(matchType, string(header.Name), header.Value)
+}
diff --git a/internal/adc/translator/httproute.go 
b/internal/adc/translator/httproute.go
index b33de0e4..d88b32cf 100644
--- a/internal/adc/translator/httproute.go
+++ b/internal/adc/translator/httproute.go
@@ -54,7 +54,7 @@ func (t *Translator) fillPluginsFromHTTPRouteFilters(
                case gatewayv1.HTTPRouteFilterRequestRedirect:
                        t.fillPluginFromHTTPRequestRedirectFilter(plugins, 
filter.RequestRedirect)
                case gatewayv1.HTTPRouteFilterRequestMirror:
-                       t.fillPluginFromHTTPRequestMirrorFilter(plugins, 
namespace, filter.RequestMirror)
+                       t.fillPluginFromHTTPRequestMirrorFilter(plugins, 
namespace, filter.RequestMirror, apiv2.SchemeHTTP)
                case gatewayv1.HTTPRouteFilterURLRewrite:
                        t.fillPluginFromURLRewriteFilter(plugins, 
filter.URLRewrite, matches)
                case gatewayv1.HTTPRouteFilterResponseHeaderModifier:
@@ -232,7 +232,7 @@ func (t *Translator) 
fillPluginFromHTTPResponseHeaderFilter(plugins adctypes.Plu
        plugin.Headers.Remove = append(plugin.Headers.Remove, 
respHeaderModifier.Remove...)
 }
 
-func (t *Translator) fillPluginFromHTTPRequestMirrorFilter(plugins 
adctypes.Plugins, namespace string, reqMirror 
*gatewayv1.HTTPRequestMirrorFilter) {
+func (t *Translator) fillPluginFromHTTPRequestMirrorFilter(plugins 
adctypes.Plugins, namespace string, reqMirror 
*gatewayv1.HTTPRequestMirrorFilter, scheme string) {
        pluginName := adctypes.PluginProxyMirror
        obj := plugins[pluginName]
 
@@ -255,7 +255,7 @@ func (t *Translator) 
fillPluginFromHTTPRequestMirrorFilter(plugins adctypes.Plug
                ns = string(*reqMirror.BackendRef.Namespace)
        }
 
-       host := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d";, 
reqMirror.BackendRef.Name, ns, port)
+       host := fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", scheme, 
reqMirror.BackendRef.Name, ns, port)
 
        plugin.Host = host
 }
@@ -722,36 +722,10 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match 
*gatewayv1.HTTPRouteMa
 
        if len(match.Headers) > 0 {
                for _, header := range match.Headers {
-                       name := strings.ToLower(string(header.Name))
-                       name = strings.ReplaceAll(name, "-", "_")
-
-                       var this []adctypes.StringOrSlice
-                       this = append(this, adctypes.StringOrSlice{
-                               StrVal: "http_" + name,
-                       })
-
-                       matchType := gatewayv1.HeaderMatchExact
-                       if header.Type != nil {
-                               matchType = *header.Type
-                       }
-
-                       switch matchType {
-                       case gatewayv1.HeaderMatchExact:
-                               this = append(this, adctypes.StringOrSlice{
-                                       StrVal: "==",
-                               })
-                       case gatewayv1.HeaderMatchRegularExpression:
-                               this = append(this, adctypes.StringOrSlice{
-                                       StrVal: "~~",
-                               })
-                       default:
-                               return nil, errors.New("unknown header match 
type " + string(matchType))
+                       this, err := 
t.translateHTTPRouteHeaderMatchToVars(header)
+                       if err != nil {
+                               return nil, err
                        }
-
-                       this = append(this, adctypes.StringOrSlice{
-                               StrVal: header.Value,
-                       })
-
                        route.Vars = append(route.Vars, this)
                }
        }
@@ -797,3 +771,39 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match 
*gatewayv1.HTTPRouteMa
 
        return route, nil
 }
+
+func HeaderMatchToVars(matchType, name, value string) 
([]adctypes.StringOrSlice, error) {
+       name = strings.ToLower(name)
+       name = strings.ReplaceAll(name, "-", "_")
+
+       var this []adctypes.StringOrSlice
+       this = append(this, adctypes.StringOrSlice{
+               StrVal: "http_" + name,
+       })
+
+       switch matchType {
+       case string(gatewayv1.HeaderMatchExact):
+               this = append(this, adctypes.StringOrSlice{
+                       StrVal: "==",
+               })
+       case string(gatewayv1.HeaderMatchRegularExpression):
+               this = append(this, adctypes.StringOrSlice{
+                       StrVal: "~~",
+               })
+       default:
+               return nil, errors.New("unknown header match type " + matchType)
+       }
+
+       this = append(this, adctypes.StringOrSlice{
+               StrVal: value,
+       })
+       return this, nil
+}
+
+func (t *Translator) translateHTTPRouteHeaderMatchToVars(header 
gatewayv1.HTTPHeaderMatch) ([]adctypes.StringOrSlice, error) {
+       var matchType string
+       if header.Type != nil {
+               matchType = string(*header.Type)
+       }
+       return HeaderMatchToVars(matchType, string(header.Name), header.Value)
+}
diff --git a/internal/controller/context.go b/internal/controller/context.go
index f5d85194..5398f044 100644
--- a/internal/controller/context.go
+++ b/internal/controller/context.go
@@ -26,5 +26,7 @@ type RouteParentRefContext struct {
        Gateway *gatewayv1.Gateway
 
        ListenerName string
-       Conditions   []metav1.Condition
+       Listener     *gatewayv1.Listener
+
+       Conditions []metav1.Condition
 }
diff --git a/internal/controller/httproute_controller.go 
b/internal/controller/grpcroute_controller.go
similarity index 58%
copy from internal/controller/httproute_controller.go
copy to internal/controller/grpcroute_controller.go
index 6fe87881..138011a9 100644
--- a/internal/controller/httproute_controller.go
+++ b/internal/controller/grpcroute_controller.go
@@ -22,17 +22,12 @@ import (
        "context"
        "fmt"
 
-       "github.com/api7/gopkg/pkg/log"
        "github.com/go-logr/logr"
-       "github.com/pkg/errors"
-       "go.uber.org/zap"
-       "golang.org/x/exp/slices"
        corev1 "k8s.io/api/core/v1"
        discoveryv1 "k8s.io/api/discovery/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
        k8stypes "k8s.io/apimachinery/pkg/types"
-       "k8s.io/utils/ptr"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/builder"
        "sigs.k8s.io/controller-runtime/pkg/client"
@@ -42,7 +37,6 @@ import (
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
        "sigs.k8s.io/controller-runtime/pkg/source"
        gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
-       "sigs.k8s.io/gateway-api/apis/v1alpha2"
        "sigs.k8s.io/gateway-api/apis/v1beta1"
 
        "github.com/apache/apisix-ingress-controller/api/v1alpha1"
@@ -54,8 +48,8 @@ import (
        "github.com/apache/apisix-ingress-controller/internal/utils"
 )
 
-// HTTPRouteReconciler reconciles a GatewayClass object.
-type HTTPRouteReconciler struct { //nolint:revive
+// GRPCRouteReconciler reconciles a GatewayClass object.
+type GRPCRouteReconciler struct { //nolint:revive
        client.Client
        Scheme *runtime.Scheme
 
@@ -70,20 +64,20 @@ type HTTPRouteReconciler struct { //nolint:revive
 }
 
 // SetupWithManager sets up the controller with the Manager.
-func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
+func (r *GRPCRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
        r.genericEvent = make(chan event.GenericEvent, 100)
 
        bdr := ctrl.NewControllerManagedBy(mgr).
-               For(&gatewayv1.HTTPRoute{}).
+               For(&gatewayv1.GRPCRoute{}).
                WithEventFilter(predicate.GenerationChangedPredicate{}).
                Watches(&discoveryv1.EndpointSlice{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceBef),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesByServiceRef),
                ).
                Watches(&v1alpha1.PluginConfig{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByExtensionRef),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesByExtensionRef),
                ).
                Watches(&gatewayv1.Gateway{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesForGateway),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesForGateway),
                        builder.WithPredicates(
                                predicate.Funcs{
                                        GenericFunc: func(e event.GenericEvent) 
bool {
@@ -102,53 +96,74 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr 
ctrl.Manager) error {
                        ),
                ).
                Watches(&v1alpha1.BackendTrafficPolicy{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesForBackendTrafficPolicy),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesForBackendTrafficPolicy),
                        builder.WithPredicates(
                                
BackendTrafficPolicyPredicateFunc(r.genericEvent),
                        ),
                ).
-               Watches(&v1alpha1.HTTPRoutePolicy{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRouteByHTTPRoutePolicy),
-                       
builder.WithPredicates(httpRoutePolicyPredicateFuncs(r.genericEvent)),
-               ).
                Watches(&v1alpha1.GatewayProxy{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesForGatewayProxy),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesForGatewayProxy),
                ).
                WatchesRawSource(
                        source.Channel(
                                r.genericEvent,
-                               
handler.EnqueueRequestsFromMapFunc(r.listHTTPRouteForGenericEvent),
+                               
handler.EnqueueRequestsFromMapFunc(r.listGRPCRouteForGenericEvent),
                        ),
                )
 
        if GetEnableReferenceGrant() {
                bdr.Watches(&v1beta1.ReferenceGrant{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesForReferenceGrant),
-                       
builder.WithPredicates(referenceGrantPredicates(KindHTTPRoute)),
+                       
handler.EnqueueRequestsFromMapFunc(r.listGRPCRoutesForReferenceGrant),
+                       
builder.WithPredicates(referenceGrantPredicates(KindGRPCRoute)),
                )
        }
 
        return bdr.Complete(r)
 }
 
-func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) 
(ctrl.Result, error) {
-       defer r.Readier.Done(&gatewayv1.HTTPRoute{}, req.NamespacedName)
-       hr := new(gatewayv1.HTTPRoute)
-       if err := r.Get(ctx, req.NamespacedName, hr); err != nil {
+func (r *GRPCRouteReconciler) listGRPCRoutesByExtensionRef(ctx 
context.Context, obj client.Object) []reconcile.Request {
+       pluginconfig, ok := obj.(*v1alpha1.PluginConfig)
+       if !ok {
+               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to PluginConfig")
+               return nil
+       }
+       namespace := pluginconfig.GetNamespace()
+       name := pluginconfig.GetName()
+
+       grList := &gatewayv1.GRPCRouteList{}
+       if err := r.List(ctx, grList, client.MatchingFields{
+               indexer.ExtensionRef: indexer.GenIndexKey(namespace, name),
+       }); err != nil {
+               r.Log.Error(err, "failed to list grpcroutes by extension 
reference", "extension", name)
+               return nil
+       }
+       requests := make([]reconcile.Request, 0, len(grList.Items))
+       for _, gr := range grList.Items {
+               requests = append(requests, reconcile.Request{
+                       NamespacedName: client.ObjectKey{
+                               Namespace: gr.Namespace,
+                               Name:      gr.Name,
+                       },
+               })
+       }
+       return requests
+}
+
+func (r *GRPCRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) 
(ctrl.Result, error) {
+       defer r.Readier.Done(&gatewayv1.GRPCRoute{}, req.NamespacedName)
+       gr := new(gatewayv1.GRPCRoute)
+       if err := r.Get(ctx, req.NamespacedName, gr); err != nil {
                if client.IgnoreNotFound(err) == nil {
-                       if err := r.updateHTTPRoutePolicyStatusOnDeleting(ctx, 
req.NamespacedName); err != nil {
-                               return ctrl.Result{}, err
-                       }
-                       hr.Namespace = req.Namespace
-                       hr.Name = req.Name
+                       gr.Namespace = req.Namespace
+                       gr.Name = req.Name
 
-                       hr.TypeMeta = metav1.TypeMeta{
-                               Kind:       KindHTTPRoute,
+                       gr.TypeMeta = metav1.TypeMeta{
+                               Kind:       KindGRPCRoute,
                                APIVersion: gatewayv1.GroupVersion.String(),
                        }
 
-                       if err := r.Provider.Delete(ctx, hr); err != nil {
-                               r.Log.Error(err, "failed to delete httproute", 
"httproute", hr)
+                       if err := r.Provider.Delete(ctx, gr); err != nil {
+                               r.Log.Error(err, "failed to delete grpcroute", 
"grpcroute", gr)
                                return ctrl.Result{}, err
                        }
                        return ctrl.Result{}, nil
@@ -167,7 +182,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
                msg:    "Route is accepted",
        }
 
-       gateways, err := ParseRouteParentRefs(ctx, r.Client, hr, 
hr.Spec.ParentRefs)
+       gateways, err := ParseRouteParentRefs(ctx, r.Client, gr, 
gr.Spec.ParentRefs)
        if err != nil {
                return ctrl.Result{}, err
        }
@@ -178,17 +193,20 @@ func (r *HTTPRouteReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
 
        tctx := provider.NewDefaultTranslateContext(ctx)
 
-       tctx.RouteParentRefs = hr.Spec.ParentRefs
-       rk := utils.NamespacedNameKind(hr)
+       tctx.RouteParentRefs = gr.Spec.ParentRefs
+       rk := utils.NamespacedNameKind(gr)
        for _, gateway := range gateways {
                if err := ProcessGatewayProxy(r.Client, r.Log, tctx, 
gateway.Gateway, rk); err != nil {
                        acceptStatus.status = false
                        acceptStatus.msg = err.Error()
                }
+               if gateway.Listener != nil {
+                       tctx.Listeners = append(tctx.Listeners, 
*gateway.Listener)
+               }
        }
 
        var backendRefErr error
-       if err := r.processHTTPRoute(tctx, hr); err != nil {
+       if err := r.processGRPCRoute(tctx, gr); err != nil {
                // When encountering a backend reference error, it should not 
affect the acceptance status
                if types.IsSomeReasonError(err, 
gatewayv1.RouteReasonInvalidKind) {
                        backendRefErr = err
@@ -198,61 +216,46 @@ func (r *HTTPRouteReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
                }
        }
 
-       if err := r.processHTTPRoutePolicies(tctx, hr); err != nil {
-               acceptStatus.status = false
-               acceptStatus.msg = err.Error()
-       }
-
        // Store the backend reference error for later use.
        // If the backend reference error is because of an invalid kind, use 
this error first
-       if err := r.processHTTPRouteBackendRefs(tctx, req.NamespacedName); err 
!= nil && backendRefErr == nil {
+       if err := r.processGRPCRouteBackendRefs(tctx, req.NamespacedName); err 
!= nil && backendRefErr == nil {
                backendRefErr = err
        }
 
        ProcessBackendTrafficPolicy(r.Client, r.Log, tctx)
 
-       filteredHTTPRoute, err := filterHostnames(gateways, hr.DeepCopy())
-       if err != nil {
-               acceptStatus.status = false
-               acceptStatus.msg = err.Error()
-       }
-
        // TODO: diff the old and new status
-       hr.Status.Parents = make([]gatewayv1.RouteParentStatus, 0, 
len(gateways))
+       gr.Status.Parents = make([]gatewayv1.RouteParentStatus, 0, 
len(gateways))
        for _, gateway := range gateways {
                parentStatus := gatewayv1.RouteParentStatus{}
                SetRouteParentRef(&parentStatus, gateway.Gateway.Name, 
gateway.Gateway.Namespace)
                for _, condition := range gateway.Conditions {
                        parentStatus.Conditions = 
MergeCondition(parentStatus.Conditions, condition)
                }
-               SetRouteConditionAccepted(&parentStatus, hr.GetGeneration(), 
acceptStatus.status, acceptStatus.msg)
-               SetRouteConditionResolvedRefs(&parentStatus, 
hr.GetGeneration(), backendRefErr)
+               SetRouteConditionAccepted(&parentStatus, gr.GetGeneration(), 
acceptStatus.status, acceptStatus.msg)
+               SetRouteConditionResolvedRefs(&parentStatus, 
gr.GetGeneration(), backendRefErr)
 
-               hr.Status.Parents = append(hr.Status.Parents, parentStatus)
+               gr.Status.Parents = append(gr.Status.Parents, parentStatus)
        }
 
        r.Updater.Update(status.Update{
-               NamespacedName: utils.NamespacedName(hr),
-               Resource:       &gatewayv1.HTTPRoute{},
+               NamespacedName: utils.NamespacedName(gr),
+               Resource:       &gatewayv1.GRPCRoute{},
                Mutator: status.MutatorFunc(func(obj client.Object) 
client.Object {
-                       h, ok := obj.(*gatewayv1.HTTPRoute)
+                       h, ok := obj.(*gatewayv1.GRPCRoute)
                        if !ok {
                                err := fmt.Errorf("unsupported object type %T", 
obj)
                                panic(err)
                        }
                        hCopy := h.DeepCopy()
-                       hCopy.Status = hr.Status
+                       hCopy.Status = gr.Status
                        return hCopy
                }),
        })
        UpdateStatus(r.Updater, r.Log, tctx)
 
        if isRouteAccepted(gateways) && err == nil {
-               routeToUpdate := hr
-               if filteredHTTPRoute != nil {
-                       log.Debugw("filteredHTTPRoute", 
zap.Any("filteredHTTPRoute", filteredHTTPRoute))
-                       routeToUpdate = filteredHTTPRoute
-               }
+               routeToUpdate := gr
                if err := r.Provider.Update(ctx, tctx, routeToUpdate); err != 
nil {
                        return ctrl.Result{}, err
                }
@@ -260,7 +263,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
        return ctrl.Result{}, nil
 }
 
-func (r *HTTPRouteReconciler) listHTTPRoutesByServiceBef(ctx context.Context, 
obj client.Object) []reconcile.Request {
+func (r *GRPCRouteReconciler) listGRPCRoutesByServiceRef(ctx context.Context, 
obj client.Object) []reconcile.Request {
        endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to EndpointSlice")
@@ -269,61 +272,33 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesByServiceBef(ctx context.Context, ob
        namespace := endpointSlice.GetNamespace()
        serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
 
-       hrList := &gatewayv1.HTTPRouteList{}
-       if err := r.List(ctx, hrList, client.MatchingFields{
+       gList := &gatewayv1.GRPCRouteList{}
+       if err := r.List(ctx, gList, client.MatchingFields{
                indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, 
serviceName),
        }); err != nil {
-               r.Log.Error(err, "failed to list httproutes by service", 
"service", serviceName)
-               return nil
-       }
-       requests := make([]reconcile.Request, 0, len(hrList.Items))
-       for _, hr := range hrList.Items {
-               requests = append(requests, reconcile.Request{
-                       NamespacedName: client.ObjectKey{
-                               Namespace: hr.Namespace,
-                               Name:      hr.Name,
-                       },
-               })
-       }
-       return requests
-}
-
-func (r *HTTPRouteReconciler) listHTTPRoutesByExtensionRef(ctx 
context.Context, obj client.Object) []reconcile.Request {
-       pluginconfig, ok := obj.(*v1alpha1.PluginConfig)
-       if !ok {
-               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to EndpointSlice")
-               return nil
-       }
-       namespace := pluginconfig.GetNamespace()
-       name := pluginconfig.GetName()
-
-       hrList := &gatewayv1.HTTPRouteList{}
-       if err := r.List(ctx, hrList, client.MatchingFields{
-               indexer.ExtensionRef: indexer.GenIndexKey(namespace, name),
-       }); err != nil {
-               r.Log.Error(err, "failed to list httproutes by extension 
reference", "extension", name)
+               r.Log.Error(err, "failed to list grpcroutes by service", 
"service", serviceName)
                return nil
        }
-       requests := make([]reconcile.Request, 0, len(hrList.Items))
-       for _, hr := range hrList.Items {
+       requests := make([]reconcile.Request, 0, len(gList.Items))
+       for _, gr := range gList.Items {
                requests = append(requests, reconcile.Request{
                        NamespacedName: client.ObjectKey{
-                               Namespace: hr.Namespace,
-                               Name:      hr.Name,
+                               Namespace: gr.Namespace,
+                               Name:      gr.Name,
                        },
                })
        }
        return requests
 }
 
-func (r *HTTPRouteReconciler) listHTTPRoutesForBackendTrafficPolicy(ctx 
context.Context, obj client.Object) []reconcile.Request {
+func (r *GRPCRouteReconciler) listGRPCRoutesForBackendTrafficPolicy(ctx 
context.Context, obj client.Object) []reconcile.Request {
        policy, ok := obj.(*v1alpha1.BackendTrafficPolicy)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to BackendTrafficPolicy")
                return nil
        }
 
-       httprouteList := []gatewayv1.HTTPRoute{}
+       grpcRouteList := []gatewayv1.GRPCRoute{}
        for _, targetRef := range policy.Spec.TargetRefs {
                service := &corev1.Service{}
                if err := r.Get(ctx, client.ObjectKey{
@@ -335,28 +310,28 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesForBackendTrafficPolicy(ctx context.
                        }
                        continue
                }
-               hrList := &gatewayv1.HTTPRouteList{}
-               if err := r.List(ctx, hrList, client.MatchingFields{
+               grList := &gatewayv1.GRPCRouteList{}
+               if err := r.List(ctx, grList, client.MatchingFields{
                        indexer.ServiceIndexRef: 
indexer.GenIndexKey(policy.Namespace, string(targetRef.Name)),
                }); err != nil {
-                       r.Log.Error(err, "failed to list httproutes by service 
reference", "service", targetRef.Name)
+                       r.Log.Error(err, "failed to list grpcroutes by service 
reference", "service", targetRef.Name)
                        return nil
                }
-               httprouteList = append(httprouteList, hrList.Items...)
+               grpcRouteList = append(grpcRouteList, grList.Items...)
        }
        var namespacedNameMap = make(map[k8stypes.NamespacedName]struct{})
-       requests := make([]reconcile.Request, 0, len(httprouteList))
-       for _, hr := range httprouteList {
+       requests := make([]reconcile.Request, 0, len(grpcRouteList))
+       for _, gr := range grpcRouteList {
                key := k8stypes.NamespacedName{
-                       Namespace: hr.Namespace,
-                       Name:      hr.Name,
+                       Namespace: gr.Namespace,
+                       Name:      gr.Name,
                }
                if _, ok := namespacedNameMap[key]; !ok {
                        namespacedNameMap[key] = struct{}{}
                        requests = append(requests, reconcile.Request{
                                NamespacedName: client.ObjectKey{
-                                       Namespace: hr.Namespace,
-                                       Name:      hr.Name,
+                                       Namespace: gr.Namespace,
+                                       Name:      gr.Name,
                                },
                        })
                }
@@ -364,99 +339,52 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesForBackendTrafficPolicy(ctx context.
        return requests
 }
 
-func (r *HTTPRouteReconciler) listHTTPRoutesForGateway(ctx context.Context, 
obj client.Object) []reconcile.Request {
+func (r *GRPCRouteReconciler) listGRPCRoutesForGateway(ctx context.Context, 
obj client.Object) []reconcile.Request {
        gateway, ok := obj.(*gatewayv1.Gateway)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to Gateway")
        }
-       hrList := &gatewayv1.HTTPRouteList{}
-       if err := r.List(ctx, hrList, client.MatchingFields{
+       grList := &gatewayv1.GRPCRouteList{}
+       if err := r.List(ctx, grList, client.MatchingFields{
                indexer.ParentRefs: indexer.GenIndexKey(gateway.Namespace, 
gateway.Name),
        }); err != nil {
-               r.Log.Error(err, "failed to list httproutes by gateway", 
"gateway", gateway.Name)
+               r.Log.Error(err, "failed to list grpcroutes by gateway", 
"gateway", gateway.Name)
                return nil
        }
-       requests := make([]reconcile.Request, 0, len(hrList.Items))
-       for _, hr := range hrList.Items {
+       requests := make([]reconcile.Request, 0, len(grList.Items))
+       for _, gr := range grList.Items {
                requests = append(requests, reconcile.Request{
                        NamespacedName: client.ObjectKey{
-                               Namespace: hr.Namespace,
-                               Name:      hr.Name,
+                               Namespace: gr.Namespace,
+                               Name:      gr.Name,
                        },
                })
        }
        return requests
 }
 
-func (r *HTTPRouteReconciler) listHTTPRouteByHTTPRoutePolicy(ctx 
context.Context, obj client.Object) (requests []reconcile.Request) {
-       httpRoutePolicy, ok := obj.(*v1alpha1.HTTPRoutePolicy)
-       if !ok {
-               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to HTTPRoutePolicy")
-               return nil
-       }
-
-       var keys = make(map[k8stypes.NamespacedName]struct{})
-       for _, ref := range httpRoutePolicy.Spec.TargetRefs {
-               if ref.Kind != types.KindHTTPRoute {
-                       continue
-               }
-               key := k8stypes.NamespacedName{
-                       Namespace: obj.GetNamespace(),
-                       Name:      string(ref.Name),
-               }
-               if _, ok := keys[key]; ok {
-                       continue
-               }
-
-               var httpRoute gatewayv1.HTTPRoute
-               if err := r.Get(ctx, key, &httpRoute); err != nil {
-                       r.Log.Error(err, "failed to get HTTPRoute by 
HTTPRoutePolicy targetRef", "namespace", key.Namespace, "name", key.Name)
-                       continue
-               }
-               if ref.SectionName != nil {
-                       var matchRuleName bool
-                       for _, rule := range httpRoute.Spec.Rules {
-                               if rule.Name != nil && *rule.Name == 
*ref.SectionName {
-                                       matchRuleName = true
-                                       break
-                               }
-                       }
-                       if !matchRuleName {
-                               r.Log.Error(errors.Errorf("failed to get 
HTTPRoute rule by HTTPRoutePolicy targetRef"), "namespace", key.Namespace, 
"name", key.Name, "sectionName", *ref.SectionName)
-                               continue
-                       }
-               }
-               keys[key] = struct{}{}
-               requests = append(requests, reconcile.Request{NamespacedName: 
key})
-       }
-
-       return requests
-}
-
-func (r *HTTPRouteReconciler) listHTTPRouteForGenericEvent(ctx 
context.Context, obj client.Object) (requests []reconcile.Request) {
+func (r *GRPCRouteReconciler) listGRPCRouteForGenericEvent(ctx 
context.Context, obj client.Object) (requests []reconcile.Request) {
        switch obj.(type) {
        case *v1alpha1.BackendTrafficPolicy:
-               return r.listHTTPRoutesForBackendTrafficPolicy(ctx, obj)
-       case *v1alpha1.HTTPRoutePolicy:
-               return r.listHTTPRouteByHTTPRoutePolicy(ctx, obj)
+               return r.listGRPCRoutesForBackendTrafficPolicy(ctx, obj)
        default:
-               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to BackendTrafficPolicy or HTTPRoutePolicy")
+               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to BackendTrafficPolicy")
                return nil
        }
 }
 
-func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx 
*provider.TranslateContext, hrNN k8stypes.NamespacedName) error {
+func (r *GRPCRouteReconciler) processGRPCRouteBackendRefs(tctx 
*provider.TranslateContext, grNN k8stypes.NamespacedName) error {
        var terr error
        for _, backend := range tctx.BackendRefs {
                targetNN := k8stypes.NamespacedName{
-                       Namespace: hrNN.Namespace,
+                       Namespace: grNN.Namespace,
                        Name:      string(backend.Name),
                }
                if backend.Namespace != nil {
                        targetNN.Namespace = string(*backend.Namespace)
                }
 
-               if backend.Kind != nil && *backend.Kind != types.KindService {
+               if backend.Kind != nil && *backend.Kind != "Service" {
                        terr = types.NewInvalidKindError(*backend.Kind)
                        continue
                }
@@ -478,14 +406,14 @@ func (r *HTTPRouteReconciler) 
processHTTPRouteBackendRefs(tctx *provider.Transla
                        continue
                }
 
-               // if cross namespaces between HTTPRoute and referenced 
Service, check ReferenceGrant
-               if hrNN.Namespace != targetNN.Namespace {
+               // if cross namespaces between GRPCRoute and referenced 
Service, check ReferenceGrant
+               if grNN.Namespace != targetNN.Namespace {
                        if permitted := checkReferenceGrant(tctx,
                                r.Client,
                                v1beta1.ReferenceGrantFrom{
                                        Group:     gatewayv1.GroupName,
-                                       Kind:      KindHTTPRoute,
-                                       Namespace: 
v1beta1.Namespace(hrNN.Namespace),
+                                       Kind:      KindGRPCRoute,
+                                       Namespace: 
v1beta1.Namespace(grNN.Namespace),
                                },
                                gatewayv1.ObjectReference{
                                        Group:     corev1.GroupName,
@@ -496,7 +424,7 @@ func (r *HTTPRouteReconciler) 
processHTTPRouteBackendRefs(tctx *provider.Transla
                        ); !permitted {
                                terr = types.ReasonError{
                                        Reason:  
string(v1beta1.RouteReasonRefNotPermitted),
-                                       Message: fmt.Sprintf("%s is in a 
different namespace than the HTTPRoute %s and no ReferenceGrant allowing 
reference is configured", targetNN, hrNN),
+                                       Message: fmt.Sprintf("%s is in a 
different namespace than the GRPCRoute %s and no ReferenceGrant allowing 
reference is configured", targetNN, grNN),
                                }
                                continue
                        }
@@ -537,37 +465,37 @@ func (r *HTTPRouteReconciler) 
processHTTPRouteBackendRefs(tctx *provider.Transla
        return terr
 }
 
-func (r *HTTPRouteReconciler) processHTTPRoute(tctx 
*provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) error {
+func (r *GRPCRouteReconciler) processGRPCRoute(tctx 
*provider.TranslateContext, grpcroute *gatewayv1.GRPCRoute) error {
        var terror error
-       for _, rule := range httpRoute.Spec.Rules {
+       for _, rule := range grpcroute.Spec.Rules {
                for _, filter := range rule.Filters {
-                       if filter.Type != gatewayv1.HTTPRouteFilterExtensionRef 
|| filter.ExtensionRef == nil {
+                       if filter.Type != gatewayv1.GRPCRouteFilterExtensionRef 
|| filter.ExtensionRef == nil {
                                continue
                        }
-                       if filter.ExtensionRef.Kind == types.KindPluginConfig {
+                       if filter.ExtensionRef.Kind == "PluginConfig" {
                                pluginconfig := new(v1alpha1.PluginConfig)
                                if err := r.Get(context.Background(), 
client.ObjectKey{
-                                       Namespace: httpRoute.GetNamespace(),
+                                       Namespace: grpcroute.GetNamespace(),
                                        Name:      
string(filter.ExtensionRef.Name),
                                }, pluginconfig); err != nil {
                                        terror = err
                                        continue
                                }
                                tctx.PluginConfigs[k8stypes.NamespacedName{
-                                       Namespace: httpRoute.GetNamespace(),
+                                       Namespace: grpcroute.GetNamespace(),
                                        Name:      
string(filter.ExtensionRef.Name),
                                }] = pluginconfig
                        }
                }
                for _, backend := range rule.BackendRefs {
-                       if backend.Kind != nil && *backend.Kind != 
types.KindService {
+                       if backend.Kind != nil && *backend.Kind != "Service" {
                                terror = 
types.NewInvalidKindError(*backend.Kind)
                                continue
                        }
                        tctx.BackendRefs = append(tctx.BackendRefs, 
gatewayv1.BackendRef{
                                BackendObjectReference: 
gatewayv1.BackendObjectReference{
                                        Name:      backend.Name,
-                                       Namespace: cmp.Or(backend.Namespace, 
(*gatewayv1.Namespace)(&httpRoute.Namespace)),
+                                       Namespace: cmp.Or(backend.Namespace, 
(*gatewayv1.Namespace)(&grpcroute.Namespace)),
                                        Port:      backend.Port,
                                },
                        })
@@ -577,40 +505,8 @@ func (r *HTTPRouteReconciler) processHTTPRoute(tctx 
*provider.TranslateContext,
        return terror
 }
 
-func httpRoutePolicyPredicateFuncs(channel chan event.GenericEvent) 
predicate.Predicate {
-       return predicate.Funcs{
-               CreateFunc: func(e event.CreateEvent) bool {
-                       return true
-               },
-               DeleteFunc: func(e event.DeleteEvent) bool {
-                       return true
-               },
-               UpdateFunc: func(e event.UpdateEvent) bool {
-                       oldPolicy, ok0 := 
e.ObjectOld.(*v1alpha1.HTTPRoutePolicy)
-                       newPolicy, ok1 := 
e.ObjectNew.(*v1alpha1.HTTPRoutePolicy)
-                       if !ok0 || !ok1 {
-                               return false
-                       }
-                       discardsRefs := 
slices.DeleteFunc(oldPolicy.Spec.TargetRefs, func(oldRef 
v1alpha2.LocalPolicyTargetReferenceWithSectionName) bool {
-                               return 
slices.ContainsFunc(newPolicy.Spec.TargetRefs, func(newRef 
v1alpha2.LocalPolicyTargetReferenceWithSectionName) bool {
-                                       return 
oldRef.LocalPolicyTargetReference == newRef.LocalPolicyTargetReference && 
ptr.Equal(oldRef.SectionName, newRef.SectionName)
-                               })
-                       })
-                       if len(discardsRefs) > 0 {
-                               dump := oldPolicy.DeepCopy()
-                               dump.Spec.TargetRefs = discardsRefs
-                               channel <- event.GenericEvent{Object: dump}
-                       }
-                       return true
-               },
-               GenericFunc: func(e event.GenericEvent) bool {
-                       return false
-               },
-       }
-}
-
-// listHTTPRoutesForGatewayProxy list all HTTPRoute resources that are 
affected by a given GatewayProxy
-func (r *HTTPRouteReconciler) listHTTPRoutesForGatewayProxy(ctx 
context.Context, obj client.Object) []reconcile.Request {
+// listGRPCRoutesForGatewayProxy list all GRPCRoute resources that are 
affected by a given GatewayProxy
+func (r *GRPCRouteReconciler) listGRPCRoutesForGatewayProxy(ctx 
context.Context, obj client.Object) []reconcile.Request {
        gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to GatewayProxy")
@@ -631,21 +527,21 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesForGatewayProxy(ctx context.Context,
 
        var requests []reconcile.Request
 
-       // for each gateway, find all HTTPRoute resources that reference it
+       // for each gateway, find all GRPCRoute resources that reference it
        for _, gateway := range gatewayList.Items {
-               httpRouteList := &gatewayv1.HTTPRouteList{}
-               if err := r.List(ctx, httpRouteList, client.MatchingFields{
+               grpcRouteList := &gatewayv1.GRPCRouteList{}
+               if err := r.List(ctx, grpcRouteList, client.MatchingFields{
                        indexer.ParentRefs: 
indexer.GenIndexKey(gateway.Namespace, gateway.Name),
                }); err != nil {
-                       r.Log.Error(err, "failed to list httproutes for 
gateway", "gateway", gateway.Name)
+                       r.Log.Error(err, "failed to list grpcroutes for 
gateway", "gateway", gateway.Name)
                        continue
                }
 
-               for _, httpRoute := range httpRouteList.Items {
+               for _, grpcRoute := range grpcRouteList.Items {
                        requests = append(requests, reconcile.Request{
                                NamespacedName: client.ObjectKey{
-                                       Namespace: httpRoute.Namespace,
-                                       Name:      httpRoute.Name,
+                                       Namespace: grpcRoute.Namespace,
+                                       Name:      grpcRoute.Name,
                                },
                        })
                }
@@ -654,31 +550,31 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesForGatewayProxy(ctx context.Context,
        return requests
 }
 
-func (r *HTTPRouteReconciler) listHTTPRoutesForReferenceGrant(ctx 
context.Context, obj client.Object) (requests []reconcile.Request) {
+func (r *GRPCRouteReconciler) listGRPCRoutesForReferenceGrant(ctx 
context.Context, obj client.Object) (requests []reconcile.Request) {
        grant, ok := obj.(*v1beta1.ReferenceGrant)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to ReferenceGrant")
                return nil
        }
 
-       var httpRouteList gatewayv1.HTTPRouteList
-       if err := r.List(ctx, &httpRouteList); err != nil {
-               r.Log.Error(err, "failed to list httproutes for reference 
ReferenceGrant", "ReferenceGrant", k8stypes.NamespacedName{Namespace: 
obj.GetNamespace(), Name: obj.GetName()})
+       var grpcRouteList gatewayv1.GRPCRouteList
+       if err := r.List(ctx, &grpcRouteList); err != nil {
+               r.Log.Error(err, "failed to list grpcroutes for reference 
ReferenceGrant", "ReferenceGrant", k8stypes.NamespacedName{Namespace: 
obj.GetNamespace(), Name: obj.GetName()})
                return nil
        }
 
-       for _, httpRoute := range httpRouteList.Items {
-               hr := v1beta1.ReferenceGrantFrom{
+       for _, grpcRoute := range grpcRouteList.Items {
+               gr := v1beta1.ReferenceGrantFrom{
                        Group:     gatewayv1.GroupName,
-                       Kind:      KindHTTPRoute,
-                       Namespace: v1beta1.Namespace(httpRoute.GetNamespace()),
+                       Kind:      KindGRPCRoute,
+                       Namespace: v1beta1.Namespace(grpcRoute.GetNamespace()),
                }
                for _, from := range grant.Spec.From {
-                       if from == hr {
+                       if from == gr {
                                requests = append(requests, reconcile.Request{
                                        NamespacedName: client.ObjectKey{
-                                               Namespace: 
httpRoute.GetNamespace(),
-                                               Name:      httpRoute.GetName(),
+                                               Namespace: 
grpcRoute.GetNamespace(),
+                                               Name:      grpcRoute.GetName(),
                                        },
                                })
                        }
diff --git a/internal/controller/httproute_controller.go 
b/internal/controller/httproute_controller.go
index 6fe87881..8cd7b507 100644
--- a/internal/controller/httproute_controller.go
+++ b/internal/controller/httproute_controller.go
@@ -77,7 +77,7 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr 
ctrl.Manager) error {
                For(&gatewayv1.HTTPRoute{}).
                WithEventFilter(predicate.GenerationChangedPredicate{}).
                Watches(&discoveryv1.EndpointSlice{},
-                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceBef),
+                       
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByServiceRef),
                ).
                Watches(&v1alpha1.PluginConfig{},
                        
handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesByExtensionRef),
@@ -260,7 +260,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx 
context.Context, req ctrl.Request) (
        return ctrl.Result{}, nil
 }
 
-func (r *HTTPRouteReconciler) listHTTPRoutesByServiceBef(ctx context.Context, 
obj client.Object) []reconcile.Request {
+func (r *HTTPRouteReconciler) listHTTPRoutesByServiceRef(ctx context.Context, 
obj client.Object) []reconcile.Request {
        endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
        if !ok {
                r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to EndpointSlice")
@@ -291,7 +291,7 @@ func (r *HTTPRouteReconciler) 
listHTTPRoutesByServiceBef(ctx context.Context, ob
 func (r *HTTPRouteReconciler) listHTTPRoutesByExtensionRef(ctx 
context.Context, obj client.Object) []reconcile.Request {
        pluginconfig, ok := obj.(*v1alpha1.PluginConfig)
        if !ok {
-               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to EndpointSlice")
+               r.Log.Error(fmt.Errorf("unexpected object type"), "failed to 
convert object to PluginConfig")
                return nil
        }
        namespace := pluginconfig.GetNamespace()
diff --git a/internal/controller/indexer/grpcroute.go 
b/internal/controller/indexer/grpcroute.go
new file mode 100644
index 00000000..656acf68
--- /dev/null
+++ b/internal/controller/indexer/grpcroute.go
@@ -0,0 +1,106 @@
+// 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 indexer
+
+import (
+       "context"
+
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
+)
+
+func setupGRPCRouteIndexer(mgr ctrl.Manager) error {
+       if err := mgr.GetFieldIndexer().IndexField(
+               context.Background(),
+               &gatewayv1.GRPCRoute{},
+               ParentRefs,
+               GRPCRouteParentRefsIndexFunc,
+       ); err != nil {
+               return err
+       }
+
+       if err := mgr.GetFieldIndexer().IndexField(
+               context.Background(),
+               &gatewayv1.GRPCRoute{},
+               ExtensionRef,
+               GRPCRouteExtensionIndexFunc,
+       ); err != nil {
+               return err
+       }
+
+       if err := mgr.GetFieldIndexer().IndexField(
+               context.Background(),
+               &gatewayv1.GRPCRoute{},
+               ServiceIndexRef,
+               GRPCRouteServiceIndexFunc,
+       ); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func GRPCRouteParentRefsIndexFunc(rawObj client.Object) []string {
+       gr := rawObj.(*gatewayv1.GRPCRoute)
+       keys := make([]string, 0, len(gr.Spec.ParentRefs))
+       for _, ref := range gr.Spec.ParentRefs {
+               ns := gr.GetNamespace()
+               if ref.Namespace != nil {
+                       ns = string(*ref.Namespace)
+               }
+               keys = append(keys, GenIndexKey(ns, string(ref.Name)))
+       }
+       return keys
+}
+
+func GRPCRouteServiceIndexFunc(rawObj client.Object) []string {
+       gr := rawObj.(*gatewayv1.GRPCRoute)
+       keys := make([]string, 0, len(gr.Spec.Rules))
+       for _, rule := range gr.Spec.Rules {
+               for _, backend := range rule.BackendRefs {
+                       namespace := gr.GetNamespace()
+                       if backend.Kind != nil && *backend.Kind != 
internaltypes.KindService {
+                               continue
+                       }
+                       if backend.Namespace != nil {
+                               namespace = string(*backend.Namespace)
+                       }
+                       keys = append(keys, GenIndexKey(namespace, 
string(backend.Name)))
+               }
+       }
+       return keys
+}
+
+func GRPCRouteExtensionIndexFunc(rawObj client.Object) []string {
+       gr := rawObj.(*gatewayv1.GRPCRoute)
+       keys := make([]string, 0, len(gr.Spec.Rules))
+       for _, rule := range gr.Spec.Rules {
+               for _, filter := range rule.Filters {
+                       if filter.Type != gatewayv1.GRPCRouteFilterExtensionRef 
|| filter.ExtensionRef == nil {
+                               continue
+                       }
+                       if filter.ExtensionRef.Kind == 
internaltypes.KindPluginConfig {
+                               keys = append(keys, 
GenIndexKey(gr.GetNamespace(), string(filter.ExtensionRef.Name)))
+                       }
+               }
+       }
+       return keys
+}
diff --git a/internal/controller/indexer/indexer.go 
b/internal/controller/indexer/indexer.go
index e89a3934..a63cef6f 100644
--- a/internal/controller/indexer/indexer.go
+++ b/internal/controller/indexer/indexer.go
@@ -55,6 +55,7 @@ func SetupIndexer(mgr ctrl.Manager) error {
        for _, setup := range []func(ctrl.Manager) error{
                setupGatewayIndexer,
                setupHTTPRouteIndexer,
+               setupGRPCRouteIndexer,
                setupIngressIndexer,
                setupConsumerIndexer,
                setupBackendTrafficPolicyIndexer,
diff --git a/internal/controller/utils.go b/internal/controller/utils.go
index 3d0c5018..3b2e7d5b 100644
--- a/internal/controller/utils.go
+++ b/internal/controller/utils.go
@@ -60,6 +60,7 @@ import (
 const (
        KindGateway            = "Gateway"
        KindHTTPRoute          = "HTTPRoute"
+       KindGRPCRoute          = "GRPCRoute"
        KindGatewayClass       = "GatewayClass"
        KindIngress            = "Ingress"
        KindIngressClass       = "IngressClass"
@@ -356,6 +357,7 @@ func ParseRouteParentRefs(
                matched := false
                reason := gatewayv1.RouteReasonNoMatchingParent
                var listenerName string
+               var matchedListener gatewayv1.Listener
 
                for _, listener := range gateway.Spec.Listeners {
                        if parentRef.SectionName != nil {
@@ -397,6 +399,7 @@ func ParseRouteParentRefs(
                        // TODO: check if the listener status is programmed
 
                        matched = true
+                       matchedListener = listener
                        break
                }
 
@@ -404,6 +407,7 @@ func ParseRouteParentRefs(
                        gateways = append(gateways, RouteParentRefContext{
                                Gateway:      &gateway,
                                ListenerName: listenerName,
+                               Listener:     &matchedListener,
                                Conditions: []metav1.Condition{{
                                        Type:               
string(gatewayv1.RouteConditionAccepted),
                                        Status:             
metav1.ConditionTrue,
@@ -415,6 +419,7 @@ func ParseRouteParentRefs(
                        gateways = append(gateways, RouteParentRefContext{
                                Gateway:      &gateway,
                                ListenerName: listenerName,
+                               Listener:     &matchedListener,
                                Conditions: []metav1.Condition{{
                                        Type:               
string(gatewayv1.RouteConditionAccepted),
                                        Status:             
metav1.ConditionFalse,
@@ -488,6 +493,8 @@ func routeHostnamesIntersectsWithListenerHostname(route 
client.Object, listener
        switch r := route.(type) {
        case *gatewayv1.HTTPRoute:
                return listenerHostnameIntersectWithRouteHostnames(listener, 
r.Spec.Hostnames)
+       case *gatewayv1.GRPCRoute:
+               return listenerHostnameIntersectWithRouteHostnames(listener, 
r.Spec.Hostnames)
        default:
                return false
        }
@@ -638,7 +645,7 @@ func isRouteNamespaceAllowed(
 
 func routeMatchesListenerType(route client.Object, listener 
gatewayv1.Listener) bool {
        switch route.(type) {
-       case *gatewayv1.HTTPRoute:
+       case *gatewayv1.HTTPRoute, *gatewayv1.GRPCRoute:
                if listener.Protocol != gatewayv1.HTTPProtocolType && 
listener.Protocol != gatewayv1.HTTPSProtocolType {
                        return false
                }
diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go
index 79886225..46b896f5 100644
--- a/internal/manager/controllers.go
+++ b/internal/manager/controllers.go
@@ -77,15 +77,17 @@ import (
 // GatewayAPI
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gatewayclasses,verbs=get;list;watch;update
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gatewayclasses/status,verbs=get;update
-// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update
+// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways/status,verbs=get;update
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes/status,verbs=get;update
-// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants,verbs=list;watch;update
+// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants,verbs=get;list;watch
 // 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants/status,verbs=get;update
+// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=grpcroutes,verbs=get;list;watch
+// 
+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=grpcroutes/status,verbs=get;update
 
 // Networking
-// 
+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;update
+// 
+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch
 // 
+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update
 // 
+kubebuilder:rbac:groups=networking.k8s.io,resources=ingressclasses,verbs=get;list;watch
 
@@ -119,6 +121,14 @@ func setupControllers(ctx context.Context, mgr 
manager.Manager, pro provider.Pro
                        Updater:  updater,
                        Readier:  readier,
                },
+               &controller.GRPCRouteReconciler{
+                       Client:   mgr.GetClient(),
+                       Scheme:   mgr.GetScheme(),
+                       Log:      
ctrl.LoggerFrom(ctx).WithName("controllers").WithName(types.KindGRPCRoute),
+                       Provider: pro,
+                       Updater:  updater,
+                       Readier:  readier,
+               },
                &controller.IngressReconciler{
                        Client:   mgr.GetClient(),
                        Scheme:   mgr.GetScheme(),
@@ -200,6 +210,7 @@ func registerReadinessGVK(c client.Client, readier 
readiness.ReadinessManager) {
                {
                        GVKs: []schema.GroupVersionKind{
                                types.GvkOf(&gatewayv1.HTTPRoute{}),
+                               types.GvkOf(&gatewayv1.GRPCRoute{}),
                        },
                },
                {
diff --git a/internal/provider/apisix/provider.go 
b/internal/provider/apisix/provider.go
index 01f5f4e3..f37fbe9e 100644
--- a/internal/provider/apisix/provider.go
+++ b/internal/provider/apisix/provider.go
@@ -108,6 +108,9 @@ func (d *apisixProvider) Update(ctx context.Context, tctx 
*provider.TranslateCon
        case *gatewayv1.HTTPRoute:
                result, err = d.translator.TranslateHTTPRoute(tctx, 
t.DeepCopy())
                resourceTypes = append(resourceTypes, adctypes.TypeService)
+       case *gatewayv1.GRPCRoute:
+               result, err = d.translator.TranslateGRPCRoute(tctx, 
t.DeepCopy())
+               resourceTypes = append(resourceTypes, adctypes.TypeService)
        case *gatewayv1.Gateway:
                result, err = d.translator.TranslateGateway(tctx, t.DeepCopy())
                resourceTypes = append(resourceTypes, adctypes.TypeGlobalRule, 
adctypes.TypeSSL, adctypes.TypePluginMetadata)
@@ -178,7 +181,7 @@ func (d *apisixProvider) Delete(ctx context.Context, obj 
client.Object) error {
        var resourceTypes []string
        var labels map[string]string
        switch obj.(type) {
-       case *gatewayv1.HTTPRoute, *apiv2.ApisixRoute:
+       case *gatewayv1.HTTPRoute, *apiv2.ApisixRoute, *gatewayv1.GRPCRoute:
                resourceTypes = append(resourceTypes, adctypes.TypeService)
                labels = label.GenLabel(obj)
        case *gatewayv1.Gateway:
diff --git a/internal/provider/apisix/status.go 
b/internal/provider/apisix/status.go
index a254b815..01768f9e 100644
--- a/internal/provider/apisix/status.go
+++ b/internal/provider/apisix/status.go
@@ -144,6 +144,41 @@ func (d *apisixProvider) updateStatus(nnk 
types.NamespacedNameKind, condition me
                                return cp
                        }),
                })
+       case types.KindGRPCRoute:
+               parentRefs := 
d.client.ConfigManager.GetConfigRefsByResourceKey(nnk)
+               log.Debugw("updating GRPCRoute status", zap.Any("parentRefs", 
parentRefs))
+               gatewayRefs := map[types.NamespacedNameKind]struct{}{}
+               for _, parentRef := range parentRefs {
+                       if parentRef.Kind == types.KindGateway {
+                               gatewayRefs[parentRef] = struct{}{}
+                       }
+               }
+               d.updater.Update(status.Update{
+                       NamespacedName: nnk.NamespacedName(),
+                       Resource:       &gatewayv1.GRPCRoute{},
+                       Mutator: status.MutatorFunc(func(obj client.Object) 
client.Object {
+                               cp := obj.(*gatewayv1.GRPCRoute).DeepCopy()
+                               gatewayNs := cp.GetNamespace()
+                               for i, ref := range cp.Status.Parents {
+                                       ns := gatewayNs
+                                       if ref.ParentRef.Namespace != nil {
+                                               ns = 
string(*ref.ParentRef.Namespace)
+                                       }
+                                       if ref.ParentRef.Kind == nil || 
*ref.ParentRef.Kind == types.KindGateway {
+                                               nnk := types.NamespacedNameKind{
+                                                       Name:      
string(ref.ParentRef.Name),
+                                                       Namespace: ns,
+                                                       Kind:      
types.KindGateway,
+                                               }
+                                               if _, ok := gatewayRefs[nnk]; 
ok {
+                                                       ref.Conditions = 
cutils.MergeCondition(ref.Conditions, condition)
+                                                       cp.Status.Parents[i] = 
ref
+                                               }
+                                       }
+                               }
+                               return cp
+                       }),
+               })
        }
 }
 
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 3f0bcded..92ada510 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -46,6 +46,7 @@ type TranslateContext struct {
        BackendRefs      []gatewayv1.BackendRef
        GatewayTLSConfig []gatewayv1.GatewayTLSConfig
        Credentials      []v1alpha1.Credential
+       Listeners        []gatewayv1.Listener
 
        EndpointSlices         
map[k8stypes.NamespacedName][]discoveryv1.EndpointSlice
        Secrets                map[k8stypes.NamespacedName]*corev1.Secret
diff --git a/internal/types/k8s.go b/internal/types/k8s.go
index 3f50033a..92cafdc7 100644
--- a/internal/types/k8s.go
+++ b/internal/types/k8s.go
@@ -22,6 +22,7 @@ import (
        netv1 "k8s.io/api/networking/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+       gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 
        "github.com/apache/apisix-ingress-controller/api/v1alpha1"
        v2 "github.com/apache/apisix-ingress-controller/api/v2"
@@ -32,6 +33,7 @@ const DefaultIngressClassAnnotation = 
"ingressclass.kubernetes.io/is-default-cla
 const (
        KindGateway              = "Gateway"
        KindHTTPRoute            = "HTTPRoute"
+       KindGRPCRoute            = "GRPCRoute"
        KindGatewayClass         = "GatewayClass"
        KindIngress              = "Ingress"
        KindIngressClass         = "IngressClass"
@@ -57,6 +59,8 @@ func KindOf(obj any) string {
                return KindGateway
        case *gatewayv1.HTTPRoute:
                return KindHTTPRoute
+       case *gatewayv1.GRPCRoute:
+               return KindGRPCRoute
        case *gatewayv1.GatewayClass:
                return KindGatewayClass
        case *netv1.Ingress:
@@ -97,8 +101,10 @@ func KindOf(obj any) string {
 func GvkOf(obj any) schema.GroupVersionKind {
        kind := KindOf(obj)
        switch obj.(type) {
-       case *gatewayv1.Gateway, *gatewayv1.HTTPRoute, *gatewayv1.GatewayClass:
+       case *gatewayv1.Gateway, *gatewayv1.HTTPRoute, *gatewayv1.GatewayClass, 
*gatewayv1.GRPCRoute:
                return gatewayv1.SchemeGroupVersion.WithKind(kind)
+       case *gatewayv1beta1.ReferenceGrant:
+               return gatewayv1beta1.SchemeGroupVersion.WithKind(kind)
        case *netv1.Ingress, *netv1.IngressClass:
                return netv1.SchemeGroupVersion.WithKind(kind)
        case *corev1.Secret, *corev1.Service:
diff --git a/test/e2e/framework/grpc.go b/test/e2e/framework/grpc.go
new file mode 100644
index 00000000..a064a075
--- /dev/null
+++ b/test/e2e/framework/grpc.go
@@ -0,0 +1,58 @@
+// 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 framework
+
+import (
+       "bytes"
+       _ "embed"
+       "text/template"
+       "time"
+
+       "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/stretchr/testify/assert"
+)
+
+var (
+       //go:embed manifests/grpc-backend.yaml
+       _grpcBackendDeployment string
+       grpcBackendTpl         *template.Template
+)
+
+type GRPCBackendOpts struct {
+       KubectlOptions *k8s.KubectlOptions
+}
+
+func init() {
+       tpl, err := template.New("grpc-backend").Parse(_grpcBackendDeployment)
+       if err != nil {
+               panic(err)
+       }
+       grpcBackendTpl = tpl
+}
+
+func (f *Framework) DeployGRPCBackend(opts GRPCBackendOpts) {
+       if opts.KubectlOptions == nil {
+               opts.KubectlOptions = f.kubectlOpts
+       }
+       buf := bytes.NewBuffer(nil)
+
+       err := grpcBackendTpl.Execute(buf, opts)
+       assert.Nil(f.GinkgoT, err, "rendering grpc backend spec")
+
+       k8s.KubectlApplyFromString(f.GinkgoT, opts.KubectlOptions, buf.String())
+
+       k8s.WaitUntilDeploymentAvailable(f.GinkgoT, opts.KubectlOptions, 
"grpc-infra-backend-v1", 10, 10*time.Second)
+}
diff --git a/test/e2e/framework/manifests/grpc-backend.yaml 
b/test/e2e/framework/manifests/grpc-backend.yaml
new file mode 100644
index 00000000..953c75b0
--- /dev/null
+++ b/test/e2e/framework/manifests/grpc-backend.yaml
@@ -0,0 +1,63 @@
+# 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: grpc-infra-backend-v1
+spec:
+  selector:
+    app: grpc-infra-backend-v1
+  ports:
+  - protocol: TCP
+    port: 8080
+    targetPort: 3000
+    appProtocol: kubernetes.io/h2c
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: grpc-infra-backend-v1
+  labels:
+    app: grpc-infra-backend-v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: grpc-infra-backend-v1
+  template:
+    metadata:
+      labels:
+        app: grpc-infra-backend-v1
+    spec:
+      containers:
+      - name: grpc-infra-backend-v1
+        image: 
gcr.io/k8s-staging-gateway-api/echo-basic:v20240412-v1.0.0-394-g40c666fd
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        - name: GRPC_ECHO_SERVER
+          value: "1"
+        resources:
+          requests:
+            cpu: 10m
diff --git a/test/e2e/framework/manifests/ingress.yaml 
b/test/e2e/framework/manifests/ingress.yaml
index 2347dae2..255a5d42 100644
--- a/test/e2e/framework/manifests/ingress.yaml
+++ b/test/e2e/framework/manifests/ingress.yaml
@@ -81,43 +81,9 @@ rules:
   - ""
   resources:
   - namespaces
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - ""
-  resources:
+  - pods
   - secrets
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - ""
-  resources:
   - services
-  - pods
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - discovery.k8s.io
-  resources:
-  - endpointslices
   verbs:
   - get
   - list
@@ -156,82 +122,64 @@ rules:
   - get
   - update
 - apiGroups:
-  - gateway.networking.k8s.io
+  - coordination.k8s.io
   resources:
-  - gatewayclasses
+  - leases
   verbs:
+  - create
+  - delete
   - get
   - list
+  - patch
   - update
   - watch
 - apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - gatewayclasses/status
-  verbs:
-  - get
-  - update
-- apiGroups:
-  - gateway.networking.k8s.io
+  - discovery.k8s.io
   resources:
-  - gateways
+  - endpointslices
   verbs:
   - get
   - list
-  - update
   - watch
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
-  - gateways/status
-  verbs:
-  - get
-  - update
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - httproutes
+  - gatewayclasses
   verbs:
   - get
   - list
+  - update
   - watch
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
+  - gatewayclasses/status
+  - gateways/status
+  - grpcroutes/status
   - httproutes/status
+  - referencegrants/status
   verbs:
   - get
   - update
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
+  - gateways
+  - grpcroutes
+  - httproutes
   - referencegrants
   verbs:
   - get
   - list
   - watch
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - referencegrants/status
-  verbs:
-  - get
 - apiGroups:
   - networking.k8s.io
   resources:
   - ingressclasses
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
   - ingresses
   verbs:
   - get
   - list
-  - update
   - watch
 - apiGroups:
   - networking.k8s.io
diff --git a/test/e2e/gatewayapi/grpcroute.go b/test/e2e/gatewayapi/grpcroute.go
new file mode 100644
index 00000000..e4485fe0
--- /dev/null
+++ b/test/e2e/gatewayapi/grpcroute.go
@@ -0,0 +1,289 @@
+// 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 gatewayapi
+
+import (
+       "fmt"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+       "google.golang.org/grpc/metadata"
+       pb "sigs.k8s.io/gateway-api/conformance/echo-basic/grpcechoserver"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test GRPCRoute", Label("networking.k8s.io", "grpcroute"), 
func() {
+       s := scaffold.NewDefaultScaffold()
+
+       BeforeEach(func() {
+               By("deploy grpc backend")
+               s.DeployGRPCBackend()
+
+               By("create GatewayProxy")
+               
Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(),
 "creating GatewayProxy")
+
+               By("create GatewayClass")
+               
Expect(s.CreateResourceFromString(s.GetGatewayClassYaml())).NotTo(HaveOccurred(),
 "creating GatewayClass")
+
+               s.RetryAssertion(func() string {
+                       gcyaml, _ := s.GetResourceYaml("GatewayClass", 
s.Namespace())
+                       return gcyaml
+               }).Should(
+                       And(
+                               ContainSubstring(`status: "True"`),
+                               ContainSubstring("message: the gatewayclass has 
been accepted by the apisix-ingress-controller"),
+                       ),
+                       "check GatewayClass condition",
+               )
+
+               By("create Gateway")
+               
Expect(s.CreateResourceFromString(s.GetGatewayYaml())).NotTo(HaveOccurred(), 
"creating Gateway")
+
+               s.RetryAssertion(func() string {
+                       gcyaml, _ := s.GetResourceYaml("Gateway", s.Namespace())
+                       return gcyaml
+               }).Should(
+                       And(
+                               ContainSubstring(`status: "True"`),
+                               ContainSubstring("message: the gateway has been 
accepted by the apisix-ingress-controlle"),
+                       ),
+                       "check Gateway condition status",
+               )
+       })
+
+       Context("GRPCRoute Filters", func() {
+               var reqHeaderModifyWithAdd = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: GRPCRoute
+metadata:
+  name: req-header-modify
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - matches: 
+    filters:
+    - type: RequestHeaderModifier
+      requestHeaderModifier:
+        add:
+        - name: X-Req-Add
+          value: "plugin-req-add"
+        set:
+        - name: X-Req-Set
+          value: "plugin-req-set"
+        remove:
+        - X-Req-Removed
+    backendRefs:
+    - name: grpc-infra-backend-v1
+      port: 8080
+`
+               var respHeaderModifyWithAdd = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: GRPCRoute
+metadata:
+  name: resp-header-modify
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - matches: 
+    filters:
+    - type: ResponseHeaderModifier
+      responseHeaderModifier:
+        add:
+        - name: X-Resp-Add
+          value: "plugin-resp-add"
+    backendRefs:
+    - name: grpc-infra-backend-v1
+      port: 8080
+`
+               It("GRPCRoute RequestHeaderModifier", func() {
+                       By("create GRPCRoute")
+                       s.ResourceApplied("GRPCRoute", "req-header-modify", 
fmt.Sprintf(reqHeaderModifyWithAdd, s.Namespace()), 1)
+
+                       testCases := []scaffold.ExpectedResponse{
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                               },
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                                       Headers: map[string]string{
+                                               "X-Req-Add": "plugin-req-add",
+                                       },
+                               },
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                                       RequestMetadata: 
&scaffold.RequestMetadata{
+                                               Metadata: map[string]string{
+                                                       "X-Req-Set": "test-set",
+                                               },
+                                       },
+                                       Headers: map[string]string{
+                                               "X-Req-Set": "plugin-req-set",
+                                       },
+                               },
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                                       RequestMetadata: 
&scaffold.RequestMetadata{
+                                               Metadata: map[string]string{
+                                                       "X-Req-Removed": 
"to-be-removed",
+                                               },
+                                       },
+                                       Headers: map[string]string{
+                                               "X-Req-Removed": "",
+                                       },
+                               },
+                       }
+
+                       for i := range testCases {
+                               tc := testCases[i]
+                               s.RetryAssertion(func() error {
+                                       return s.RequestEchoBackend(tc)
+                               }).ShouldNot(HaveOccurred(), "request grpc 
backend")
+                       }
+               })
+
+               It("GRPCRoute ResponseHeaderModifier", func() {
+                       By("create GRPCRoute")
+                       s.ResourceApplied("GRPCRoute", "resp-header-modify", 
fmt.Sprintf(respHeaderModifyWithAdd, s.Namespace()), 1)
+
+                       testCases := []scaffold.ExpectedResponse{
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                               },
+                               {
+                                       EchoRequest: &pb.EchoRequest{},
+                                       EchoResponse: scaffold.EchoResponse{
+                                               Headers: &metadata.MD{
+                                                       "X-Resp-Add": 
[]string{"plugin-resp-add"},
+                                               },
+                                       },
+                               },
+                       }
+
+                       for i := range testCases {
+                               tc := testCases[i]
+                               s.RetryAssertion(func() error {
+                                       return s.RequestEchoBackend(tc)
+                               }).ShouldNot(HaveOccurred(), "request grpc 
backend")
+                       }
+               })
+
+               It("GRPCRoute ExtensionRef", func() {
+                       var rewritePlugin = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+  name: rewrite
+spec:
+  plugins:
+  - name: proxy-rewrite
+    config:
+      headers:
+        add:
+          x-req-add: "plugin-req-add"
+`
+                       var rewritePluginUpdate = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: PluginConfig
+metadata:
+  name: rewrite
+spec:
+  plugins:
+  - name: proxy-rewrite
+    config:
+      headers:
+        add:
+          x-req-add: "plugin-req-add-v2"
+`
+                       var extensionRefRewritePlugin = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: GRPCRoute
+metadata:
+  name: rewrite
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - matches: 
+    filters:
+    - type: ExtensionRef
+      extensionRef:
+        group: apisix.apache.org
+        kind: PluginConfig
+        name: rewrite
+    backendRefs:
+    - name: grpc-infra-backend-v1
+      port: 8080
+`
+                       
Expect(s.CreateResourceFromString(rewritePlugin)).NotTo(HaveOccurred(), 
"creating PluginConfig")
+                       s.ResourceApplied("GRPCRoute", "rewrite", 
fmt.Sprintf(extensionRefRewritePlugin, s.Namespace()), 1)
+
+                       testCases := []struct {
+                               scaffold.ExpectedResponse
+                               Helper func()
+                       }{
+                               {
+                                       ExpectedResponse: 
scaffold.ExpectedResponse{
+                                               EchoRequest: &pb.EchoRequest{},
+                                       },
+                               },
+                               {
+                                       ExpectedResponse: 
scaffold.ExpectedResponse{
+                                               EchoRequest: &pb.EchoRequest{},
+                                               Headers: map[string]string{
+                                                       "x-req-add": 
"plugin-req-add",
+                                               },
+                                       },
+                               },
+                               {
+                                       ExpectedResponse: 
scaffold.ExpectedResponse{
+                                               EchoRequest: &pb.EchoRequest{},
+                                               Headers: map[string]string{
+                                                       "x-req-add": 
"plugin-req-add-v2",
+                                               },
+                                       },
+                                       Helper: func() {
+                                               
Expect(s.CreateResourceFromString(rewritePluginUpdate)).NotTo(HaveOccurred(), 
"updating PluginConfig")
+                                       },
+                               },
+                       }
+
+                       for i := range testCases {
+                               if testCases[i].Helper != nil {
+                                       testCases[i].Helper()
+                               }
+                               tc := testCases[i].ExpectedResponse
+                               s.RetryAssertion(func() error {
+                                       return s.RequestEchoBackend(tc)
+                               }).ShouldNot(HaveOccurred(), "request grpc 
backend")
+                       }
+               })
+
+               // TODO: add GRPCRoute RequestMirror test
+               /*
+                       It("GRPCRoute RequestMirror", func() {})
+               */
+       })
+
+       // TODO: add BackendTrafficPolicy test
+       /*
+               Context("GRPCRoute With BackendTrafficPolicy", func() {})
+       */
+})
diff --git a/test/e2e/scaffold/grpc.go b/test/e2e/scaffold/grpc.go
new file mode 100644
index 00000000..00a694da
--- /dev/null
+++ b/test/e2e/scaffold/grpc.go
@@ -0,0 +1,159 @@
+// 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 scaffold
+
+import (
+       "context"
+       _ "embed"
+       "fmt"
+       "strings"
+       "time"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/codes"
+       "google.golang.org/grpc/credentials/insecure"
+       "google.golang.org/grpc/metadata"
+       "google.golang.org/grpc/status"
+       pb "sigs.k8s.io/gateway-api/conformance/echo-basic/grpcechoserver"
+)
+
+type RequestMetadata struct {
+       // The :authority pseudoheader to set on the outgoing request.
+       Authority string
+
+       // Outgoing metadata pairs to add to the request.
+       Metadata map[string]string
+}
+
+type ExpectedResponse struct {
+       EchoRequest      *pb.EchoRequest
+       EchoTwoRequest   *pb.EchoRequest
+       EchoThreeRequest *pb.EchoRequest
+
+       RequestMetadata *RequestMetadata
+
+       Headers map[string]string
+
+       EchoResponse EchoResponse
+}
+
+type EchoResponse struct {
+       Code     codes.Code
+       Headers  *metadata.MD
+       Trailers *metadata.MD
+       Response *pb.EchoResponse
+}
+
+func (s *Scaffold) DeployGRPCBackend() {
+       s.Framework.DeployGRPCBackend(framework.GRPCBackendOpts{
+               KubectlOptions: s.kubectlOptions,
+       })
+}
+
+func (s *Scaffold) RequestEchoBackend(exp ExpectedResponse) error {
+       endpoint := s.apisixTunnels.HTTP.Endpoint()
+
+       endpoint = strings.Replace(endpoint, "localhost", "127.0.0.1", 1)
+
+       dialOpts := 
[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
+       if exp.RequestMetadata != nil && exp.RequestMetadata.Authority != "" {
+               dialOpts = append(dialOpts, 
grpc.WithAuthority(exp.RequestMetadata.Authority))
+       }
+       conn, err := grpc.NewClient(endpoint, dialOpts...)
+       if err != nil {
+               return err
+       }
+       defer func() { _ = conn.Close() }()
+
+       ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+       defer cancel()
+
+       if exp.RequestMetadata != nil && len(exp.RequestMetadata.Metadata) > 0 {
+               ctx = metadata.NewOutgoingContext(ctx, 
metadata.New(exp.RequestMetadata.Metadata))
+       }
+
+       var (
+               resp = &EchoResponse{
+                       Headers:  &metadata.MD{},
+                       Trailers: &metadata.MD{},
+               }
+       )
+
+       client := pb.NewGrpcEchoClient(conn)
+       switch {
+       case exp.EchoRequest != nil:
+               resp.Response, err = client.Echo(ctx, exp.EchoRequest, 
grpc.Header(resp.Headers), grpc.Trailer(resp.Trailers))
+       case exp.EchoTwoRequest != nil:
+               resp.Response, err = client.EchoTwo(ctx, exp.EchoTwoRequest, 
grpc.Header(resp.Headers), grpc.Trailer(resp.Trailers))
+       case exp.EchoThreeRequest != nil:
+               resp.Response, err = client.EchoThree(ctx, 
exp.EchoThreeRequest, grpc.Header(resp.Headers), grpc.Trailer(resp.Trailers))
+       }
+       if err != nil {
+               resp.Code = status.Code(err)
+               fmt.Printf("RPC finished with error: %v\n", err)
+       } else {
+               resp.Code = codes.OK
+       }
+       if err := expectEchoResponses(&exp, resp); err != nil {
+               return err
+       }
+       return nil
+}
+
+func expectEchoResponses(expected *ExpectedResponse, actual *EchoResponse) 
error {
+       if expected.EchoResponse.Code != actual.Code {
+               return fmt.Errorf("expected status code to be %s (%d), but got 
%s (%d)",
+                       expected.EchoResponse.Code.String(),
+                       expected.EchoResponse.Code,
+                       actual.Code.String(),
+                       actual.Code,
+               )
+       }
+       if expected.EchoResponse.Headers != nil {
+               for key, values := range *expected.EchoResponse.Headers {
+                       actualValues := actual.Headers.Get(key)
+                       if len(values) != len(actualValues) {
+                               return fmt.Errorf("expected header %q to have 
%d values, but got %d", key, len(values), len(actualValues))
+                       }
+                       for i, v := range values {
+                               if actualValues[i] != v {
+                                       return fmt.Errorf("expected header %q 
to have value %q, but got %q", key, v, actualValues[i])
+                               }
+                       }
+               }
+       }
+       if len(expected.Headers) > 0 {
+               msgHeaders := actual.Response.GetAssertions().GetHeaders()
+
+               kv := make(map[string]string)
+               for _, header := range msgHeaders {
+                       kv[header.GetKey()] = header.GetValue()
+               }
+               for key, value := range expected.Headers {
+                       actualValue, ok := kv[strings.ToLower(key)]
+                       if !ok {
+                               if value != "" {
+                                       return fmt.Errorf("expected header %q 
to be present, but not found", key)
+                               }
+                               continue
+                       }
+                       if actualValue != value {
+                               return fmt.Errorf("expected header %q to be %q, 
but got %q", key, value, actualValue)
+                       }
+               }
+       }
+       return nil
+}

Reply via email to