Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apis/v1alpha2/grpcroute_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ type GRPCRouteSpec struct {
//
// +optional
// +kubebuilder:validation:MaxItems=16
// +kubebuilder:default={{matches: {{method: {type: "Exact"}}}}}
Rules []GRPCRouteRule `json:"rules,omitempty"`
}

Expand Down Expand Up @@ -313,6 +312,10 @@ type GRPCRouteMatch struct {
// request service and/or method.
//
// At least one of Service and Method MUST be a non-empty string.
//
// +kubebuilder:validation:XValidation:message="One or both of 'service' or 'method' must be specified",rule="has(self.type) ? has(self.service) || has(self.method) : true"
// +kubebuilder:validation:XValidation:message="service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)",rule="(!has(self.type) || self.type == 'Exact') && has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"): true"
// +kubebuilder:validation:XValidation:message="method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)",rule="(!has(self.type) || self.type == 'Exact') && has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"): true"
type GRPCMethodMatch struct {
// Type specifies how to match against the service and/or method.
// Support: Core (Exact with service and method specified)
Expand Down
19 changes: 15 additions & 4 deletions config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 120 additions & 4 deletions pkg/test/cel/grpcroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ package main
import (
"context"
"fmt"
"testing"
"strings"
"testing"
"time"

gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -175,7 +176,7 @@ func TestGRPCRouteRule(t *testing.T) {
Matches: []gatewayv1a2.GRPCRouteMatch{
{
Method: &gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Service: ptrTo("helloworld.Greeter"),
},
},
Expand All @@ -201,7 +202,7 @@ func TestGRPCRouteRule(t *testing.T) {
Matches: []gatewayv1a2.GRPCRouteMatch{
{
Method: &gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Method: ptrTo("SayHello"),
},
},
Expand All @@ -228,7 +229,7 @@ func TestGRPCRouteRule(t *testing.T) {
Matches: []gatewayv1a2.GRPCRouteMatch{
{
Method: &gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Type: ptrTo(gatewayv1a2.GRPCMethodMatchType("Exact")),
Service: ptrTo("helloworld.Greeter"),
},
},
Expand Down Expand Up @@ -297,6 +298,121 @@ func TestGRPCRouteRule(t *testing.T) {
}
}

func TestGRPCMethodMatch(t *testing.T) {
tests := []struct {
name string
method gatewayv1a2.GRPCMethodMatch
wantErrors []string
}{
{
name: "valid GRPCRoute with 1 service in GRPCMethodMatch field",
method: gatewayv1a2.GRPCMethodMatch{
Service: ptrTo("foo.Test.Example"),
},
},
{
name: "valid GRPCRoute with 1 method in GRPCMethodMatch field",
method: gatewayv1a2.GRPCMethodMatch{
Method: ptrTo("Login"),
},
},
{
name: "invalid GRPCRoute missing service or method in GRPCMethodMatch field",
method: gatewayv1a2.GRPCMethodMatch{
Service: nil,
Method: nil,
},
wantErrors: []string{"One or both of 'service' or 'method"},
},
{
name: "GRPCRoute uses regex in service and method with undefined match type",
method: gatewayv1a2.GRPCMethodMatch{
Service: ptrTo(".*"),
Method: ptrTo(".*"),
},
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)", "method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
},
{
name: "GRPCRoute uses regex in service and method with match type Exact",
method: gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
Service: ptrTo(".*"),
Method: ptrTo(".*"),
},
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)", "method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
},
{
name: "GRPCRoute uses regex in method with undefined match type",
method: gatewayv1a2.GRPCMethodMatch{
Method: ptrTo(".*"),
},
wantErrors: []string{"method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$)"},
},
{
name: "GRPCRoute uses regex in service with match type Exact",
method: gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
Service: ptrTo(".*"),
},
wantErrors: []string{"service must only contain valid characters (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)"},
},
{
name: "GRPCRoute uses regex in service and method with match type RegularExpression",
method: gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchRegularExpression),
Service: ptrTo(".*"),
Method: ptrTo(".*"),
},
},
{
name: "GRPCRoute uses valid service and method with undefined match type",
method: gatewayv1a2.GRPCMethodMatch{
Service: ptrTo("foo.Test.Example"),
Method: ptrTo("Login"),
},
},
{
name: "GRPCRoute uses valid service and method with match type Exact",
method: gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
Service: ptrTo("foo.Test.Example"),
Method: ptrTo("Login"),
},
},
{
name: "GRPCRoute uses a valid service with a leading dot when match type is Exact",
method: gatewayv1a2.GRPCMethodMatch{
Type: ptrTo(gatewayv1a2.GRPCMethodMatchExact),
Service: ptrTo(".foo.Test.Example"),
},
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
route := gatewayv1a2.GRPCRoute{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("foo-%v", time.Now().UnixNano()),
Namespace: metav1.NamespaceDefault,
},
Spec: gatewayv1a2.GRPCRouteSpec{
Rules: []gatewayv1a2.GRPCRouteRule{
{
Matches: []gatewayv1a2.GRPCRouteMatch{
{
Method: &tc.method,
},
},
},
},
},
}
validateGRPCRoute(t, &route, tc.wantErrors)
})
}
}

func validateGRPCRoute(t *testing.T, route *gatewayv1a2.GRPCRoute, wantErrors []string) {
t.Helper()

Expand Down