ronething commented on code in PR #2570:
URL: 
https://github.com/apache/apisix-ingress-controller/pull/2570#discussion_r2367238835


##########
internal/adc/translator/grpcroute.go:
##########
@@ -0,0 +1,364 @@
+// 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")

Review Comment:
   ```suggestion
                        return nil, fmt.Errorf("service and method cannot both 
be empty for exact match type")
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@apisix.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to