Support HTTPRoute method and query param matching
This commit is contained in:
parent
a696f7c654
commit
b4f99ae3ac
5 changed files with 268 additions and 16 deletions
|
@ -215,11 +215,6 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
),
|
),
|
||||||
EnableAllSupportedFeatures: false,
|
EnableAllSupportedFeatures: false,
|
||||||
RunTest: *k8sConformanceRunTest,
|
RunTest: *k8sConformanceRunTest,
|
||||||
// Until the feature are all supported, following tests are skipped.
|
|
||||||
SkipTests: []string{
|
|
||||||
tests.HTTPRouteMethodMatching.ShortName,
|
|
||||||
tests.HTTPRouteQueryParamMatching.ShortName,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cSuite, err := ksuite.NewExperimentalConformanceTestSuite(ksuite.ExperimentalConformanceOptions{
|
cSuite, err := ksuite.NewExperimentalConformanceTestSuite(ksuite.ExperimentalConformanceOptions{
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
kind: GatewayClass
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-class
|
||||||
|
spec:
|
||||||
|
controllerName: traefik.io/gateway-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- method: GET
|
||||||
|
path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /foo
|
||||||
|
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
kind: GatewayClass
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-class
|
||||||
|
spec:
|
||||||
|
controllerName: traefik.io/gateway-controller
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: my-gateway
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: http
|
||||||
|
protocol: HTTP
|
||||||
|
port: 80
|
||||||
|
allowedRoutes:
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: HTTPRoute
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: http-app-1
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
kind: Gateway
|
||||||
|
group: gateway.networking.k8s.io
|
||||||
|
hostnames:
|
||||||
|
- "foo.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- queryParams:
|
||||||
|
- type: Exact
|
||||||
|
name: foo
|
||||||
|
value: bar
|
||||||
|
- type: RegularExpression
|
||||||
|
name: baz
|
||||||
|
value: buz
|
||||||
|
path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /foo
|
||||||
|
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
|
@ -469,11 +469,11 @@ func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||||
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
||||||
// The priority is computed to match the following precedence order:
|
// The priority is computed to match the following precedence order:
|
||||||
//
|
//
|
||||||
// * "Exact" path match. (+100000)
|
// * "Exact" path match (+100000).
|
||||||
// * "Prefix" path match with largest number of characters. (+10000) PathRegex (+1000)
|
// * "Prefix" path match with largest number of characters (+10000 + nb_characters*100).
|
||||||
// * Method match. (not implemented)
|
// * Method match (+1000).
|
||||||
// * Largest number of header matches. (+100 each) or with PathRegex (+10 each)
|
// * Largest number of header matches (+100 each).
|
||||||
// * Largest number of query param matches. (not implemented)
|
// * Largest number of query param matches (+10 each).
|
||||||
//
|
//
|
||||||
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
||||||
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
|
func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (string, int) {
|
||||||
|
@ -489,10 +489,19 @@ func buildMatchRule(hostnames []gatev1.Hostname, match gatev1.HTTPRouteMatch) (s
|
||||||
matchRules = append(matchRules, pathRule)
|
matchRules = append(matchRules, pathRule)
|
||||||
priority += pathPriority
|
priority += pathPriority
|
||||||
|
|
||||||
|
if match.Method != nil {
|
||||||
|
matchRules = append(matchRules, fmt.Sprintf("Method(`%s`)", *match.Method))
|
||||||
|
priority += 1000
|
||||||
|
}
|
||||||
|
|
||||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||||
matchRules = append(matchRules, headerRules...)
|
matchRules = append(matchRules, headerRules...)
|
||||||
priority += headersPriority
|
priority += headersPriority
|
||||||
|
|
||||||
|
queryParamRules, queryParamsPriority := buildQueryParamRules(match.QueryParams)
|
||||||
|
matchRules = append(matchRules, queryParamRules...)
|
||||||
|
priority += queryParamsPriority
|
||||||
|
|
||||||
matchRulesStr := strings.Join(matchRules, " && ")
|
matchRulesStr := strings.Join(matchRules, " && ")
|
||||||
|
|
||||||
hostRule, hostPriority := buildHostRule(hostnames)
|
hostRule, hostPriority := buildHostRule(hostnames)
|
||||||
|
@ -525,7 +534,7 @@ func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
||||||
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", pv), 10000 + len(pathValue)*100
|
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", pv), 10000 + len(pathValue)*100
|
||||||
|
|
||||||
case gatev1.PathMatchRegularExpression:
|
case gatev1.PathMatchRegularExpression:
|
||||||
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 1000 + len(pathValue)*100
|
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 10000 + len(pathValue)*100
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "PathPrefix(`/`)", 1
|
return "PathPrefix(`/`)", 1
|
||||||
|
@ -533,18 +542,38 @@ func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, int) {
|
func buildHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, int) {
|
||||||
var rules []string
|
var (
|
||||||
var priority int
|
rules []string
|
||||||
|
priority int
|
||||||
|
)
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
|
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
|
||||||
switch typ {
|
switch typ {
|
||||||
case gatev1.HeaderMatchExact:
|
case gatev1.HeaderMatchExact:
|
||||||
rules = append(rules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
rules = append(rules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||||
priority += 100
|
|
||||||
case gatev1.HeaderMatchRegularExpression:
|
case gatev1.HeaderMatchRegularExpression:
|
||||||
rules = append(rules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
rules = append(rules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||||
priority += 10
|
|
||||||
}
|
}
|
||||||
|
priority += 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, priority
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildQueryParamRules(queryParams []gatev1.HTTPQueryParamMatch) ([]string, int) {
|
||||||
|
var (
|
||||||
|
rules []string
|
||||||
|
priority int
|
||||||
|
)
|
||||||
|
for _, qp := range queryParams {
|
||||||
|
typ := ptr.Deref(qp.Type, gatev1.QueryParamMatchExact)
|
||||||
|
switch typ {
|
||||||
|
case gatev1.QueryParamMatchExact:
|
||||||
|
rules = append(rules, fmt.Sprintf("Query(`%s`,`%s`)", qp.Name, qp.Value))
|
||||||
|
case gatev1.QueryParamMatchRegularExpression:
|
||||||
|
rules = append(rules, fmt.Sprintf("QueryRegexp(`%s`,`%s`)", qp.Name, qp.Value))
|
||||||
|
}
|
||||||
|
priority += 10
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules, priority
|
return rules, priority
|
||||||
|
|
|
@ -1288,7 +1288,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
"default-http-app-1-my-gateway-web-2-d23f7039bc8036fb918c": {
|
"default-http-app-1-my-gateway-web-2-d23f7039bc8036fb918c": {
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
Rule: "Host(`foo.com`) && PathRegexp(`^/buzz/[0-9]+$`)",
|
Rule: "Host(`foo.com`) && PathRegexp(`^/buzz/[0-9]+$`)",
|
||||||
Priority: 2408,
|
Priority: 11408,
|
||||||
RuleSyntax: "v3",
|
RuleSyntax: "v3",
|
||||||
Service: "default-http-app-1-my-gateway-web-2-wrr",
|
Service: "default-http-app-1-my-gateway-web-2-wrr",
|
||||||
},
|
},
|
||||||
|
@ -1354,6 +1354,128 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute, with method matching",
|
||||||
|
paths: []string{"services.yml", "httproute/with_method_matching.yml"},
|
||||||
|
entryPoints: map[string]Entrypoint{"web": {
|
||||||
|
Address: ":80",
|
||||||
|
}},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-0-74ad70a7cf090becdd3c": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Rule: "Host(`foo.com`) && (Path(`/foo`) || PathPrefix(`/foo/`)) && Method(`GET`)",
|
||||||
|
Priority: 11408,
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "default-whoami-80",
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute, with query param matching",
|
||||||
|
paths: []string{"services.yml", "httproute/with_query_param_matching.yml"},
|
||||||
|
entryPoints: map[string]Entrypoint{"web": {
|
||||||
|
Address: ":80",
|
||||||
|
}},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-http-app-1-my-gateway-web-0-bb7b03c9610e982fd627": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Rule: "Host(`foo.com`) && (Path(`/foo`) || PathPrefix(`/foo/`)) && Query(`foo`,`bar`) && QueryRegexp(`baz`,`buz`)",
|
||||||
|
Priority: 10428,
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-wrr": {
|
||||||
|
Weighted: &dynamic.WeightedRoundRobin{
|
||||||
|
Services: []dynamic.WRRService{
|
||||||
|
{
|
||||||
|
Name: "default-whoami-80",
|
||||||
|
Weight: ptr.To(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"default-whoami-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: ptr.To(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPRoute with Same namespace selector",
|
desc: "HTTPRoute with Same namespace selector",
|
||||||
paths: []string{"services.yml", "httproute/with_namespace_same.yml"},
|
paths: []string{"services.yml", "httproute/with_namespace_same.yml"},
|
||||||
|
|
Loading…
Reference in a new issue