Skip to content

Commit

Permalink
address comments and move GenerateKongRoutesFromGRPCRouteRule to tran…
Browse files Browse the repository at this point in the history
…slators
  • Loading branch information
randmonkey committed May 15, 2023
1 parent ff058a0 commit 3696668
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 141 deletions.
106 changes: 2 additions & 104 deletions internal/dataplane/parser/translate_grpcroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ package parser
import (
"fmt"

"github.com/kong/go-kong/kong"
"github.com/samber/lo"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser/translators"
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
)

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -63,10 +60,9 @@ func (p *Parser) ingressRulesFromGRPCRoute(result *ingressRules, grpcroute *gate
// determine the routes needed to route traffic to services for this rule
var routes []kongstate.Route
if p.featureFlags.ExpressionRoutes {
routes = translators.GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute, ruleNumber, rule)
routes = translators.GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute, ruleNumber)
} else {
// REVIEW: move generateKongRoutesFromGRPCRouteRule to package translators?
routes = generateKongRoutesFromGRPCRouteRule(grpcroute, ruleNumber, rule)
routes = translators.GenerateKongRoutesFromGRPCRouteRule(grpcroute, ruleNumber)
}

// create a service and attach the routes to it
Expand All @@ -83,91 +79,6 @@ func (p *Parser) ingressRulesFromGRPCRoute(result *ingressRules, grpcroute *gate
return nil
}

func getGRPCMatchDefaults() (
map[gatewayv1alpha2.GRPCMethodMatchType]string,
map[gatewayv1alpha2.GRPCMethodMatchType]string,
) {
// Kong routes derived from a GRPCRoute use a path composed of the match's gRPC service and method
// If either the service or method is omitted, there is a default regex determined by the match type
// https://gateway-api.sigs.k8s.io/geps/gep-1016/#matcher-types describes the defaults

// default path components for the GRPC service
return map[gatewayv1alpha2.GRPCMethodMatchType]string{
gatewayv1alpha2.GRPCMethodMatchType(""): ".+",
gatewayv1alpha2.GRPCMethodMatchExact: ".+",
gatewayv1alpha2.GRPCMethodMatchRegularExpression: ".+",
},
// default path components for the GRPC method
map[gatewayv1alpha2.GRPCMethodMatchType]string{
gatewayv1alpha2.GRPCMethodMatchType(""): "",
gatewayv1alpha2.GRPCMethodMatchExact: "",
gatewayv1alpha2.GRPCMethodMatchRegularExpression: ".+",
}
}

func generateKongRoutesFromGRPCRouteRule(grpcroute *gatewayv1alpha2.GRPCRoute, ruleNumber int, rule gatewayv1alpha2.GRPCRouteRule) []kongstate.Route {
routes := make([]kongstate.Route, 0, len(rule.Matches))

// gather the k8s object information and hostnames from the grpcroute
ingressObjectInfo := util.FromK8sObject(grpcroute)

for matchNumber, match := range rule.Matches {
routeName := fmt.Sprintf(
"grpcroute.%s.%s.%d.%d",
grpcroute.Namespace,
grpcroute.Name,
ruleNumber,
matchNumber,
)

r := kongstate.Route{
Ingress: ingressObjectInfo,
Route: kong.Route{
Name: kong.String(routeName),
Protocols: kong.StringSlice("grpc", "grpcs"),
},
}

if match.Method != nil {
serviceMap, methodMap := getGRPCMatchDefaults()
var method, service string
matchMethod := match.Method.Method
matchService := match.Method.Service
var matchType gatewayv1alpha2.GRPCMethodMatchType
if match.Method.Type == nil {
matchType = gatewayv1alpha2.GRPCMethodMatchExact
} else {
matchType = *match.Method.Type
}
if matchMethod == nil {
method = methodMap[matchType]
} else {
method = *matchMethod
}
if matchService == nil {
service = serviceMap[matchType]
} else {
service = *matchService
}
r.Paths = append(r.Paths, kong.String(fmt.Sprintf("~/%s/%s", service, method)))
}

if len(grpcroute.Spec.Hostnames) > 0 {
r.Hosts = getGRPCRouteHostnamesAsSliceOfStringPointers(grpcroute)
}

r.Headers = map[string][]string{}
for _, hmatch := range match.Headers {
name := string(hmatch.Name)
r.Headers[name] = append(r.Headers[name], hmatch.Value)
}

routes = append(routes, r)
}

return routes
}

