Skip to content

Commit bd49fb3

Browse files
authored
Merge pull request #1490 from gyohuangxin/validate_grpc
Webhook: validate at least one of GRPCRoute.Rules.Matches.{Method,Service} is supplied
2 parents b9b9d29 + 77e65a2 commit bd49fb3

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/util/validation/field"
21+
22+
gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
23+
)
24+
25+
// ValidateGRPCRoute validates GRPCRoute according to the Gateway API specification.
26+
// For additional details of the GRPCRoute spec, refer to:
27+
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute
28+
func ValidateGRPCRoute(route *gatewayv1a2.GRPCRoute) field.ErrorList {
29+
return validateGRPCRouteSpec(&route.Spec, field.NewPath("spec"))
30+
}
31+
32+
// validateRouteSpec validates that required fields of spec are set according to the
33+
// Gateway API specification.
34+
func validateGRPCRouteSpec(spec *gatewayv1a2.GRPCRouteSpec, path *field.Path) field.ErrorList {
35+
var errs field.ErrorList
36+
errs = append(errs, validateGRPCRouteRules(spec.Rules, path.Child("rules"))...)
37+
return errs
38+
}
39+
40+
// validateGRPCRouteRules validates whether required fields of rules are set according
41+
// to the Gateway API specification.
42+
func validateGRPCRouteRules(rules []gatewayv1a2.GRPCRouteRule, path *field.Path) field.ErrorList {
43+
var errs field.ErrorList
44+
for i, rule := range rules {
45+
errs = append(errs, validateRuleMatches(rule.Matches, path.Index(i).Child("matches"))...)
46+
}
47+
return errs
48+
}
49+
50+
// validateRuleMatches validates that at least one of the fields Service or Method of
51+
// GRPCMethodMatch to be specified
52+
func validateRuleMatches(matches []gatewayv1a2.GRPCRouteMatch, path *field.Path) field.ErrorList {
53+
var errs field.ErrorList
54+
for i, m := range matches {
55+
if m.Method != nil && m.Method.Service == nil && m.Method.Method == nil {
56+
errs = append(errs, field.Required(path.Index(i).Child("methods"), "should have at least one of fields Service or Method"))
57+
return errs
58+
}
59+
}
60+
return errs
61+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"testing"
21+
22+
"k8s.io/apimachinery/pkg/util/validation/field"
23+
24+
gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
25+
)
26+
27+
func TestValidateGRPCRoute(t *testing.T) {
28+
t.Parallel()
29+
30+
service := "foo"
31+
method := "login"
32+
33+
tests := []struct {
34+
name string
35+
rules []gatewayv1a2.GRPCRouteRule
36+
errs field.ErrorList
37+
}{
38+
{
39+
name: "valid GRPCRoute with 1 service in GRPCMethodMatch field",
40+
rules: []gatewayv1a2.GRPCRouteRule{
41+
{
42+
Matches: []gatewayv1a2.GRPCRouteMatch{
43+
{
44+
Method: &gatewayv1a2.GRPCMethodMatch{
45+
Service: &service,
46+
},
47+
},
48+
},
49+
},
50+
},
51+
},
52+
{
53+
name: "valid GRPCRoute with 1 method in GRPCMethodMatch field",
54+
rules: []gatewayv1a2.GRPCRouteRule{
55+
{
56+
Matches: []gatewayv1a2.GRPCRouteMatch{
57+
{
58+
Method: &gatewayv1a2.GRPCMethodMatch{
59+
Method: &method,
60+
},
61+
},
62+
},
63+
},
64+
},
65+
},
66+
{
67+
name: "invalid GRPCRoute missing service or method in GRPCMethodMatch field",
68+
rules: []gatewayv1a2.GRPCRouteRule{
69+
{
70+
Matches: []gatewayv1a2.GRPCRouteMatch{
71+
{
72+
Method: &gatewayv1a2.GRPCMethodMatch{
73+
Service: nil,
74+
Method: nil,
75+
},
76+
},
77+
},
78+
},
79+
},
80+
errs: field.ErrorList{
81+
{
82+
Type: field.ErrorTypeRequired,
83+
Field: "spec.rules[0].matches[0].methods",
84+
Detail: "should have at least one of fields Service or Method",
85+
},
86+
},
87+
},
88+
}
89+
90+
for _, tc := range tests {
91+
tc := tc
92+
t.Run(tc.name, func(t *testing.T) {
93+
t.Parallel()
94+
95+
route := gatewayv1a2.GRPCRoute{Spec: gatewayv1a2.GRPCRouteSpec{Rules: tc.rules}}
96+
errs := ValidateGRPCRoute(&route)
97+
if len(errs) != len(tc.errs) {
98+
t.Errorf("got %d errors, want %d errors: %s", len(errs), len(tc.errs), errs)
99+
t.FailNow()
100+
}
101+
for i := 0; i < len(errs); i++ {
102+
realErr := errs[i].Error()
103+
expectedErr := tc.errs[i].Error()
104+
if realErr != expectedErr {
105+
t.Errorf("expect error message: %s, but got: %s", expectedErr, realErr)
106+
t.FailNow()
107+
}
108+
}
109+
})
110+
}
111+
}

pkg/admission/server.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ var (
6161
Version: v1alpha2.SchemeGroupVersion.Version,
6262
Resource: "httproutes",
6363
}
64+
v1a2GRPCRouteGVR = meta.GroupVersionResource{
65+
Group: v1alpha2.SchemeGroupVersion.Group,
66+
Version: v1alpha2.SchemeGroupVersion.Version,
67+
Resource: "grpcroutes",
68+
}
6469
v1a2GatewayGVR = meta.GroupVersionResource{
6570
Group: v1alpha2.SchemeGroupVersion.Group,
6671
Version: v1alpha2.SchemeGroupVersion.Version,
@@ -207,6 +212,14 @@ func handleValidation(request admission.AdmissionRequest) (*admission.AdmissionR
207212
}
208213

209214
fieldErr = v1a2Validation.ValidateHTTPRoute(&hRoute)
215+
case v1a2GRPCRouteGVR:
216+
var gRoute v1alpha2.GRPCRoute
217+
_, _, err := deserializer.Decode(request.Object.Raw, nil, &gRoute)
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
fieldErr = v1a2Validation.ValidateGRPCRoute(&gRoute)
210223
case v1b1HTTPRouteGVR:
211224
var hRoute v1beta1.HTTPRoute
212225
_, _, err := deserializer.Decode(request.Object.Raw, nil, &hRoute)

0 commit comments

Comments
 (0)