func grpcBackendRefsToBackendRefs(grpcBackendRef []gatewayv1alpha2.GRPCBackendRef) []gatewayv1beta1.BackendRef {
backendRefs := make([]gatewayv1beta1.BackendRef, 0, len(grpcBackendRef))

Expand All @@ -176,16 +87,3 @@ func grpcBackendRefsToBackendRefs(grpcBackendRef []gatewayv1alpha2.GRPCBackendRe
}
return backendRefs
}

// -----------------------------------------------------------------------------
// Translate GRPCRoute - Utils
// -----------------------------------------------------------------------------

// getGRPCRouteHostnamesAsSliceOfStringPointers translates the hostnames defined
// in an GRPCRoute specification into a []*string slice, which is the type required
// by kong.Route{}.
func getGRPCRouteHostnamesAsSliceOfStringPointers(grpcroute *gatewayv1alpha2.GRPCRoute) []*string {
return lo.Map(grpcroute.Spec.Hostnames, func(h gatewayv1beta1.Hostname, _ int) *string {
return lo.ToPtr(string(h))
})
}
115 changes: 115 additions & 0 deletions internal/dataplane/parser/translators/grpcroute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package translators

import (
"fmt"

"github.com/kong/go-kong/kong"
"github.com/samber/lo"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
)

func getGRPCMatchDefaults() (
map[gatewayv1alpha2.GRPCMethodMatchType]string,
map[gatewayv1alpha2.GRPCMethodMatchType]string,
) {
// Kong routes derived from a GRPCRoute use a path composed of the match's gRPC service and method
// If either the service or method is omitted, there is a default regex determined by the match type
// https://gateway-api.sigs.k8s.io/geps/gep-1016/#matcher-types describes the defaults

// default path components for the GRPC service
return map[gatewayv1alpha2.GRPCMethodMatchType]string{
gatewayv1alpha2.GRPCMethodMatchType(""): ".+",
gatewayv1alpha2.GRPCMethodMatchExact: ".+",
gatewayv1alpha2.GRPCMethodMatchRegularExpression: ".+",
},
// default path components for the GRPC method
map[gatewayv1alpha2.GRPCMethodMatchType]string{
gatewayv1alpha2.GRPCMethodMatchType(""): "",
gatewayv1alpha2.GRPCMethodMatchExact: "",
gatewayv1alpha2.GRPCMethodMatchRegularExpression: ".+",
}
}

func GenerateKongRoutesFromGRPCRouteRule(grpcroute *gatewayv1alpha2.GRPCRoute, ruleNumber int) []kongstate.Route {
if ruleNumber >= len(grpcroute.Spec.Rules) {
return nil
}
rule := grpcroute.Spec.Rules[ruleNumber]

routes := make([]kongstate.Route, 0, len(rule.Matches))
// gather the k8s object information and hostnames from the grpcroute
ingressObjectInfo := util.FromK8sObject(grpcroute)

for matchNumber, match := range rule.Matches {
routeName := fmt.Sprintf(
"grpcroute.%s.%s.%d.%d",
grpcroute.Namespace,
grpcroute.Name,
ruleNumber,
matchNumber,
)

r := kongstate.Route{
Ingress: ingressObjectInfo,
Route: kong.Route{
Name: kong.String(routeName),
Protocols: kong.StringSlice("grpc", "grpcs"),
},
}

if match.Method != nil {
serviceMap, methodMap := getGRPCMatchDefaults()
var method, service string
matchMethod := match.Method.Method
matchService := match.Method.Service
var matchType gatewayv1alpha2.GRPCMethodMatchType
if match.Method.Type == nil {
matchType = gatewayv1alpha2.GRPCMethodMatchExact
} else {
matchType = *match.Method.Type
}
if matchMethod == nil {
method = methodMap[matchType]
} else {
method = *matchMethod
}
if matchService == nil {
service = serviceMap[matchType]
} else {
service = *matchService
}
r.Paths = append(r.Paths, kong.String(fmt.Sprintf("~/%s/%s", service, method)))
}

if len(grpcroute.Spec.Hostnames) > 0 {
r.Hosts = getGRPCRouteHostnamesAsSliceOfStringPointers(grpcroute)
}

r.Headers = map[string][]string{}
for _, hmatch := range match.Headers {
name := string(hmatch.Name)
r.Headers[name] = append(r.Headers[name], hmatch.Value)
}

routes = append(routes, r)
}

return routes
}

// -----------------------------------------------------------------------------
// Translate GRPCRoute - Utils
// -----------------------------------------------------------------------------

// getGRPCRouteHostnamesAsSliceOfStringPointers translates the hostnames defined
// in an GRPCRoute specification into a []*string slice, which is the type required
// by kong.Route{}.
func getGRPCRouteHostnamesAsSliceOfStringPointers(grpcroute *gatewayv1alpha2.GRPCRoute) []*string {
return lo.Map(grpcroute.Spec.Hostnames, func(h gatewayv1beta1.Hostname, _ int) *string {
return lo.ToPtr(string(h))
})
}
15 changes: 12 additions & 3 deletions internal/dataplane/parser/translators/grpcroute_atc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ import (
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
)

func GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute *gatewayv1alpha2.GRPCRoute, ruleNumber int, rule gatewayv1alpha2.GRPCRouteRule) []kongstate.Route {
routes := make([]kongstate.Route, 0, len(rule.Matches))
// GenerateKongExpressionRoutesFromGRPCRouteRule generates expression based kong routes
// from a single GRPCRouteRule.
func GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute *gatewayv1alpha2.GRPCRoute, ruleNumber int) []kongstate.Route {
if ruleNumber >= len(grpcroute.Spec.Rules) {
return nil
}
rule := grpcroute.Spec.Rules[ruleNumber]

routes := make([]kongstate.Route, 0, len(rule.Matches))
// gather the k8s object information and hostnames from the grpcroute
ingressObjectInfo := util.FromK8sObject(grpcroute)

Expand Down Expand Up @@ -66,6 +72,10 @@ func generateMathcherFromGRPCMatch(match gatewayv1alpha2.GRPCRouteMatch, hostnam
routeMatcher.And(hostMatcher)
}

// override protocols from annotations.
// Because Kong expression based router extracts net.protocol field from scheme of request,
// GRPC over HTTP/2 requests could not be matched if protocol is set to grpc/grpcs since protocol could only be http or https.
// So we do not AND a protocol matcher if no protocol is specified in annotations.
protocols := annotations.ExtractProtocolNames(metaAnnotations)
if len(protocols) > 0 {
protocolMatcher := protocolMatcherFromProtocols(protocols)
Expand All @@ -82,7 +92,6 @@ func generateMathcherFromGRPCMatch(match gatewayv1alpha2.GRPCRouteMatch, hostnam
}

// methodMatcherFromGRPCMethodMatch translates ONE GRPC method match in GRPCRoute to ATC matcher.
// REVIEW(naming): this function actually generates matcher to match HTTP path but not HTTP method. rename to pathMatcher...?
func methodMatcherFromGRPCMethodMatch(methodMatch *gatewayv1alpha2.GRPCMethodMatch) atc.Matcher {
matchType := gatewayv1alpha2.GRPCMethodMatchExact
if methodMatch.Type != nil {
Expand Down
36 changes: 2 additions & 34 deletions internal/dataplane/parser/translators/grpcroute_atc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,13 @@ import (
"github.com/kong/go-kong/kong"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
)

var grpcRouteGVK = schema.GroupVersionKind{
Group: "gateway.networking.k8s.io",
Version: "v1alpha2",
Kind: "GRPCRoute",
}

func TestGenerateKongExpressionRoutesFromGRPCRouteRule(t *testing.T) {
makeGRPCRoute := func(
name string, namespace string, annotations map[string]string,
hostnames []string,
rules []gatewayv1alpha2.GRPCRouteRule,
) *gatewayv1alpha2.GRPCRoute {
return &gatewayv1alpha2.GRPCRoute{
TypeMeta: metav1.TypeMeta{
Kind: "GRPCRoute",
APIVersion: "gateway.networking.k8s.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: annotations,
},
Spec: gatewayv1alpha2.GRPCRouteSpec{
Hostnames: lo.Map(hostnames, func(h string, _ int) gatewayv1beta1.Hostname {
return gatewayv1beta1.Hostname(h)
}),
Rules: rules,
},
}
}
testCases := []struct {
name string
objectName string
Expand Down Expand Up @@ -226,8 +194,8 @@ func TestGenerateKongExpressionRoutesFromGRPCRouteRule(t *testing.T) {
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
grpcroute := makeGRPCRoute(tc.objectName, "default", tc.annotations, tc.hostnames, []gatewayv1alpha2.GRPCRouteRule{tc.rule})
routes := GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute, 0, tc.rule)
grpcroute := makeTestGRPCRoute(tc.objectName, "default", tc.annotations, tc.hostnames, []gatewayv1alpha2.GRPCRouteRule{tc.rule})
routes := GenerateKongExpressionRoutesFromGRPCRouteRule(grpcroute, 0)
require.Equal(t, tc.expectedRoutes, routes)
})
}
Expand Down
Loading

0 comments on commit 3696668

Please sign in to comment.