From 6b789c230efb07f0f81bdb6fd8d67d7e7f726a5a Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Thu, 21 Sep 2023 12:09:33 -0400 Subject: [PATCH 01/15] Stops route sorting on `HTTPProxy` CRD Contour generates the Envoy routes for each virtual host in the same order as they are in the `HTTPProxy`. This benefits users from having no suprises between the Envoy actual routing table and what they described in the CRD. * Change the underlying data structure in `dag.go` `VirtualHost` to be a slice such that we can maintain the order. * Change the sorting to be conditional on the caller wanting it. This is used to implement sorting for virtual hosts which are "partially" controlled by an Ingress so we can have ingress conformance. * Change the implementation of `expandPrefixMatches` to maintain the order of the original list. mechanical to switch it to do it. I don't have a strong opinion. I would prefer if we didnt but I am not familiar with any conformance requirements. I believe this would be a lot of work given the tests but I am not sure what the spec requires. Signed-off-by: Sotiris Nanopoulos --- internal/dag/accessors_test.go | 8 +- internal/dag/builder_test.go | 617 ++++++++++-------- internal/dag/dag.go | 32 +- internal/dag/dag_test.go | 12 +- internal/dag/gatewayapi_processor.go | 4 + internal/dag/httpproxy_processor.go | 102 ++- internal/dag/ingress_processor.go | 2 + .../featuretests/v3/headercondition_test.go | 357 +++++----- .../featuretests/v3/jwtverification_test.go | 16 +- .../featuretests/v3/localratelimit_test.go | 44 +- .../v3/queryparametercondition_test.go | 388 +++++------ .../featuretests/v3/replaceprefix_test.go | 87 ++- internal/featuretests/v3/route_test.go | 107 +-- internal/featuretests/v3/websockets_test.go | 98 +-- internal/xdscache/v3/route.go | 16 +- 15 files changed, 1019 insertions(+), 871 deletions(-) diff --git a/internal/dag/accessors_test.go b/internal/dag/accessors_test.go index 67f0f932213..28e52b5e58d 100644 --- a/internal/dag/accessors_test.go +++ b/internal/dag/accessors_test.go @@ -252,8 +252,8 @@ func TestGetServiceClusters(t *testing.T) { "http-1": { VirtualHosts: []*VirtualHost{ { - Routes: map[string]*Route{ - "foo": { + Routes: []*Route{ + { Clusters: []*Cluster{ {Upstream: &Service{ExternalName: "bar.com"}}, {Upstream: &Service{}}, @@ -274,8 +274,8 @@ func TestGetServiceClusters(t *testing.T) { "http-1": { VirtualHosts: []*VirtualHost{ { - Routes: map[string]*Route{ - "foo": { + Routes: []*Route{ + { Clusters: []*Cluster{ {Upstream: &Service{}}, }, diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index d1012fcc530..ee84a688b4e 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -487,7 +487,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -520,7 +520,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -588,7 +588,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), ), }, ), @@ -1018,8 +1018,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("another.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("another.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1128,6 +1128,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts( virtualhost( "test.projectcontour.io", + false, prefixrouteHTTPRoute("/", service(kuardService)), &Route{ PathMatchCondition: prefixSegment("/blog"), @@ -1183,10 +1184,10 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test2.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test3.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test4.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test2.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test3.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test4.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1216,7 +1217,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1248,7 +1249,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("*.projectcontour.io", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -1361,7 +1362,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }, ), @@ -1399,7 +1400,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }, ), @@ -1438,7 +1439,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1491,7 +1492,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService)))), + VirtualHosts: virtualhosts(virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService)))), }), }, "HTTPRoute references a backend in a different namespace, with valid ReferenceGrant (service-specific)": { @@ -1544,7 +1545,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService)))), + VirtualHosts: virtualhosts(virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService)))), }), }, "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong Kind)": { @@ -1597,7 +1598,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1651,7 +1652,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1705,7 +1706,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1760,7 +1761,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1793,6 +1794,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", + false, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -1827,6 +1829,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", + false, regexrouteHTTPRoute("/bl+og", service(kuardService))), ), }, @@ -1877,6 +1880,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", + false, prefixrouteHTTPRoute("/", service(kuardService)), segmentPrefixHTTPRoute("/blog", service(kuardService)), segmentPrefixHTTPRoute("/tech", service(kuardService))), @@ -1921,6 +1925,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Port: 8443, VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", + false, prefixrouteHTTPRoute("/", service(kuardService)), )), EnableWebsockets: true, @@ -2026,7 +2031,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -2474,7 +2479,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -2513,6 +2518,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2566,6 +2572,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/blog"), Clusters: clustersWeight(service(kuardService)), @@ -2611,6 +2618,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2657,6 +2665,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2703,6 +2712,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2753,6 +2763,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2804,6 +2815,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2870,6 +2882,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2939,6 +2952,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2999,6 +3013,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -3052,6 +3067,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -3120,6 +3136,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3195,6 +3212,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3277,6 +3295,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(map[string]string{"Custom-Header-Set": "foo-bar"}, map[string]string{"Custom-Header-Add": "foo-bar"}, []string{"X-Remove"}, "bar.com", nil, nil, nil, service(kuardService)), @@ -3349,6 +3368,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{"Custom-Header-Add": "foo-bar"}, []string{"X-Remove"}, service(kuardService)), @@ -3398,6 +3418,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3459,6 +3480,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(map[string]string{"Custom-Header-Set": "foo-bar"}, map[string]string{}, nil, "bar.com", nil, nil, nil, service(kuardService)), @@ -3515,6 +3537,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{}, nil, service(kuardService)), @@ -3559,6 +3582,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -3611,6 +3635,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -3670,6 +3695,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3719,6 +3745,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3768,6 +3795,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3814,7 +3842,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2)}, 100))), }, ), @@ -3862,7 +3890,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2), service(kuardService3)}, 100))), }, ), @@ -3904,7 +3932,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2)}, 100), withMirror(segmentPrefixHTTPRoute("/another-match", service(kuardService)), []*Service{service(kuardService2)}, 100), )), @@ -3948,6 +3976,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -3996,6 +4025,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -4044,6 +4074,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -4089,6 +4120,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), RequestHeadersPolicy: &HeadersPolicy{HostRewrite: "rewritten.com"}, @@ -4145,6 +4177,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: prefixSegment("/prefix"), RequestHeadersPolicy: &HeadersPolicy{ @@ -4190,7 +4223,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", prefixrouteHTTPRoute("/", + virtualhost("*", false, prefixrouteHTTPRoute("/", &Service{ Weighted: WeightedService{ Weight: 5, @@ -4254,7 +4287,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", prefixrouteHTTPRoute("/", + virtualhost("*", false, prefixrouteHTTPRoute("/", &Service{ Weighted: WeightedService{ Weight: 5, @@ -4314,7 +4347,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", directResponseRouteService("/", http.StatusInternalServerError, &Service{ + virtualhost("*", false, directResponseRouteService("/", http.StatusInternalServerError, &Service{ Weighted: WeightedService{ Weight: 0, ServiceName: kuardService.Name, @@ -5071,7 +5104,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("gateway.projectcontour.io", + virtualhost("gateway.projectcontour.io", false, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -5106,6 +5139,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("http.projectcontour.io", + false, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -5122,7 +5156,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), + virtualhost("test.projectcontour.io", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), ), }, ), @@ -5253,7 +5287,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), + virtualhost("test.projectcontour.io", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), ), }, ), @@ -5289,6 +5323,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -5331,6 +5366,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -5372,6 +5408,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: &PrefixMatchCondition{Prefix: "/"}, HeaderMatchConditions: []HeaderMatchCondition{ @@ -5411,6 +5448,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: &PrefixMatchCondition{Prefix: "/"}, Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5479,6 +5517,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5556,6 +5595,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5617,6 +5657,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5680,6 +5721,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + false, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{}, nil, grpcService(kuardService, "h2c")), @@ -5724,7 +5766,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, withMirror(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")), []*Service{grpcService(kuardService2, "h2c")}, 100))), }, ), @@ -5774,7 +5816,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, withMirror(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")), []*Service{grpcService(kuardService2, "h2c"), grpcService(kuardService3, "h2c")}, 100))), }, ), @@ -5831,7 +5873,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")))), + VirtualHosts: virtualhosts(virtualhost("*", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")))), }), }, } @@ -9680,7 +9722,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9701,7 +9743,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9728,7 +9770,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, ), @@ -9764,7 +9806,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9779,7 +9821,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9794,7 +9836,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9809,7 +9851,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9838,7 +9880,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9862,14 +9904,14 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("kuard.example.com", sec1, prefixroute("/", service(s1))), + securevirtualhost("kuard.example.com", true, sec1, prefixroute("/", service(s1))), ), }, ), @@ -9885,14 +9927,14 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("kuard.example.com", sec3, prefixroute("/", service(s1))), + securevirtualhost("kuard.example.com", true, sec3, prefixroute("/", service(s1))), ), }, ), @@ -9915,7 +9957,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), + virtualhost("*", true, prefixroute("/", service(s1))), ), }, ), @@ -9938,7 +9980,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, ), @@ -9959,8 +10001,8 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("a.example.com", prefixroute("/", service(s1))), - virtualhost("b.example.com", prefixroute("/", service(s1))), + virtualhost("a.example.com", true, prefixroute("/", service(s1))), + virtualhost("b.example.com", true, prefixroute("/", service(s1))), ), }, ), @@ -9975,8 +10017,8 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("a.example.com", prefixroute("/", service(s1))), - virtualhost("b.example.com", prefixroute("/", service(s1))), + virtualhost("a.example.com", true, prefixroute("/", service(s1))), + virtualhost("b.example.com", true, prefixroute("/", service(s1))), ), }, ), @@ -9992,14 +10034,14 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("a.example.com", prefixroute("/", service(s1))), - virtualhost("b.example.com", prefixroute("/", service(s1))), + virtualhost("a.example.com", true, prefixroute("/", service(s1))), + virtualhost("b.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, prefixroute("/", service(s1))), + securevirtualhost("b.example.com", true, sec1, prefixroute("/", service(s1))), ), }, ), @@ -10015,14 +10057,14 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("a.example.com", prefixroute("/", service(s1))), - virtualhost("b.example.com", prefixroute("/", service(s1))), + virtualhost("a.example.com", true, prefixroute("/", service(s1))), + virtualhost("b.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, prefixroute("/", service(s1))), + securevirtualhost("b.example.com", true, sec1, prefixroute("/", service(s1))), ), }, ), @@ -10037,7 +10079,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("b.example.com", + virtualhost("b.example.com", true, prefixroute("/", service(s1)), ), ), @@ -10055,7 +10097,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("b.example.com", + virtualhost("b.example.com", true, prefixroute("/", service(s1)), prefixroute("/kuarder", service(s2)), ), @@ -10073,6 +10115,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("b.example.com", + true, prefixroute("/", service(s1)), prefixroute("/kuarder", service(s2)), ), @@ -10097,7 +10140,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, + securevirtualhost("b.example.com", true, sec1, prefixroute("/", service(s1)), prefixroute("/kuarder", service(s2)), ), @@ -10132,7 +10175,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, prefixroute("/", service(s1))), + securevirtualhost("b.example.com", true, sec1, prefixroute("/", service(s1))), ), }, ), @@ -10146,13 +10189,13 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("b.example.com", routeUpgrade("/", service(s1))), + virtualhost("b.example.com", true, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, routeUpgrade("/", service(s1))), + securevirtualhost("b.example.com", true, sec1, routeUpgrade("/", service(s1))), ), }, ), @@ -10167,13 +10210,13 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("b.example.com", routeUpgrade("/", service(s1))), + virtualhost("b.example.com", true, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("b.example.com", sec1, routeUpgrade("/", service(s1))), + securevirtualhost("b.example.com", true, sec1, routeUpgrade("/", service(s1))), ), }, ), @@ -10188,7 +10231,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("foo.com", routeUpgrade("/", service(s1))), + virtualhost("foo.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -10219,7 +10262,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("foo.com", routeUpgrade("/", service(s1))), + virtualhost("foo.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -10250,13 +10293,13 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("foo.com", routeUpgrade("/", service(s1))), + virtualhost("foo.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("foo.com", sec1, routeUpgrade("/", service(s1))), + securevirtualhost("foo.com", false, sec1, routeUpgrade("/", service(s1))), ), }, ), @@ -10271,7 +10314,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s2))), + virtualhost("example.com", false, prefixroute("/", service(s2))), ), }, ), @@ -10286,6 +10329,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("*.projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -10306,7 +10350,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s1), service(s2))), + virtualhost("example.com", false, prefixroute("/", service(s1), service(s2))), ), }, ), @@ -10322,7 +10366,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("b.example.com", prefixroute("/", service(s1))), + virtualhost("b.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -10334,6 +10378,7 @@ func TestDAGInsert(t *testing.T) { Routes: routes( prefixroute("/", service(s1)), ), + ShouldSortRoutes: true, }, MinTLSVersion: "1.3", MaxTLSVersion: "1.3", @@ -10355,6 +10400,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("*", + true, prefixroute("/", service(s1)), routeWebsocket("/ws1", service(s1)), ), @@ -10372,7 +10418,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), }), @@ -10390,7 +10436,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), }), @@ -10415,7 +10461,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ @@ -10436,7 +10482,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ @@ -10457,7 +10503,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("bar.com", &Route{ + virtualhost("bar.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ResponseTimeout: timeout.DurationSetting(90 * time.Second)}, @@ -10476,7 +10522,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ @@ -10498,7 +10544,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ @@ -10519,7 +10565,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("bar.com", &Route{ + virtualhost("bar.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), TimeoutPolicy: RouteTimeoutPolicy{ResponseTimeout: timeout.DisabledSetting()}, @@ -10544,7 +10590,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("bar.com", &Route{ + virtualhost("bar.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), RetryPolicy: &RetryPolicy{ @@ -10567,7 +10613,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("bar.com", &Route{ + virtualhost("bar.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), RetryPolicy: &RetryPolicy{ @@ -10590,7 +10636,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("bar.com", &Route{ + virtualhost("bar.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), RetryPolicy: &RetryPolicy{ @@ -10613,7 +10659,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s1), RetryPolicy: &RetryPolicy{ @@ -10636,7 +10682,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: regex("/[^/]+/invoices(/.*|/?)"), Clusters: clustermap(s1), }), @@ -10662,6 +10708,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("*", + true, &Route{ PathMatchCondition: exact("/exact"), Clusters: clustermap(s1), @@ -10706,8 +10753,8 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(s1))), - virtualhost("*.example.com", &Route{ + virtualhost("*", true, prefixroute("/", service(s1))), + virtualhost("*.example.com", true, &Route{ PathMatchCondition: &PrefixMatchCondition{Prefix: "/"}, HeaderMatchConditions: []HeaderMatchCondition{ { @@ -10731,7 +10778,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", true, routeUpgrade("/", service(s13a)), prefixroute("/.well-known/acme-challenge/gVJl5NWL2owUqZekjHkt_bo3OHYC2XNDURRRgLI5JTk", service(s13b)), ), @@ -10740,7 +10787,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("example.com", sec13, + securevirtualhost("example.com", true, sec13, routeUpgrade("/", service(s13a)), prefixroute("/.well-known/acme-challenge/gVJl5NWL2owUqZekjHkt_bo3OHYC2XNDURRRgLI5JTk", service(s13b)), ), @@ -10757,7 +10804,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", + virtualhost("*", true, prefixroute("/", &Service{ Protocol: "h2c", Weighted: WeightedService{ @@ -10782,7 +10829,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", + virtualhost("*", true, prefixroute("/", &Service{ Protocol: "h2", Weighted: WeightedService{ @@ -10807,7 +10854,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", + virtualhost("*", true, prefixroute("/", &Service{ Protocol: "tls", Weighted: WeightedService{ @@ -10833,7 +10880,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", + virtualhost("*", true, prefixroute("/", &Service{ Weighted: WeightedService{ Weight: 1, @@ -10862,7 +10909,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, routeCluster("/a", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ @@ -10902,6 +10949,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeCluster("/a", &Cluster{ Upstream: &Service{ @@ -10941,7 +10989,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s1))), + virtualhost("example.com", false, prefixroute("/", service(s1))), ), }, ), @@ -10955,7 +11003,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s1))), + virtualhost("example.com", false, prefixroute("/", service(s1))), ), }, ), @@ -10986,7 +11034,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/finance"), DirectResponse: &DirectResponse{ StatusCode: http.StatusBadGateway, @@ -11033,7 +11081,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/finance"), DirectResponse: &DirectResponse{ StatusCode: http.StatusBadGateway, @@ -11052,7 +11100,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/kuard"), HeaderMatchConditions: []HeaderMatchCondition{ {Name: "x-request-id", MatchType: "present"}, @@ -11076,7 +11124,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ {Name: "e-tag", Value: "abc", MatchType: "contains"}, @@ -11103,7 +11151,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ {Name: "e-tag", Value: "abc", MatchType: "contains"}, @@ -11129,7 +11177,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/kuard"), HeaderMatchConditions: []HeaderMatchCondition{ {Name: "x-request-id", MatchType: "present"}, @@ -11164,6 +11212,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeCluster("/", &Cluster{ Upstream: service(s1), HTTPHealthCheckPolicy: &HTTPHealthCheckPolicy{ @@ -11184,7 +11233,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, withMirror(prefixroute("/", service(s1)), []*Service{service(s2)}, 100), ), ), @@ -11206,7 +11255,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, prefixroute("/", service(s1)), routeWebsocket("/websocket", service(s1)), ), @@ -11223,7 +11272,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, prefixroute("/", service(s1)), routeWebsocket("/websocket", service(s1)), ), @@ -11241,7 +11290,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, routeProtocol("/", protocol, service(s1))), ), }, @@ -11257,13 +11306,13 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("foo.com", routeUpgrade("/", service(s1))), + virtualhost("foo.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("foo.com", sec1, routeUpgrade("/", service(s1))), + securevirtualhost("foo.com", false, sec1, routeUpgrade("/", service(s1))), ), }, ), @@ -11279,6 +11328,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeCluster("/", &Cluster{ Upstream: &Service{ @@ -11313,6 +11363,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeCluster("/", &Cluster{ Upstream: &Service{ @@ -11351,7 +11402,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, prefixroute("/", service(s1)), ), ), @@ -11387,6 +11438,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeCluster("/", &Cluster{ Upstream: &Service{ @@ -11421,7 +11473,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11484,7 +11536,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11517,7 +11569,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11551,7 +11603,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11585,7 +11637,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11620,7 +11672,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11660,7 +11712,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s1))), + virtualhost("example.com", false, routeUpgrade("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, @@ -11801,28 +11853,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/blog", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s4.Name, + ServiceNamespace: s4.Namespace, + ServicePort: s4.Spec.Ports[0], + HealthPort: s4.Spec.Ports[0], }, }, }, ), - routeCluster("/blog", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s4.Name, - ServiceNamespace: s4.Namespace, - ServicePort: s4.Spec.Ports[0], - HealthPort: s4.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -11842,19 +11895,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", - &Cluster{ - Upstream: &Service{ - Weighted: WeightedService{ - Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], - }, - }, - }, - ), + false, &Route{ PathMatchCondition: prefixString("/blog/infotech"), Clusters: []*Cluster{{ @@ -11870,6 +11911,19 @@ func TestDAGInsert(t *testing.T) { }, }, }, + routeCluster("/", + &Cluster{ + Upstream: &Service{ + Weighted: WeightedService{ + Weight: 1, + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], + }, + }, + }, + ), ), ), }, @@ -11885,19 +11939,21 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", - &Cluster{ + false, + &Route{ + PathMatchCondition: prefixString("/blog/it/foo"), + Clusters: []*Cluster{{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s11.Name, + ServiceNamespace: s11.Namespace, + ServicePort: s11.Spec.Ports[0], + HealthPort: s11.Spec.Ports[0], }, }, - }, - ), + }}, + }, &Route{ PathMatchCondition: prefixString("/blog/infotech"), Clusters: []*Cluster{{ @@ -11925,20 +11981,19 @@ func TestDAGInsert(t *testing.T) { }, }, ), - &Route{ - PathMatchCondition: prefixString("/blog/it/foo"), - Clusters: []*Cluster{{ + routeCluster("/", + &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s11.Name, - ServiceNamespace: s11.Namespace, - ServicePort: s11.Spec.Ports[0], - HealthPort: s11.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, - }}, - }, + }, + ), ), ), }, @@ -11954,28 +12009,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/kuarder", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s2.Name, + ServiceNamespace: s2.Namespace, + ServicePort: s2.Spec.Ports[0], + HealthPort: s2.Spec.Ports[0], }, }, }, ), - routeCluster("/kuarder", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s2.Name, - ServiceNamespace: s2.Namespace, - ServicePort: s2.Spec.Ports[0], - HealthPort: s2.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -11995,28 +12051,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/kuarder", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s2.Name, + ServiceNamespace: s2.Namespace, + ServicePort: s2.Spec.Ports[0], + HealthPort: s2.Spec.Ports[0], }, }, }, ), - routeCluster("/kuarder", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s2.Name, - ServiceNamespace: s2.Namespace, - ServicePort: s2.Spec.Ports[0], - HealthPort: s2.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -12036,28 +12093,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/kuarder/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s2.Name, + ServiceNamespace: s2.Namespace, + ServicePort: s2.Spec.Ports[0], + HealthPort: s2.Spec.Ports[0], }, }, }, ), - routeCluster("/kuarder/", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s2.Name, - ServiceNamespace: s2.Namespace, - ServicePort: s2.Spec.Ports[0], - HealthPort: s2.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -12077,28 +12135,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/kuarder/withavengeance", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s2.Name, + ServiceNamespace: s2.Namespace, + ServicePort: s2.Spec.Ports[0], + HealthPort: s2.Spec.Ports[0], }, }, }, ), - routeCluster("/kuarder/withavengeance", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s2.Name, - ServiceNamespace: s2.Namespace, - ServicePort: s2.Spec.Ports[0], - HealthPort: s2.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -12122,6 +12181,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example114.com", + false, routeClusterExact("/foo/exact", &Cluster{ Upstream: &Service{ @@ -12164,28 +12224,29 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - routeCluster("/", + false, + routeCluster("/kuarder/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s1.Name, - ServiceNamespace: s1.Namespace, - ServicePort: s1.Spec.Ports[0], - HealthPort: s1.Spec.Ports[0], + ServiceName: s2.Name, + ServiceNamespace: s2.Namespace, + ServicePort: s2.Spec.Ports[0], + HealthPort: s2.Spec.Ports[0], }, }, }, ), - routeCluster("/kuarder/", + routeCluster("/", &Cluster{ Upstream: &Service{ Weighted: WeightedService{ Weight: 1, - ServiceName: s2.Name, - ServiceNamespace: s2.Namespace, - ServicePort: s2.Spec.Ports[0], - HealthPort: s2.Spec.Ports[0], + ServiceName: s1.Name, + ServiceNamespace: s1.Namespace, + ServicePort: s1.Spec.Ports[0], + HealthPort: s1.Spec.Ports[0], }, }, }, @@ -12211,7 +12272,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( // route on root proxy is served, includes is ignored since condition is invalid - virtualhost("example.com", prefixroute("/", service(s1))), + virtualhost("example.com", false, prefixroute("/", service(s1))), ), }, ), @@ -12226,8 +12287,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", - // route on root proxy is served - prefixroute("/", service(s1)), + false, // route for first valid include is served &Route{ PathMatchCondition: prefixString("/blog"), @@ -12236,6 +12296,8 @@ func TestDAGInsert(t *testing.T) { }, Clusters: clusters(service(s12)), }, + // route on root proxy is served + prefixroute("/", service(s1)), ), ), }, @@ -12275,6 +12337,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("kuard.example.com", + false, routeUpgrade("/", service(s1)), ), ), @@ -12307,6 +12370,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("kuard.example.com", + false, routeCluster("/", &Cluster{ Upstream: &Service{ @@ -12353,6 +12417,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, routeHeaders("/", map[string]string{ "In-Foo": "bar", }, []string{"In-Baz"}, map[string]string{ @@ -12411,7 +12476,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s9))), + virtualhost("example.com", true, prefixroute("/", service(s9))), ), }, &Listener{ @@ -12473,7 +12538,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( // not upgraded because the route is permitInsecure: true - virtualhost("example.com", prefixroute("/", service(s9))), + virtualhost("example.com", false, prefixroute("/", service(s9))), ), }, &Listener{ @@ -12533,7 +12598,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( // not upgraded because the route is permitInsecure: true - virtualhost("example.com", prefixroute("/", service(s9))), + virtualhost("example.com", false, prefixroute("/", service(s9))), ), }, &Listener{ @@ -12583,7 +12648,7 @@ func TestDAGInsert(t *testing.T) { &Listener{ Name: HTTP_LISTENER_NAME, Port: 8080, - VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + VirtualHosts: virtualhosts(virtualhost("projectcontour.io", false, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -12627,6 +12692,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -12679,6 +12745,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ @@ -12735,6 +12802,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), DirectResponse: &DirectResponse{ @@ -12774,6 +12842,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), DirectResponse: &DirectResponse{ @@ -12832,6 +12901,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts(virtualhost("projectcontour.io", + false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ @@ -12879,7 +12949,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", prefixroute("/", service(s2a))), + virtualhost("example.com", true, prefixroute("/", service(s2a))), ), }, ), @@ -12895,7 +12965,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, ), @@ -12923,14 +12993,14 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("kuard.example.com", prefixroute("/", service(s1))), + virtualhost("kuard.example.com", true, prefixroute("/", service(s1))), ), }, &Listener{ Name: HTTPS_LISTENER_NAME, Port: 8443, SecureVirtualHosts: securevirtualhosts( - securevirtualhost("kuard.example.com", sec4, prefixroute("/", service(s1))), + securevirtualhost("kuard.example.com", true, sec4, prefixroute("/", service(s1))), ), }, ), @@ -12946,7 +13016,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: &Service{ @@ -12984,7 +13054,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: &Service{ @@ -13055,7 +13125,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: service(s9), @@ -13080,7 +13150,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: &Service{ @@ -13113,7 +13183,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: &Service{ @@ -13146,7 +13216,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: &Service{ @@ -13193,7 +13263,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{{ Upstream: service(s9), @@ -13221,7 +13291,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s9), RequestHeadersPolicy: &HeadersPolicy{ @@ -13245,7 +13315,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustermap(s9), RequestHeadersPolicy: &HeadersPolicy{ @@ -13268,7 +13338,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/foo"), Clusters: clustermap(s9), CookieRewritePolicies: []CookieRewritePolicy{ @@ -13300,7 +13370,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/foo"), Clusters: []*Cluster{ { @@ -13363,7 +13433,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{ {Upstream: service(s9), LoadBalancerPolicy: "Cookie"}, @@ -13392,7 +13462,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{ {Upstream: service(s9), LoadBalancerPolicy: "RequestHash"}, @@ -13423,7 +13493,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{ {Upstream: service(s9), LoadBalancerPolicy: "RequestHash"}, @@ -13456,7 +13526,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{ {Upstream: service(s9), LoadBalancerPolicy: "RequestHash"}, @@ -13489,7 +13559,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: []*Cluster{ {Upstream: service(s9), LoadBalancerPolicy: "RoundRobin"}, @@ -13533,7 +13603,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -13632,7 +13702,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -13699,7 +13769,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -13839,8 +13909,8 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), - virtualhost("projectcontour.io", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), + virtualhost("projectcontour.io", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -13904,7 +13974,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -13960,7 +14030,7 @@ func TestDAGInsert(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", routeUpgrade("/", service(s9))), + virtualhost("example.com", false, routeUpgrade("/", service(s9))), ), }, &Listener{ @@ -14130,6 +14200,7 @@ func TestDAGInsert(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.valid.regexpath.com", + false, routeClusterRegex("/foo/regex/.*", &Cluster{ Upstream: service(s1), @@ -14268,7 +14339,7 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", false, prefixroute("/", service(kuardService))), ), }, ), @@ -14404,7 +14475,7 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", false, prefixroute("/", service(kuardService))), ), }, ), @@ -14481,13 +14552,13 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", routeUpgrade("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", false, routeUpgrade("/", service(kuardService))), ), }, &Listener{ Name: "https-443", SecureVirtualHosts: []*SecureVirtualHost{ - securevirtualhost("kuard.projectcontour.io", sec1, routeUpgrade("/", service(kuardService))), + securevirtualhost("kuard.projectcontour.io", false, sec1, routeUpgrade("/", service(kuardService))), }, }, ), @@ -14561,13 +14632,13 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", routeUpgrade("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", false, routeUpgrade("/", service(kuardService))), ), }, &Listener{ Name: "https-443", SecureVirtualHosts: []*SecureVirtualHost{ - securevirtualhost("kuard.projectcontour.io", sec1, routeUpgrade("/", service(kuardService))), + securevirtualhost("kuard.projectcontour.io", false, sec1, routeUpgrade("/", service(kuardService))), }, }, ), @@ -14681,7 +14752,7 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", true, prefixroute("/", service(kuardService))), ), }, ), @@ -14809,7 +14880,7 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", true, prefixroute("/", service(kuardService))), ), }, ), @@ -14885,13 +14956,13 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", true, prefixroute("/", service(kuardService))), ), }, &Listener{ Name: "https-443", SecureVirtualHosts: []*SecureVirtualHost{ - securevirtualhost("kuard.projectcontour.io", sec1, prefixroute("/", service(kuardService))), + securevirtualhost("kuard.projectcontour.io", true, sec1, prefixroute("/", service(kuardService))), }, }, ), @@ -14964,13 +15035,13 @@ func TestGatewayWithHTTPProxyAndIngress(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("kuard.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("kuard.projectcontour.io", true, prefixroute("/", service(kuardService))), ), }, &Listener{ Name: "https-443", SecureVirtualHosts: []*SecureVirtualHost{ - securevirtualhost("kuard.projectcontour.io", sec1, prefixroute("/", service(kuardService))), + securevirtualhost("kuard.projectcontour.io", true, sec1, prefixroute("/", service(kuardService))), }, }, ), @@ -15358,7 +15429,7 @@ func TestHTTPProxyConficts(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", directResponseRoute("/", http.StatusServiceUnavailable)), + virtualhost("example.com", false, directResponseRoute("/", http.StatusServiceUnavailable)), ), }, ), @@ -15419,7 +15490,7 @@ func TestHTTPProxyConficts(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", directResponseRoute("/", http.StatusBadGateway)), + virtualhost("example.com", false, directResponseRoute("/", http.StatusBadGateway)), ), }, ), @@ -15462,6 +15533,7 @@ func TestHTTPProxyConficts(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, directResponseRoute("/", http.StatusServiceUnavailable), prefixroute("/valid", service(existingService1))), ), @@ -15510,7 +15582,7 @@ func TestHTTPProxyConficts(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", + virtualhost("example.com", false, routeCluster("/", &Cluster{ Upstream: service(existingService1), @@ -15578,6 +15650,7 @@ func TestHTTPProxyConficts(t *testing.T) { Port: 8080, VirtualHosts: virtualhosts( virtualhost("example.com", + false, directResponseRoute("/", http.StatusBadGateway), prefixroute("/valid", service(existingService1)), ), @@ -15628,7 +15701,7 @@ func TestHTTPProxyConficts(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", directResponseRoute("/missing", http.StatusServiceUnavailable)), + virtualhost("example.com", false, directResponseRoute("/missing", http.StatusServiceUnavailable)), ), }, ), @@ -15697,6 +15770,7 @@ func TestHTTPProxyConficts(t *testing.T) { Port: 8080, VirtualHosts: []*VirtualHost{ virtualhost("example.com", + false, directResponseRoute("/missing", http.StatusServiceUnavailable), prefixroute("/existing", service(existingService1))), }, @@ -15792,7 +15866,7 @@ func TestDefaultHeadersPolicies(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("*", &Route{ + virtualhost("*", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeadersUnweighted(map[string]string{"Custom-Header-Set": "foo-bar"}, nil, []string{"K-Nada"}, "", service(s1)), }, @@ -15830,7 +15904,7 @@ func TestDefaultHeadersPolicies(t *testing.T) { Name: HTTP_LISTENER_NAME, Port: 8080, VirtualHosts: virtualhosts( - virtualhost("example.com", &Route{ + virtualhost("example.com", false, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeadersUnweighted(map[string]string{"Custom-Header-Set": "foo-bar"}, nil, []string{"K-Nada"}, "", service(s1), service(s2)), }, @@ -15900,15 +15974,8 @@ func TestDefaultHeadersPolicies(t *testing.T) { } } -func routes(routes ...*Route) map[string]*Route { - if len(routes) == 0 { - return nil - } - m := make(map[string]*Route) - for _, r := range routes { - m[conditionsToString(r)] = r - } - return m +func routes(routes ...*Route) []*Route { + return routes } func directResponseRoute(prefix string, statusCode uint32) *Route { @@ -16187,18 +16254,20 @@ func securevirtualhosts(vx ...*SecureVirtualHost) []*SecureVirtualHost { return vx } -func virtualhost(name string, first *Route, rest ...*Route) *VirtualHost { +func virtualhost(name string, shouldSort bool, first *Route, rest ...*Route) *VirtualHost { return &VirtualHost{ - Name: name, - Routes: routes(append([]*Route{first}, rest...)...), + Name: name, + Routes: routes(append([]*Route{first}, rest...)...), + ShouldSortRoutes: shouldSort, } } -func securevirtualhost(name string, sec *v1.Secret, first *Route, rest ...*Route) *SecureVirtualHost { +func securevirtualhost(name string, shouldSort bool, sec *v1.Secret, first *Route, rest ...*Route) *SecureVirtualHost { return &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: name, - Routes: routes(append([]*Route{first}, rest...)...), + Name: name, + Routes: routes(append([]*Route{first}, rest...)...), + ShouldSortRoutes: shouldSort, }, MinTLSVersion: "1.2", MaxTLSVersion: "1.3", diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 09918e17b21..04e22612eb8 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -714,14 +714,40 @@ type VirtualHost struct { // by IPFilterAllow. IPFilterRules []IPFilterRule - Routes map[string]*Route + // Order here matters since we want to be able to generate the Envoy routes + // in the same order as the ones specified in the HTTPProxy objects. + // As a result we are not allowed to use a map to store the routes internally + // and we have to use a slice. + Routes []*Route + + // ShouldSortRoutes is set to true if the routes in this virtual host need + // to be logically sorted before being used. + // The `Routes` in this objected are ordered in the order they are inserted + // in the object. + ShouldSortRoutes bool } func (v *VirtualHost) AddRoute(route *Route) { if v.Routes == nil { - v.Routes = make(map[string]*Route) + v.Routes = make([]*Route, 0) + } + + // There are cases where the route might already exist and in that case + // we need to replace it with the new route. We use the `conditionsToString` + // as a way to compare different routes and then delete the original route + // and re-add it at the end of the routing table. + name := conditionsToString(route) + idxToDel := -1 + for i, r := range v.Routes { + if strings.EqualFold(name, conditionsToString(r)) { + idxToDel = i + } + } + + if idxToDel != -1 { + v.Routes = append(v.Routes[:idxToDel], v.Routes[idxToDel+1:]...) } - v.Routes[conditionsToString(route)] = route + v.Routes = append(v.Routes, route) } func conditionsToString(r *Route) string { diff --git a/internal/dag/dag_test.go b/internal/dag/dag_test.go index a7ee4def4b9..16519f1d7d1 100644 --- a/internal/dag/dag_test.go +++ b/internal/dag/dag_test.go @@ -27,8 +27,8 @@ func TestVirtualHostValid(t *testing.T) { assert.False(t, vh.Valid()) vh = VirtualHost{ - Routes: map[string]*Route{ - "/": {}, + Routes: []*Route{ + {}, }, } assert.True(t, vh.Valid()) @@ -46,8 +46,8 @@ func TestSecureVirtualHostValid(t *testing.T) { vh = SecureVirtualHost{ VirtualHost: VirtualHost{ - Routes: map[string]*Route{ - "/": {}, + Routes: []*Route{ + {}, }, }, } @@ -56,8 +56,8 @@ func TestSecureVirtualHostValid(t *testing.T) { vh = SecureVirtualHost{ Secret: new(Secret), VirtualHost: VirtualHost{ - Routes: map[string]*Route{ - "/": {}, + Routes: []*Route{ + {}, }, }, } diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index cdf5469adb8..0d321190c1a 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -1357,9 +1357,11 @@ func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1be svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret svhost.AddRoute(route) + //TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) vhost.AddRoute(route) + //TODO(davinci26): should we use the sorter here? } programmed = true @@ -1501,9 +1503,11 @@ func (p *GatewayAPIProcessor) computeGRPCRouteForListener(route *gatewayapi_v1al svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret svhost.AddRoute(route) + //TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) vhost.AddRoute(route) + //TODO(davinci26): should we use the sorter here? } programmed = true diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index e2b2ddbfa1c..5daabc648f2 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -1486,84 +1486,78 @@ func (p *HTTPProxyProcessor) GlobalAuthorizationContext() map[string]string { // | `/foo` | `/bar/` | `/foosball` | X `/bar/sball` | // | `/foo/` | `/bar/` | `/foo/type` | `/bar/type` | func expandPrefixMatches(routes []*Route) []*Route { - prefixedRoutes := map[string][]*Route{} + return expandPrefixMatchesOrdered(routes) +} +func expandPrefixMatchesOrdered(routes []*Route) []*Route { expandedRoutes := []*Route{} - // First, we group the Routes by their slash-consistent prefix match condition. + prefixCount := make(map[string]int, 0) + + // First, we find the non expandable prefix routes. Those are two: + // - if both versions of the route exist e.g. /api/ and /api + // - root prefix `/` for _, r := range routes { - // If there is no path prefix, we won't do any expansion, so skip it. if !r.HasPathPrefix() { - expandedRoutes = append(expandedRoutes, r) - } - - // Skip for exact path match conditions - if r.HasPathExact() || r.HasPathRegex() { continue } routingPrefix := r.PathMatchCondition.(*PrefixMatchCondition).Prefix + if routingPrefix == "/" { + prefixCount[routingPrefix] = 2 + continue + } + if routingPrefix != "/" { routingPrefix = strings.TrimRight(routingPrefix, "/") } - prefixedRoutes[routingPrefix] = append(prefixedRoutes[routingPrefix], r) + prefixCount[routingPrefix] += 1 } - for prefix, routes := range prefixedRoutes { - // Propagate the Routes into the expanded set. Since - // we have a slice of pointers, we can propagate here - // prior to any Route modifications. - expandedRoutes = append(expandedRoutes, routes...) - - switch len(routes) { - case 1: - // Don't modify if we are not doing a replacement. - if routes[0].PathRewritePolicy == nil { - continue - } + for _, r := range routes { + expandedRoutes = append(expandedRoutes, r) + // If there is no path prefix, we won't do any expansion, so skip it. + if !r.HasPathPrefix() { + continue + } - routingPrefix := routes[0].PathMatchCondition.(*PrefixMatchCondition).Prefix + if r.PathRewritePolicy == nil { + continue + } - // There's no alternate forms for '/' :) - if routingPrefix == "/" { - continue - } + routingPrefix := r.PathMatchCondition.(*PrefixMatchCondition).Prefix + // We want to trim `/` at the end to create two routes + // one with `/` at the end and one with not. + if routingPrefix != "/" { + routingPrefix = strings.TrimRight(routingPrefix, "/") + } - // Shallow copy the Route. TODO(jpeach) deep copying would be more robust. - newRoute := *routes[0] + if c, ok := prefixCount[routingPrefix]; ok && c == 2 { + continue + } - // Now, make the original route handle '/foo' and the new route handle '/foo'. - routes[0].PathRewritePolicy.PrefixRewrite = strings.TrimRight(routes[0].PathRewritePolicy.PrefixRewrite, "/") - routes[0].PathMatchCondition = &PrefixMatchCondition{Prefix: prefix} + r.PathRewritePolicy.PrefixRewrite = strings.TrimRight(r.PathRewritePolicy.PrefixRewrite, "/") + r.PathMatchCondition = &PrefixMatchCondition{Prefix: routingPrefix} - // Replace the entire PathRewritePolicy since we didn't deep-copy the route. - newRoute.PathRewritePolicy = &PathRewritePolicy{ - PrefixRewrite: routes[0].PathRewritePolicy.PrefixRewrite + "/", - } - newRoute.PathMatchCondition = &PrefixMatchCondition{Prefix: prefix + "/"} - - // Since we trimmed trailing '/', it's possible that - // we made the replacement empty. There's no such - // thing as an empty rewrite; it's the same as - // rewriting to '/'. - if len(routes[0].PathRewritePolicy.PrefixRewrite) == 0 { - routes[0].PathRewritePolicy.PrefixRewrite = "/" - } + newRoute := *r + // Replace the entire PathRewritePolicy since we didn't deep-copy the route. + newRoute.PathRewritePolicy = &PathRewritePolicy{ + PrefixRewrite: r.PathRewritePolicy.PrefixRewrite + "/", + } + newRoute.PathMatchCondition = &PrefixMatchCondition{Prefix: routingPrefix + "/"} - expandedRoutes = append(expandedRoutes, &newRoute) - case 2: - // This group routes on both '/foo' and - // '/foo/' so we can't add any implicit prefix - // matches. This is why we didn't filter out - // routes that don't have replacements earlier. - continue - default: - // This can't happen unless there are routes - // with duplicate prefix paths. + // Since we trimmed trailing '/', it's possible that + // we made the replacement empty. There's no such + // thing as an empty rewrite; it's the same as + // rewriting to '/'. + if len(r.PathRewritePolicy.PrefixRewrite) == 0 { + r.PathRewritePolicy.PrefixRewrite = "/" } + expandedRoutes = append(expandedRoutes, &newRoute) + } return expandedRoutes diff --git a/internal/dag/ingress_processor.go b/internal/dag/ingress_processor.go index 918f6fcab29..85f8effa9cb 100644 --- a/internal/dag/ingress_processor.go +++ b/internal/dag/ingress_processor.go @@ -237,6 +237,7 @@ func (p *IngressProcessor) computeIngressRule(ing *networking_v1.Ingress, rule n vhost := p.dag.EnsureVirtualHost(listener.Name, host) vhost.AddRoute(r) + vhost.ShouldSortRoutes = true } listener, err := p.dag.GetSingleListener("https") @@ -253,6 +254,7 @@ func (p *IngressProcessor) computeIngressRule(ing *networking_v1.Ingress, rule n // it is correctly configured for TLS. if svh := p.dag.GetSecureVirtualHost(listener.Name, host); svh != nil && host != "*" { svh.AddRoute(r) + svh.ShouldSortRoutes = true } } } diff --git a/internal/featuretests/v3/headercondition_test.go b/internal/featuretests/v3/headercondition_test.go index 13ec7289152..ac007b5d17e 100644 --- a/internal/featuretests/v3/headercondition_test.go +++ b/internal/featuretests/v3/headercondition_test.go @@ -50,12 +50,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { }, Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, + Routes: []contour_api_v1.Route{ { Conditions: matchconditions( prefixMatchCondition("/"), @@ -96,6 +91,12 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { Port: 80, }}, }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, }, }, } @@ -105,6 +106,24 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", + &envoy_route_v3.Route{ + Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ + Name: "x-header", + Value: "abc", + MatchType: "contains", + Invert: false, + }), + Action: routeCluster("default/svc2/80/da39a3ee5e"), + }, + &envoy_route_v3.Route{ + Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ + Name: "x-header", + Value: "abc", + MatchType: "contains", + Invert: false, + }), + Action: routeCluster("default/svc3/80/da39a3ee5e"), + }, &envoy_route_v3.Route{ Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ Name: "x-beta-release", @@ -125,24 +144,6 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { }), Action: routeCluster("default/svc2/80/da39a3ee5e"), }, - &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ - Name: "x-header", - Value: "abc", - MatchType: "contains", - Invert: false, - }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), - }, - &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ - Name: "x-header", - Value: "abc", - MatchType: "contains", - Invert: false, - }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), - }, &envoy_route_v3.Route{ Match: routePrefix("/"), Action: routeCluster("default/svc1/80/da39a3ee5e"), @@ -156,30 +157,33 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { proxy2 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerNotContainsMatchCondition("x-header", "123", false, false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - headerNotContainsMatchCondition("x-header", "abc", false, false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerNotContainsMatchCondition("x-header", "123", false, false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + headerNotContainsMatchCondition("x-header", "abc", false, false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy1, proxy2) @@ -189,22 +193,22 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ Name: "x-header", - Value: "abc", + Value: "123", MatchType: "contains", Invert: true, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ Name: "x-header", - Value: "123", + Value: "abc", MatchType: "contains", Invert: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefix("/"), @@ -219,30 +223,33 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { proxy3 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerExactMatchCondition("x-header", "abc", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - headerExactMatchCondition("x-header", "123", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerExactMatchCondition("x-header", "abc", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + headerExactMatchCondition("x-header", "123", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy2, proxy3) @@ -252,22 +259,22 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ Name: "x-header", - Value: "123", + Value: "abc", MatchType: "exact", Invert: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ Name: "x-header", - Value: "abc", + Value: "123", MatchType: "exact", Invert: false, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefix("/"), @@ -282,30 +289,34 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { proxy4 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerNotExactMatchCondition("x-header", "abc", false, false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - headerNotExactMatchCondition("x-header", "123", false, false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerNotExactMatchCondition("x-header", "abc", false, false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + headerNotExactMatchCondition("x-header", "123", false, false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy3, proxy4) @@ -315,22 +326,22 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ Name: "x-header", - Value: "123", + Value: "abc", MatchType: "exact", Invert: true, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ Name: "x-header", - Value: "abc", + Value: "123", MatchType: "exact", Invert: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefix("/"), @@ -345,30 +356,34 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { proxy5 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerPresentMatchCondition("x-header"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - headerPresentMatchCondition("x-header"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerPresentMatchCondition("x-header"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + headerPresentMatchCondition("x-header"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy4, proxy5) @@ -378,20 +393,20 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ Name: "x-header", MatchType: "present", Invert: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithHeaderConditions("/", dag.HeaderMatchCondition{ + Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{ Name: "x-header", MatchType: "present", Invert: false, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefix("/"), @@ -428,7 +443,8 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { Name: "svc2", Port: 80, }}, - }}, + }, + }, }, ) @@ -487,7 +503,8 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { Name: "svc2", Port: 80, }}, - }}, + }, + }, }, ) @@ -525,30 +542,34 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) { proxy8 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerRegexMatchCondition("x-header", "^123.*"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - headerRegexMatchCondition("x-header", "^789.*"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerRegexMatchCondition("x-header", "^123.*"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/"), + headerRegexMatchCondition("x-header", "^789.*"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy7, proxy8) diff --git a/internal/featuretests/v3/jwtverification_test.go b/internal/featuretests/v3/jwtverification_test.go index c3ba34337b3..ba523ca6ec1 100644 --- a/internal/featuretests/v3/jwtverification_test.go +++ b/internal/featuretests/v3/jwtverification_test.go @@ -263,18 +263,18 @@ func TestJWTVerification(t *testing.T) { }, Routes: []contour_api_v1.Route{ { + Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, - JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Require: "provider-1"}, }, { - Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, + JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Require: "provider-1"}, }, }, }) @@ -371,18 +371,18 @@ func TestJWTVerification(t *testing.T) { }, Routes: []contour_api_v1.Route{ { + Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, + JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Disabled: true}, }, { - Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, - JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Disabled: true}, }, }, }) @@ -1442,18 +1442,18 @@ func TestJWTVerification_Inclusion(t *testing.T) { contour_api_v1.HTTPProxySpec{ Routes: []contour_api_v1.Route{ { + Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, - JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Require: "provider-1"}, }, { - Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, + JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Require: "provider-1"}, }, }, }) @@ -1560,18 +1560,18 @@ func TestJWTVerification_Inclusion(t *testing.T) { contour_api_v1.HTTPProxySpec{ Routes: []contour_api_v1.Route{ { + Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, + JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Disabled: true}, }, { - Conditions: []contour_api_v1.MatchCondition{{Prefix: "/css"}}, Services: []contour_api_v1.Service{{ Name: s1.Name, Port: 80, }}, - JWTVerificationPolicy: &contour_api_v1.JWTVerificationPolicy{Disabled: true}, }, }, }) diff --git a/internal/featuretests/v3/localratelimit_test.go b/internal/featuretests/v3/localratelimit_test.go index 1d870798eef..d722dfd1d84 100644 --- a/internal/featuretests/v3/localratelimit_test.go +++ b/internal/featuretests/v3/localratelimit_test.go @@ -225,18 +225,16 @@ func routeRateLimitsDefined(t *testing.T, rh ResourceEventHandlerWrapper, c *Con rh.OnAdd(p) vhost := envoy_v3.VirtualHost("foo.com", - // note, order of routes is reversed here because route sorting of prefixes - // is reverse alphabetic. &envoy_route_v3.Route{ - Match: routePrefix("/s2"), - Action: routeCluster("default/s2/80/da39a3ee5e"), + Match: routePrefix("/s1"), + Action: routeCluster("default/s1/80/da39a3ee5e"), TypedPerFilterConfig: withFilterConfig("envoy.filters.http.local_ratelimit", &envoy_config_filter_http_local_ratelimit_v3.LocalRateLimit{ StatPrefix: "vhost.foo.com", TokenBucket: &envoy_type_v3.TokenBucket{ - MaxTokens: 6, - TokensPerFill: wrapperspb.UInt32(5), - FillInterval: durationpb.New(time.Second), + MaxTokens: 150, + TokensPerFill: wrapperspb.UInt32(100), + FillInterval: durationpb.New(time.Minute), }, FilterEnabled: &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ @@ -253,15 +251,15 @@ func routeRateLimitsDefined(t *testing.T, rh ResourceEventHandlerWrapper, c *Con }), }, &envoy_route_v3.Route{ - Match: routePrefix("/s1"), - Action: routeCluster("default/s1/80/da39a3ee5e"), + Match: routePrefix("/s2"), + Action: routeCluster("default/s2/80/da39a3ee5e"), TypedPerFilterConfig: withFilterConfig("envoy.filters.http.local_ratelimit", &envoy_config_filter_http_local_ratelimit_v3.LocalRateLimit{ StatPrefix: "vhost.foo.com", TokenBucket: &envoy_type_v3.TokenBucket{ - MaxTokens: 150, - TokensPerFill: wrapperspb.UInt32(100), - FillInterval: durationpb.New(time.Minute), + MaxTokens: 6, + TokensPerFill: wrapperspb.UInt32(5), + FillInterval: durationpb.New(time.Second), }, FilterEnabled: &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ @@ -350,18 +348,16 @@ func vhostAndRouteRateLimitsDefined(t *testing.T, rh ResourceEventHandlerWrapper rh.OnAdd(p) vhost := envoy_v3.VirtualHost("foo.com", - // note, order of routes is reversed here because route sorting of prefixes - // is reverse alphabetic. &envoy_route_v3.Route{ - Match: routePrefix("/s2"), - Action: routeCluster("default/s2/80/da39a3ee5e"), + Match: routePrefix("/s1"), + Action: routeCluster("default/s1/80/da39a3ee5e"), TypedPerFilterConfig: withFilterConfig("envoy.filters.http.local_ratelimit", &envoy_config_filter_http_local_ratelimit_v3.LocalRateLimit{ StatPrefix: "vhost.foo.com", TokenBucket: &envoy_type_v3.TokenBucket{ - MaxTokens: 6, - TokensPerFill: wrapperspb.UInt32(5), - FillInterval: durationpb.New(time.Second), + MaxTokens: 150, + TokensPerFill: wrapperspb.UInt32(100), + FillInterval: durationpb.New(time.Minute), }, FilterEnabled: &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ @@ -378,15 +374,15 @@ func vhostAndRouteRateLimitsDefined(t *testing.T, rh ResourceEventHandlerWrapper }), }, &envoy_route_v3.Route{ - Match: routePrefix("/s1"), - Action: routeCluster("default/s1/80/da39a3ee5e"), + Match: routePrefix("/s2"), + Action: routeCluster("default/s2/80/da39a3ee5e"), TypedPerFilterConfig: withFilterConfig("envoy.filters.http.local_ratelimit", &envoy_config_filter_http_local_ratelimit_v3.LocalRateLimit{ StatPrefix: "vhost.foo.com", TokenBucket: &envoy_type_v3.TokenBucket{ - MaxTokens: 150, - TokensPerFill: wrapperspb.UInt32(100), - FillInterval: durationpb.New(time.Minute), + MaxTokens: 6, + TokensPerFill: wrapperspb.UInt32(5), + FillInterval: durationpb.New(time.Second), }, FilterEnabled: &envoy_core_v3.RuntimeFractionalPercent{ DefaultValue: &envoy_type_v3.FractionalPercent{ diff --git a/internal/featuretests/v3/queryparametercondition_test.go b/internal/featuretests/v3/queryparametercondition_test.go index 409688467c9..229eff3dec7 100644 --- a/internal/featuretests/v3/queryparametercondition_test.go +++ b/internal/featuretests/v3/queryparametercondition_test.go @@ -50,30 +50,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { }, Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterContainsMatchCondition("query-param", "abc", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterContainsMatchCondition("query-param", "abc", true), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterContainsMatchCondition("query-param", "abc", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterContainsMatchCondition("query-param", "abc", true), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }, } rh.OnAdd(proxy1) @@ -83,22 +87,22 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", Value: "abc", MatchType: "contains", - IgnoreCase: true, + IgnoreCase: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", Value: "abc", MatchType: "contains", - IgnoreCase: false, + IgnoreCase: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), @@ -113,30 +117,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { proxy2 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterExactMatchCondition("query-param", "123", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterExactMatchCondition("query-param", "abc", true), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterExactMatchCondition("query-param", "123", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterExactMatchCondition("query-param", "abc", true), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy1, proxy2) @@ -146,22 +154,22 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "abc", + Value: "123", MatchType: "exact", - IgnoreCase: true, + IgnoreCase: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "123", + Value: "abc", MatchType: "exact", - IgnoreCase: false, + IgnoreCase: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), @@ -176,30 +184,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { proxy3 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterPrefixMatchCondition("query-param", "abc", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterPrefixMatchCondition("query-param", "123", true), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterPrefixMatchCondition("query-param", "abc", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterPrefixMatchCondition("query-param", "123", true), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy2, proxy3) @@ -209,22 +221,22 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "123", + Value: "abc", MatchType: "prefix", - IgnoreCase: true, + IgnoreCase: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "abc", + Value: "123", MatchType: "prefix", - IgnoreCase: false, + IgnoreCase: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), @@ -239,30 +251,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { proxy4 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterSuffixMatchCondition("query-param", "abc", false), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterSuffixMatchCondition("query-param", "123", true), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterSuffixMatchCondition("query-param", "abc", false), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterSuffixMatchCondition("query-param", "123", true), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy3, proxy4) @@ -272,22 +288,22 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "123", + Value: "abc", MatchType: "suffix", - IgnoreCase: true, + IgnoreCase: false, }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", - Value: "abc", + Value: "123", MatchType: "suffix", - IgnoreCase: false, + IgnoreCase: true, }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), @@ -302,30 +318,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { proxy5 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterRegexMatchCondition("query-param", "^123.*"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterRegexMatchCondition("query-param", "^123.*"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterRegexMatchCondition("query-param", "^123.*"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterRegexMatchCondition("query-param", "^123.*"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy4, proxy5) @@ -335,20 +355,20 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", Value: "^123.*", MatchType: "regex", }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", Value: "^123.*", MatchType: "regex", }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), @@ -363,30 +383,34 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { proxy6 := fixture.NewProxy("simple").WithSpec( contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "hello.world"}, - Routes: []contour_api_v1.Route{{ - Services: []contour_api_v1.Service{{ - Name: "svc1", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/"), - queryParameterPresentMatchCondition("query-param"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc2", - Port: 80, - }}, - }, { - Conditions: matchconditions( - prefixMatchCondition("/blog"), - queryParameterPresentMatchCondition("query-param"), - ), - Services: []contour_api_v1.Service{{ - Name: "svc3", - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions( + prefixMatchCondition("/"), + queryParameterPresentMatchCondition("query-param"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc2", + Port: 80, + }}, + }, + { + Conditions: matchconditions( + prefixMatchCondition("/blog"), + queryParameterPresentMatchCondition("query-param"), + ), + Services: []contour_api_v1.Service{{ + Name: "svc3", + Port: 80, + }}, + }, + { + Services: []contour_api_v1.Service{{ + Name: "svc1", + Port: 80, + }}, + }, + }, }) rh.OnUpdate(proxy5, proxy6) @@ -396,18 +420,18 @@ func TestConditions_ContainsQueryParameter_HTTProxy(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("hello.world", &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ Name: "query-param", MatchType: "present", }), - Action: routeCluster("default/svc3/80/da39a3ee5e"), + Action: routeCluster("default/svc2/80/da39a3ee5e"), }, &envoy_route_v3.Route{ - Match: routePrefixWithQueryParameterConditions("/", dag.QueryParamMatchCondition{ + Match: routePrefixWithQueryParameterConditions("/blog", dag.QueryParamMatchCondition{ Name: "query-param", MatchType: "present", }), - Action: routeCluster("default/svc2/80/da39a3ee5e"), + Action: routeCluster("default/svc3/80/da39a3ee5e"), }, &envoy_route_v3.Route{ Match: routePrefixWithQueryParameterConditions("/"), diff --git a/internal/featuretests/v3/replaceprefix_test.go b/internal/featuretests/v3/replaceprefix_test.go index bc46d400799..970540bf4cb 100644 --- a/internal/featuretests/v3/replaceprefix_test.go +++ b/internal/featuretests/v3/replaceprefix_test.go @@ -70,14 +70,14 @@ func basic(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("kuard.projectcontour.io", - &envoy_route_v3.Route{ - Match: routePrefix("/api/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/api"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/api/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), + }, ), ), ), @@ -115,14 +115,14 @@ func basic(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("kuard.projectcontour.io", - &envoy_route_v3.Route{ - Match: routePrefix("/api/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/api"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/api/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2/"), + }, ), ), ), @@ -161,14 +161,14 @@ func basic(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("kuard.projectcontour.io", - &envoy_route_v3.Route{ - Match: routePrefix("/api/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/full/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/api"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/full"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/api/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/full/"), + }, ), ), ), @@ -253,24 +253,24 @@ func multiInclude(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("host1.projectcontour.io", - &envoy_route_v3.Route{ - Match: routePrefix("/v1/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/v1"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1"), }, - ), - envoy_v3.VirtualHost("host2.projectcontour.io", &envoy_route_v3.Route{ - Match: routePrefix("/v2/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2/"), + Match: routePrefix("/v1/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), }, + ), + envoy_v3.VirtualHost("host2.projectcontour.io", &envoy_route_v3.Route{ Match: routePrefix("/v2"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/v2/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v2/"), + }, ), ), ), @@ -290,14 +290,14 @@ func multiInclude(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("host1.projectcontour.io", - &envoy_route_v3.Route{ - Match: routePrefix("/v1/"), - Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/v1"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/v1/"), + Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/api/v1/"), + }, ), envoy_v3.VirtualHost("host2.projectcontour.io", &envoy_route_v3.Route{ @@ -367,21 +367,21 @@ func replaceWithSlash(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("host1.projectcontour.io", &envoy_route_v3.Route{ - Match: routePrefix("/foo/"), + Match: routePrefix("/foo"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, &envoy_route_v3.Route{ - Match: routePrefix("/foo"), + Match: routePrefix("/foo/"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, ), envoy_v3.VirtualHost("host2.projectcontour.io", &envoy_route_v3.Route{ - Match: routePrefix("/bar/"), + Match: routePrefix("/bar"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, &envoy_route_v3.Route{ - Match: routePrefix("/bar"), + Match: routePrefix("/bar/"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, ), @@ -408,11 +408,11 @@ func replaceWithSlash(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("host1.projectcontour.io", &envoy_route_v3.Route{ - Match: routePrefix("/foo/"), + Match: routePrefix("/foo"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, &envoy_route_v3.Route{ - Match: routePrefix("/foo"), + Match: routePrefix("/foo/"), Action: withPrefixRewrite(routeCluster("default/kuard/8080/da39a3ee5e"), "/"), }, ), @@ -478,21 +478,15 @@ func artifactoryDocker(t *testing.T) { Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("artifactory.projectcontour.io", - - &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-sandbox/"), - Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-sandbox/v2/"), - }, &envoy_route_v3.Route{ Match: routePrefix("/v2/container-sandbox"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), "/artifactory/api/docker/container-sandbox/v2"), }, &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-release/"), + Match: routePrefix("/v2/container-sandbox/"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-release/v2/"), + "/artifactory/api/docker/container-sandbox/v2/"), }, &envoy_route_v3.Route{ Match: routePrefix("/v2/container-release"), @@ -500,14 +494,14 @@ func artifactoryDocker(t *testing.T) { "/artifactory/api/docker/container-release/v2"), }, &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-public/"), + Match: routePrefix("/v2/container-release/"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-public/v2/"), + "/artifactory/api/docker/container-release/v2/"), }, &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-public"), + Match: routePrefix("/v2/container-external"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-public/v2"), + "/artifactory/api/docker/container-external/v2"), }, &envoy_route_v3.Route{ Match: routePrefix("/v2/container-external/"), @@ -515,9 +509,14 @@ func artifactoryDocker(t *testing.T) { "/artifactory/api/docker/container-external/v2/"), }, &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-external"), + Match: routePrefix("/v2/container-public"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-external/v2"), + "/artifactory/api/docker/container-public/v2"), + }, + &envoy_route_v3.Route{ + Match: routePrefix("/v2/container-public/"), + Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), + "/artifactory/api/docker/container-public/v2/"), }, ), ), diff --git a/internal/featuretests/v3/route_test.go b/internal/featuretests/v3/route_test.go index b17f0902929..dd5176f235b 100644 --- a/internal/featuretests/v3/route_test.go +++ b/internal/featuretests/v3/route_test.go @@ -247,13 +247,16 @@ func TestEditIngressInPlace(t *testing.T) { Host: "hello.example.com", IngressRuleValue: networking_v1.IngressRuleValue{ HTTP: &networking_v1.HTTPIngressRuleValue{ - Paths: []networking_v1.HTTPIngressPath{{ - Path: "/", - Backend: *featuretests.IngressBackend(s1), - }, { - Path: "/whoop", - Backend: *featuretests.IngressBackend(s2), - }}, + Paths: []networking_v1.HTTPIngressPath{ + { + Path: "/whoop", + Backend: *featuretests.IngressBackend(s2), + }, + { + Path: "/", + Backend: *featuretests.IngressBackend(s1), + }, + }, }, }, }}, @@ -289,13 +292,16 @@ func TestEditIngressInPlace(t *testing.T) { Host: "hello.example.com", IngressRuleValue: networking_v1.IngressRuleValue{ HTTP: &networking_v1.HTTPIngressRuleValue{ - Paths: []networking_v1.HTTPIngressPath{{ - Path: "/", - Backend: *featuretests.IngressBackend(s1), - }, { - Path: "/whoop", - Backend: *featuretests.IngressBackend(s2), - }}, + Paths: []networking_v1.HTTPIngressPath{ + { + Path: "/whoop", + Backend: *featuretests.IngressBackend(s2), + }, + { + Path: "/", + Backend: *featuretests.IngressBackend(s1), + }, + }, }, }, }}, @@ -349,13 +355,16 @@ func TestEditIngressInPlace(t *testing.T) { Host: "hello.example.com", IngressRuleValue: networking_v1.IngressRuleValue{ HTTP: &networking_v1.HTTPIngressRuleValue{ - Paths: []networking_v1.HTTPIngressPath{{ - Path: "/", - Backend: *featuretests.IngressBackend(s1), - }, { - Path: "/whoop", - Backend: *featuretests.IngressBackend(s2), - }}, + Paths: []networking_v1.HTTPIngressPath{ + { + Path: "/whoop", + Backend: *featuretests.IngressBackend(s2), + }, + { + Path: "/", + Backend: *featuretests.IngressBackend(s1), + }, + }, }, }, }}, @@ -1237,26 +1246,26 @@ func TestRouteWithTLS_InsecurePaths(t *testing.T) { Resources: routeResources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: envoy_v3.UpgradeHTTPS(), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: envoy_v3.UpgradeHTTPS(), + }, ), ), envoy_v3.RouteConfiguration("https/test2.test.com", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: routecluster("default/svc2/80/da39a3ee5e"), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: routecluster("default/svc2/80/da39a3ee5e"), + }, ), ), ), @@ -1335,25 +1344,25 @@ func TestRouteWithTLS_InsecurePaths_DisablePermitInsecureTrue(t *testing.T) { envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("test2.test.com", &envoy_route_v3.Route{ - Match: routePrefix("/secure"), + Match: routePrefix("/insecure"), Action: envoy_v3.UpgradeHTTPS(), }, &envoy_route_v3.Route{ - Match: routePrefix("/insecure"), + Match: routePrefix("/secure"), Action: envoy_v3.UpgradeHTTPS(), }, ), ), envoy_v3.RouteConfiguration("https/test2.test.com", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: routecluster("default/svc2/80/da39a3ee5e"), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: routecluster("default/svc2/80/da39a3ee5e"), + }, ), ), ), @@ -1609,26 +1618,26 @@ func TestHTTPProxyRouteWithTLS_InsecurePaths(t *testing.T) { Resources: routeResources(t, envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: envoy_v3.UpgradeHTTPS(), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: envoy_v3.UpgradeHTTPS(), + }, ), ), envoy_v3.RouteConfiguration("https/test2.test.com", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: routecluster("default/svc2/80/da39a3ee5e"), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: routecluster("default/svc2/80/da39a3ee5e"), + }, ), ), ), @@ -1703,25 +1712,25 @@ func TestHTTPProxyRouteWithTLS_InsecurePaths_DisablePermitInsecureTrue(t *testin envoy_v3.RouteConfiguration("ingress_http", envoy_v3.VirtualHost("test2.test.com", &envoy_route_v3.Route{ - Match: routePrefix("/secure"), + Match: routePrefix("/insecure"), Action: envoy_v3.UpgradeHTTPS(), }, &envoy_route_v3.Route{ - Match: routePrefix("/insecure"), + Match: routePrefix("/secure"), Action: envoy_v3.UpgradeHTTPS(), }, ), ), envoy_v3.RouteConfiguration("https/test2.test.com", envoy_v3.VirtualHost("test2.test.com", - &envoy_route_v3.Route{ - Match: routePrefix("/secure"), - Action: routecluster("default/svc2/80/da39a3ee5e"), - }, &envoy_route_v3.Route{ Match: routePrefix("/insecure"), Action: routecluster("default/kuard/80/da39a3ee5e"), }, + &envoy_route_v3.Route{ + Match: routePrefix("/secure"), + Action: routecluster("default/svc2/80/da39a3ee5e"), + }, ), ), ), diff --git a/internal/featuretests/v3/websockets_test.go b/internal/featuretests/v3/websockets_test.go index 22cd3cda7eb..7defa0e8587 100644 --- a/internal/featuretests/v3/websockets_test.go +++ b/internal/featuretests/v3/websockets_test.go @@ -87,27 +87,31 @@ func TestWebsocketHTTPProxy(t *testing.T) { ObjectMeta: fixture.ObjectMeta("simple"), Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "websocket.hello.world"}, - Routes: []contour_api_v1.Route{{ - Conditions: matchconditions(prefixMatchCondition("/")), - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }}, - }, { - Conditions: matchconditions(prefixMatchCondition("/ws-1")), - EnableWebsockets: true, - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }}, - }, { - Conditions: matchconditions(prefixMatchCondition("/ws-2")), - EnableWebsockets: true, - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions(prefixMatchCondition("/ws-2")), + EnableWebsockets: true, + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + }, + { + Conditions: matchconditions(prefixMatchCondition("/ws-1")), + EnableWebsockets: true, + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + }, + { + Conditions: matchconditions(prefixMatchCondition("/")), + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + }, + }, }, } rh.OnAdd(hp1) @@ -138,30 +142,34 @@ func TestWebsocketHTTPProxy(t *testing.T) { ObjectMeta: fixture.ObjectMeta("simple"), Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{Fqdn: "websocket.hello.world"}, - Routes: []contour_api_v1.Route{{ - Conditions: matchconditions(prefixMatchCondition("/")), - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }}, - }, { - Conditions: matchconditions(prefixMatchCondition("/ws-1")), - EnableWebsockets: true, - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }, { - Name: s2.Name, - Port: 80, - }}, - }, { - Conditions: matchconditions(prefixMatchCondition("/ws-2")), - EnableWebsockets: true, - Services: []contour_api_v1.Service{{ - Name: s1.Name, - Port: 80, - }}, - }}, + Routes: []contour_api_v1.Route{ + { + Conditions: matchconditions(prefixMatchCondition("/ws-2")), + EnableWebsockets: true, + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + }, + { + Conditions: matchconditions(prefixMatchCondition("/ws-1")), + EnableWebsockets: true, + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }, { + Name: s2.Name, + Port: 80, + }}, + }, + { + Conditions: matchconditions(prefixMatchCondition("/")), + Services: []contour_api_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + }, + }, }, } rh.OnUpdate(hp1, hp2) diff --git a/internal/xdscache/v3/route.go b/internal/xdscache/v3/route.go index 6b858159abf..b22fa0fb460 100644 --- a/internal/xdscache/v3/route.go +++ b/internal/xdscache/v3/route.go @@ -111,10 +111,10 @@ func (c *RouteCache) OnChange(root *dag.DAG) { } var routes []*dag.Route - for _, route := range vhost.Routes { - routes = append(routes, route) + routes = append(routes, vhost.Routes...) + if vhost.ShouldSortRoutes { + sortRoutes(routes) } - sortRoutes(routes) routeConfigs[routeConfigName].VirtualHosts = append(routeConfigs[routeConfigName].VirtualHosts, envoy_v3.VirtualHostAndRoutes(vhost, routes, false), @@ -136,10 +136,10 @@ func (c *RouteCache) OnChange(root *dag.DAG) { } var routes []*dag.Route - for _, route := range vhost.Routes { - routes = append(routes, route) + routes = append(routes, vhost.Routes...) + if vhost.ShouldSortRoutes { + sortRoutes(routes) } - sortRoutes(routes) routeConfigs[routeConfigName].VirtualHosts = append(routeConfigs[routeConfigName].VirtualHosts, envoy_v3.VirtualHostAndRoutes(&vhost.VirtualHost, routes, true)) @@ -161,10 +161,6 @@ func (c *RouteCache) OnChange(root *dag.DAG) { } } - for _, routeConfig := range routeConfigs { - sort.Stable(sorter.For(routeConfig.VirtualHosts)) - } - c.Update(routeConfigs) } From fb8a4f73ba821d6f831be55f9914257e7b52ee6e Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Thu, 21 Sep 2023 13:35:24 -0400 Subject: [PATCH 02/15] fix lint Signed-off-by: Sotiris Nanopoulos --- internal/dag/accessors.go | 8 ++------ internal/dag/gatewayapi_processor.go | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/internal/dag/accessors.go b/internal/dag/accessors.go index 8883a1d6ff1..ca7b9f6b713 100644 --- a/internal/dag/accessors.go +++ b/internal/dag/accessors.go @@ -335,9 +335,7 @@ func (d *DAG) GetVirtualHostRoutes() map[*VirtualHost][]*Route { for _, l := range d.Listeners { for _, vhost := range l.VirtualHosts { var routes []*Route - for _, r := range vhost.Routes { - routes = append(routes, r) - } + routes = append(routes, vhost.Routes...) if len(routes) > 0 { res[vhost] = routes } @@ -353,9 +351,7 @@ func (d *DAG) GetSecureVirtualHostRoutes() map[*SecureVirtualHost][]*Route { for _, l := range d.Listeners { for _, vhost := range l.SecureVirtualHosts { var routes []*Route - for _, r := range vhost.Routes { - routes = append(routes, r) - } + routes = append(routes, vhost.Routes...) if len(routes) > 0 { res[vhost] = routes } diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index 0d321190c1a..667eca1e7a1 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -1357,11 +1357,11 @@ func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1be svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret svhost.AddRoute(route) - //TODO(davinci26): should we use the sorter here? + // TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) vhost.AddRoute(route) - //TODO(davinci26): should we use the sorter here? + // TODO(davinci26): should we use the sorter here? } programmed = true @@ -1503,11 +1503,11 @@ func (p *GatewayAPIProcessor) computeGRPCRouteForListener(route *gatewayapi_v1al svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret svhost.AddRoute(route) - //TODO(davinci26): should we use the sorter here? + // TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) vhost.AddRoute(route) - //TODO(davinci26): should we use the sorter here? + // TODO(davinci26): should we use the sorter here? } programmed = true From edfcf184cfef26b188c7c406f5892ad61ef91c02 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 23 Oct 2023 06:45:00 -0400 Subject: [PATCH 03/15] using the CI for e2e tests Signed-off-by: Sotiris Nanopoulos --- .../v1alpha1/contourconfig_helpers.go | 15 ++ changelogs/unreleased/5772-davinci26-small.md | 5 + cmd/contour/serve.go | 3 + cmd/contour/serve_test.go | 19 ++ internal/dag/builder_test.go | 227 ++++++++++-------- internal/dag/gatewayapi_processor.go | 10 +- internal/dag/httpproxy_processor.go | 10 + .../featuretests/v3/replaceprefix_test.go | 10 - 8 files changed, 182 insertions(+), 117 deletions(-) create mode 100644 changelogs/unreleased/5772-davinci26-small.md diff --git a/apis/projectcontour/v1alpha1/contourconfig_helpers.go b/apis/projectcontour/v1alpha1/contourconfig_helpers.go index b2229f187ab..02b0e862b76 100644 --- a/apis/projectcontour/v1alpha1/contourconfig_helpers.go +++ b/apis/projectcontour/v1alpha1/contourconfig_helpers.go @@ -15,11 +15,16 @@ package v1alpha1 import ( "fmt" + "os" "strconv" "k8s.io/apimachinery/pkg/util/sets" ) +const ( + featureFlagRouteSorting string = "omitRouteSorting" +) + // Validate configuration that is not already covered by CRD validation. func (c *ContourConfigurationSpec) Validate() error { // Validation of root configuration fields. @@ -232,6 +237,16 @@ func (g *GatewayConfig) Validate() error { return nil } +// TODO(sotiris): Pick up the feature flag in Endpoint Slices PR +// func (f FeatureFlags) RouteSortingEnabled() bool { +// return slices.Contains(f, featureFlagUseEndpointSlices) +// return slices.Contains([]string{featureFlagRouteSorting}, featureFlagRouteSorting) +// } +func ShouldSortHTTPProxyRoutes() bool { + f := os.Getenv(featureFlagRouteSorting) + return f == "" +} + func (e *EnvoyLogging) Validate() error { if e == nil { return nil diff --git a/changelogs/unreleased/5772-davinci26-small.md b/changelogs/unreleased/5772-davinci26-small.md new file mode 100644 index 00000000000..54122a65aa0 --- /dev/null +++ b/changelogs/unreleased/5772-davinci26-small.md @@ -0,0 +1,5 @@ +## Allow cluster operators to disable route sorting with `HTTPProxy` + +This change allows cluster admins to turn on a `feature-flag` that disables route sorting. When this feature flag is turned on routes are sent to Envoy in the same +order as they are described in the `HTTPProxy` CRD. This allows operators to build more complex routing tables but they need to be careful with changes since now order +becomes important. Includes are resolved in a depth first fashion. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 85490f7caa4..1dedd1195db 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -540,6 +540,7 @@ func (s *Server) doServe() error { globalRateLimitService: contourConfiguration.RateLimitService, maxRequestsPerConnection: contourConfiguration.Envoy.Cluster.MaxRequestsPerConnection, perConnectionBufferLimitBytes: contourConfiguration.Envoy.Cluster.PerConnectionBufferLimitBytes, + sortHTTProxyRoutes: contour_api_v1alpha1.ShouldSortHTTPProxyRoutes(), }) // Build the core Kubernetes event handler. @@ -1085,6 +1086,7 @@ type dagBuilderConfig struct { maxRequestsPerConnection *uint32 perConnectionBufferLimitBytes *uint32 globalRateLimitService *contour_api_v1alpha1.RateLimitServiceConfig + sortHTTProxyRoutes bool } func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { @@ -1177,6 +1179,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { GlobalRateLimitService: dbc.globalRateLimitService, PerConnectionBufferLimitBytes: dbc.perConnectionBufferLimitBytes, SetSourceMetadataOnRoutes: true, + ShouldSortRoutes: dbc.sortHTTProxyRoutes, }, } diff --git a/cmd/contour/serve_test.go b/cmd/contour/serve_test.go index 8f6ac4234b1..22dd996ea4e 100644 --- a/cmd/contour/serve_test.go +++ b/cmd/contour/serve_test.go @@ -40,6 +40,7 @@ func TestGetDAGBuilder(t *testing.T) { httpProxyProcessor := mustGetHTTPProxyProcessor(t, builder) assert.True(t, httpProxyProcessor.SetSourceMetadataOnRoutes) + assert.False(t, httpProxyProcessor.ShouldSortRoutes) } t.Run("all default options", func(t *testing.T) { @@ -189,6 +190,24 @@ func TestGetDAGBuilder(t *testing.T) { assert.EqualValues(t, ingressClassNames, got.Source.IngressClassNames) }) + t.Run("Sort HTTPRoutes config", func(t *testing.T) { + serve := &Server{ + log: logrus.StandardLogger(), + } + got := serve.getDAGBuilder(dagBuilderConfig{rootNamespaces: []string{}, dnsLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, sortHTTProxyRoutes: true}) + + assert.Empty(t, got.Source.ConfiguredSecretRefs) + assert.Len(t, got.Processors, 4) + assert.IsType(t, &dag.ListenerProcessor{}, got.Processors[0]) + + ingressProcessor := mustGetIngressProcessor(t, got) + assert.True(t, ingressProcessor.SetSourceMetadataOnRoutes) + + httpProxyProcessor := mustGetHTTPProxyProcessor(t, got) + assert.True(t, httpProxyProcessor.SetSourceMetadataOnRoutes) + assert.True(t, httpProxyProcessor.ShouldSortRoutes) + }) + // TODO(3453): test additional properties of the DAG builder (processor fields, cache fields, Gateway tests (requires a client fake)) } diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index ee84a688b4e..7d5838b3494 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -487,7 +487,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -520,7 +520,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -588,7 +588,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), ), }, ), @@ -687,7 +687,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", + ShouldSortRoutes: true, + Name: "test.projectcontour.io", }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardServiceCustomNs)), @@ -724,7 +725,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", + Name: "test.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -790,7 +792,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", + Name: "test.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardServiceCustomNs)), @@ -1018,8 +1021,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("another.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("another.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1128,7 +1131,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts( virtualhost( "test.projectcontour.io", - false, + true, prefixrouteHTTPRoute("/", service(kuardService)), &Route{ PathMatchCondition: prefixSegment("/blog"), @@ -1184,10 +1187,10 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test2.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test3.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), - virtualhost("test4.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test2.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test3.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test4.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1217,7 +1220,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("*", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1249,7 +1252,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("*.projectcontour.io", true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -1362,7 +1365,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }, ), @@ -1400,7 +1403,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }, ), @@ -1439,7 +1442,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1492,7 +1495,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService)))), + VirtualHosts: virtualhosts(virtualhost("*", true, prefixrouteHTTPRoute("/", service(kuardService)))), }), }, "HTTPRoute references a backend in a different namespace, with valid ReferenceGrant (service-specific)": { @@ -1545,7 +1548,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", false, prefixrouteHTTPRoute("/", service(kuardService)))), + VirtualHosts: virtualhosts(virtualhost("*", true, prefixrouteHTTPRoute("/", service(kuardService)))), }), }, "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong Kind)": { @@ -1598,7 +1601,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1652,7 +1655,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1706,7 +1709,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1761,7 +1764,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(&Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRoute("/", http.StatusInternalServerError)), + virtualhost("*", true, directResponseRoute("/", http.StatusInternalServerError)), ), }), }, @@ -1794,7 +1797,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - false, + true, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -1829,7 +1832,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - false, + true, regexrouteHTTPRoute("/bl+og", service(kuardService))), ), }, @@ -1880,7 +1883,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - false, + true, prefixrouteHTTPRoute("/", service(kuardService)), segmentPrefixHTTPRoute("/blog", service(kuardService)), segmentPrefixHTTPRoute("/tech", service(kuardService))), @@ -1925,7 +1928,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Port: 8443, VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - false, + true, prefixrouteHTTPRoute("/", service(kuardService)), )), EnableWebsockets: true, @@ -1972,8 +1975,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + Name: "test.projectcontour.io", + Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + ShouldSortRoutes: true, }, Secret: secret(sec1), }, @@ -2021,8 +2025,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + Name: "test.projectcontour.io", + Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + ShouldSortRoutes: true, }, Secret: secret(sec1), }, @@ -2031,7 +2036,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -2130,8 +2135,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + Name: "test.projectcontour.io", + Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + ShouldSortRoutes: true, }, Secret: secret(sec2), }, @@ -2180,8 +2186,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + Name: "test.projectcontour.io", + Routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), + ShouldSortRoutes: true, }, Secret: secret(sec2), }, @@ -2469,8 +2476,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(prefixrouteHTTPRoute("/", service(blogService))), + Name: "test.projectcontour.io", + Routes: routes(prefixrouteHTTPRoute("/", service(blogService))), + ShouldSortRoutes: true, }, Secret: secret(sec1), }, @@ -2479,7 +2487,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, prefixrouteHTTPRoute("/", service(kuardService))), + virtualhost("test.projectcontour.io", true, prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -2518,7 +2526,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2572,7 +2580,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/blog"), Clusters: clustersWeight(service(kuardService)), @@ -2618,7 +2626,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2665,7 +2673,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2712,7 +2720,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -2763,7 +2771,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2815,7 +2823,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2882,7 +2890,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -2952,7 +2960,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -3013,7 +3021,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -3067,7 +3075,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", Port: 8080, VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), QueryParamMatchConditions: []QueryParamMatchCondition{ @@ -3136,7 +3144,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3212,7 +3220,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3295,7 +3303,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(map[string]string{"Custom-Header-Set": "foo-bar"}, map[string]string{"Custom-Header-Add": "foo-bar"}, []string{"X-Remove"}, "bar.com", nil, nil, nil, service(kuardService)), @@ -3368,7 +3376,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{"Custom-Header-Add": "foo-bar"}, []string{"X-Remove"}, service(kuardService)), @@ -3418,7 +3426,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clustersWeight(service(kuardService)), @@ -3480,7 +3488,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(map[string]string{"Custom-Header-Set": "foo-bar"}, map[string]string{}, nil, "bar.com", nil, nil, nil, service(kuardService)), @@ -3537,7 +3545,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{}, nil, service(kuardService)), @@ -3582,7 +3590,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -3635,7 +3643,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixString("/"), Redirect: &Redirect{ @@ -3695,7 +3703,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3745,7 +3753,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3795,7 +3803,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), Redirect: &Redirect{ @@ -3842,7 +3850,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", true, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2)}, 100))), }, ), @@ -3890,7 +3898,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", true, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2), service(kuardService3)}, 100))), }, ), @@ -3932,7 +3940,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", true, withMirror(prefixrouteHTTPRoute("/", service(kuardService)), []*Service{service(kuardService2)}, 100), withMirror(segmentPrefixHTTPRoute("/another-match", service(kuardService)), []*Service{service(kuardService2)}, 100), )), @@ -3976,7 +3984,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -4025,7 +4033,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -4074,7 +4082,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), PathRewritePolicy: &PathRewritePolicy{ @@ -4120,7 +4128,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), RequestHeadersPolicy: &HeadersPolicy{HostRewrite: "rewritten.com"}, @@ -4177,7 +4185,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: prefixSegment("/prefix"), RequestHeadersPolicy: &HeadersPolicy{ @@ -4223,7 +4231,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, prefixrouteHTTPRoute("/", + virtualhost("*", true, prefixrouteHTTPRoute("/", &Service{ Weighted: WeightedService{ Weight: 5, @@ -4287,7 +4295,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, prefixrouteHTTPRoute("/", + virtualhost("*", true, prefixrouteHTTPRoute("/", &Service{ Weighted: WeightedService{ Weight: 5, @@ -4347,7 +4355,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("*", false, directResponseRouteService("/", http.StatusInternalServerError, &Service{ + virtualhost("*", true, directResponseRouteService("/", http.StatusInternalServerError, &Service{ Weighted: WeightedService{ Weight: 0, ServiceName: kuardService.Name, @@ -4387,7 +4395,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4473,7 +4482,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4537,7 +4547,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4775,7 +4786,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "another.projectcontour.io", + Name: "another.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4783,7 +4795,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4791,7 +4804,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "thing.projectcontour.io", + Name: "thing.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4832,7 +4846,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4840,7 +4855,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "thing.projectcontour.io", + Name: "thing.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4903,7 +4919,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "*", + Name: "*", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ Clusters: clustersWeight(service(kuardService)), @@ -4968,7 +4985,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ @@ -5016,7 +5034,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ @@ -5064,7 +5083,8 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "tcp.projectcontour.io", + Name: "tcp.projectcontour.io", + ShouldSortRoutes: true, }, TCPProxy: &TCPProxy{ @@ -5104,7 +5124,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("gateway.projectcontour.io", false, + virtualhost("gateway.projectcontour.io", true, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -5139,7 +5159,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Name: "http-80", VirtualHosts: virtualhosts( virtualhost("http.projectcontour.io", - false, + true, exactrouteHTTPRoute("/blog", service(kuardService))), ), }, @@ -5156,7 +5176,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), + virtualhost("test.projectcontour.io", true, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), ), }, ), @@ -5277,8 +5297,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { SecureVirtualHosts: securevirtualhosts( &SecureVirtualHost{ VirtualHost: VirtualHost{ - Name: "test.projectcontour.io", - Routes: routes(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(blogService, "h2"))), + Name: "test.projectcontour.io", + Routes: routes(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(blogService, "h2"))), + ShouldSortRoutes: true, }, Secret: secret(sec1), }, @@ -5287,7 +5308,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), + virtualhost("test.projectcontour.io", true, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c"))), ), }, ), @@ -5323,7 +5344,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -5366,7 +5387,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), HeaderMatchConditions: []HeaderMatchCondition{ @@ -5408,7 +5429,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: &PrefixMatchCondition{Prefix: "/"}, HeaderMatchConditions: []HeaderMatchCondition{ @@ -5448,7 +5469,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: &PrefixMatchCondition{Prefix: "/"}, Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5517,7 +5538,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5595,7 +5616,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5657,7 +5678,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clustersWeight(grpcService(kuardService, "h2c")), @@ -5721,7 +5742,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Name: "http-80", VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", - false, + true, &Route{ PathMatchCondition: exact("/io.projectcontour/Login"), Clusters: clusterHeaders(nil, nil, nil, "", map[string]string{"Custom-Header-Set": "foo-bar", "Host": "bar.com"}, map[string]string{}, nil, grpcService(kuardService, "h2c")), @@ -5766,7 +5787,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", true, withMirror(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")), []*Service{grpcService(kuardService2, "h2c")}, 100))), }, ), @@ -5816,7 +5837,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners( &Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", false, + VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", true, withMirror(exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")), []*Service{grpcService(kuardService2, "h2c"), grpcService(kuardService3, "h2c")}, 100))), }, ), @@ -5873,7 +5894,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(&Listener{ Name: "http-80", - VirtualHosts: virtualhosts(virtualhost("*", false, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")))), + VirtualHosts: virtualhosts(virtualhost("*", true, exactrouteGRPCRoute("/io.projectcontour/Login", grpcService(kuardService, "h2c")))), }), }, } diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index 7c32fd554c8..556287aedab 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -1024,6 +1024,7 @@ func (p *GatewayAPIProcessor) computeTLSRouteForListener(route *gatewayapi_v1alp for host := range hosts { secure := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) + secure.ShouldSortRoutes = true if listener.tlsSecret != nil { secure.Secret = listener.tlsSecret @@ -1378,12 +1379,12 @@ func (p *GatewayAPIProcessor) computeHTTPRouteForListener(route *gatewayapi_v1be case listener.tlsSecret != nil: svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret + svhost.ShouldSortRoutes = true svhost.AddRoute(route) - // TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) + vhost.ShouldSortRoutes = true vhost.AddRoute(route) - // TODO(davinci26): should we use the sorter here? } programmed = true @@ -1524,12 +1525,12 @@ func (p *GatewayAPIProcessor) computeGRPCRouteForListener(route *gatewayapi_v1al case listener.tlsSecret != nil: svhost := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, host) svhost.Secret = listener.tlsSecret + svhost.ShouldSortRoutes = true svhost.AddRoute(route) - // TODO(davinci26): should we use the sorter here? default: vhost := p.dag.EnsureVirtualHost(listener.dagListenerName, host) + vhost.ShouldSortRoutes = true vhost.AddRoute(route) - // TODO(davinci26): should we use the sorter here? } programmed = true @@ -1686,6 +1687,7 @@ func (p *GatewayAPIProcessor) computeTCPRouteForListener(route *gatewayapi_v1alp if listener.tlsSecret != nil { secure := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, "*") + secure.ShouldSortRoutes = true secure.Secret = listener.tlsSecret secure.TCPProxy = &proxy } else { diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 1bccc414a5c..f9468da8715 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -112,6 +112,10 @@ type HTTPProxyProcessor struct { // configurable and off by default in order to support the feature // without requiring all existing test cases to change. SetSourceMetadataOnRoutes bool + + // Allows cluster operators to prevent the processor from sorting the routes + // before sending them to Envoy. + ShouldSortRoutes bool } // Run translates HTTPProxies into DAG objects and @@ -544,6 +548,9 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { return } + if !insecure.ShouldSortRoutes { + insecure.ShouldSortRoutes = p.ShouldSortRoutes + } addRoutes(insecure, routes) // if TLS is enabled for this virtual host and there is no tcp proxy defined, @@ -570,6 +577,9 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { return } + if !secure.ShouldSortRoutes { + secure.ShouldSortRoutes = p.ShouldSortRoutes + } addRoutes(secure, routes) // Process JWT verification requirements. diff --git a/internal/featuretests/v3/replaceprefix_test.go b/internal/featuretests/v3/replaceprefix_test.go index 97c12608010..970540bf4cb 100644 --- a/internal/featuretests/v3/replaceprefix_test.go +++ b/internal/featuretests/v3/replaceprefix_test.go @@ -488,16 +488,6 @@ func artifactoryDocker(t *testing.T) { Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), "/artifactory/api/docker/container-sandbox/v2/"), }, - &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-external"), - Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-external/v2"), - }, - &envoy_route_v3.Route{ - Match: routePrefix("/v2/container-sandbox"), - Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), - "/artifactory/api/docker/container-sandbox/v2"), - }, &envoy_route_v3.Route{ Match: routePrefix("/v2/container-release"), Action: withPrefixRewrite(routeCluster("artifactory/service/8080/da39a3ee5e"), From 7eb02e669a295652fbc72b9114b691f3af56f80a Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 23 Oct 2023 07:09:27 -0400 Subject: [PATCH 04/15] fix linting Signed-off-by: Sotiris Nanopoulos --- internal/dag/httpproxy_processor.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index f9468da8715..97a2b5facae 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -1504,9 +1504,7 @@ func expandPrefixMatches(routes []*Route) []*Route { func expandPrefixMatchesOrdered(routes []*Route) []*Route { expandedRoutes := []*Route{} - prefixCount := make(map[string]int, 0) - // First, we find the non expandable prefix routes. Those are two: // - if both versions of the route exist e.g. /api/ and /api // - root prefix `/` @@ -1526,7 +1524,7 @@ func expandPrefixMatchesOrdered(routes []*Route) []*Route { routingPrefix = strings.TrimRight(routingPrefix, "/") } - prefixCount[routingPrefix] += 1 + prefixCount[routingPrefix]++ } for _, r := range routes { From 1f7925e1c7f2fdf088bfcd4eed52c9a6b0aaadfe Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 23 Oct 2023 09:30:51 -0400 Subject: [PATCH 05/15] tighten up tests Signed-off-by: Sotiris Nanopoulos --- internal/dag/builder_sorted_test.go | 167 ++++++++++++++++++++++++++++ internal/xdscache/v3/route_test.go | 112 +++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 internal/dag/builder_sorted_test.go diff --git a/internal/dag/builder_sorted_test.go b/internal/dag/builder_sorted_test.go new file mode 100644 index 00000000000..3fd3dec3ac6 --- /dev/null +++ b/internal/dag/builder_sorted_test.go @@ -0,0 +1,167 @@ +// Copyright Project Contour Authors +// Licensed 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 dag + +import ( + "testing" + + contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/internal/fixture" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestOriginalRouting(t *testing.T) { + proxyObject := &contour_api_v1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-com", + Namespace: "default", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + Fqdn: "unique.com", + }, + Routes: []contour_api_v1.Route{ + { + Conditions: []contour_api_v1.MatchCondition{{ + Prefix: "/", + }}, + Services: []contour_api_v1.Service{{ + Name: "kuard", + Port: 8080, + }}, + }, + { + Conditions: []contour_api_v1.MatchCondition{{ + Regex: "/fizzbuzz.*", + }}, + Services: []contour_api_v1.Service{{ + Name: "kuard", + Port: 8080, + }}, + }, + { + Conditions: []contour_api_v1.MatchCondition{{ + Exact: "/foobar", + }}, + Services: []contour_api_v1.Service{{ + Name: "kuarder", + Port: 8080, + }}, + }, + }, + }, + } + + s1 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kuard", + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{makeServicePort("http", "TCP", 8080, 8080)}, + }, + } + + // s2 is like s1 but with a different name + s2 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kuarder", + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{makeServicePort("http", "TCP", 8080, 8080)}, + }, + } + + tests := []struct { + name string + objs []any + want []*Listener + ingressReqHp *HeadersPolicy + ingressRespHp *HeadersPolicy + httpProxyReqHp *HeadersPolicy + httpProxyRespHp *HeadersPolicy + wantErr error + }{ + { + name: "VHost should be marked as needs to be sorted", + objs: []any{ + proxyObject, s1, s2, + }, + want: listeners( + &Listener{ + Name: HTTP_LISTENER_NAME, + Port: 8080, + VirtualHosts: virtualhosts( + virtualhost("unique.com", true, + &Route{ + PathMatchCondition: prefixString("/"), + Clusters: clusters(service(s1)), + }, + &Route{ + PathMatchCondition: regex("/fizzbuzz.*"), + Clusters: clusters(service(s1)), + }, + &Route{ + PathMatchCondition: exact("/foobar"), + Clusters: clusters(service(s2)), + }, + ), + ), + }, + ), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + builder := Builder{ + Source: KubernetesCache{ + FieldLogger: fixture.NewTestLogger(t), + }, + Processors: []Processor{ + &ListenerProcessor{}, + &IngressProcessor{ + FieldLogger: fixture.NewTestLogger(t), + RequestHeadersPolicy: tc.ingressReqHp, + ResponseHeadersPolicy: tc.ingressRespHp, + }, + &HTTPProxyProcessor{ + ShouldSortRoutes: true, + RequestHeadersPolicy: tc.httpProxyReqHp, + ResponseHeadersPolicy: tc.httpProxyRespHp, + }, + }, + } + + for _, o := range tc.objs { + builder.Source.Insert(o) + } + dag := builder.Build() + + got := make(map[int]*Listener) + for _, l := range dag.Listeners { + got[l.Port] = l + } + + want := make(map[int]*Listener) + for _, l := range tc.want { + want[l.Port] = l + } + assert.Equal(t, want, got) + }) + } +} diff --git a/internal/xdscache/v3/route_test.go b/internal/xdscache/v3/route_test.go index ff167ff4ee8..1e9a1dff6ce 100644 --- a/internal/xdscache/v3/route_test.go +++ b/internal/xdscache/v3/route_test.go @@ -3459,6 +3459,118 @@ func TestRouteVisit(t *testing.T) { } } +func TestRouteVisit_WithSortingEnabled(t *testing.T) { + tests := map[string]struct { + objs []any + want map[string]*envoy_route_v3.RouteConfiguration + }{ + "Routes should be sorted with sorter enabled": { + objs: []any{ + &contour_api_v1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "simple", + Namespace: "default", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + Fqdn: "www.unique.com", + }, + Routes: []contour_api_v1.Route{ + { + Conditions: []contour_api_v1.MatchCondition{{ + Prefix: "/", + }}, + Services: []contour_api_v1.Service{{ + Name: "backend", + Port: 80, + }}, + }, + { + Conditions: []contour_api_v1.MatchCondition{{ + Regex: "/fizzbuzz.*", + }}, + Services: []contour_api_v1.Service{{ + Name: "backend", + Port: 80, + }}, + }, + { + Conditions: []contour_api_v1.MatchCondition{{ + Exact: "/foobar", + }}, + Services: []contour_api_v1.Service{{ + Name: "backend", + Port: 80, + }}, + }, + }, + }, + }, + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backend", + Namespace: "default", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Protocol: "TCP", + Port: 80, + TargetPort: intstr.FromInt(8080), + }}, + }, + }, + }, + want: routeConfigurations( + envoy_v3.RouteConfiguration("ingress_http", + envoy_v3.VirtualHost("www.unique.com", + &envoy_route_v3.Route{ + Match: routeExact("/foobar"), + Action: routecluster("default/backend/80/da39a3ee5e"), + }, + &envoy_route_v3.Route{ + Match: routeRegex("/fizzbuzz.*"), + Action: routecluster("default/backend/80/da39a3ee5e"), + }, + &envoy_route_v3.Route{ + Match: routePrefix("/"), + Action: routecluster("default/backend/80/da39a3ee5e"), + }, + ), + ), + ), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + var rc RouteCache + builder := dag.Builder{ + Source: dag.KubernetesCache{ + FieldLogger: fixture.NewTestLogger(t), + }, + Processors: []dag.Processor{ + &dag.ListenerProcessor{ + HTTPAddress: "0.0.0.0", + HTTPPort: 8080, + HTTPSAddress: "0.0.0.0", + HTTPSPort: 8443, + }, + &dag.IngressProcessor{ + FieldLogger: fixture.NewTestLogger(t), + }, + &dag.HTTPProxyProcessor{ + ShouldSortRoutes: true, + }, + }, + } + for _, o := range tc.objs { + builder.Source.Insert(o) + } + rc.OnChange(builder.Build()) + protobuf.ExpectEqual(t, tc.want, rc.values) + }) + } +} + func TestRouteVisit_GlobalExternalAuthorization(t *testing.T) { tests := map[string]struct { objs []any From 42d207880f4d97dc67d55218342abf1cf33f2161 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 23 Oct 2023 11:05:43 -0400 Subject: [PATCH 06/15] retry ci Signed-off-by: Sotiris Nanopoulos From e2b470c4bbdb55a411ab65fbe72e88f2ed068f6c Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 23 Oct 2023 19:00:38 -0400 Subject: [PATCH 07/15] kick ci Signed-off-by: Sotiris Nanopoulos From 046a524c522252a31fb63183e7866d480e3121a1 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Wed, 25 Oct 2023 13:14:29 -0400 Subject: [PATCH 08/15] fixup Signed-off-by: Sotiris Nanopoulos --- cmd/contour/serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 131c82e5964..8461df3e17d 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -551,7 +551,7 @@ func (s *Server) doServe() error { globalRateLimitService: contourConfiguration.RateLimitService, maxRequestsPerConnection: contourConfiguration.Envoy.Cluster.MaxRequestsPerConnection, perConnectionBufferLimitBytes: contourConfiguration.Envoy.Cluster.PerConnectionBufferLimitBytes, - sortHTTProxyRoutes: contour_api_v1alpha1.ShouldSortHTTPProxyRoutes(), + sortHTTProxyRoutes: contourConfiguration.FeatureFlags.RouteSortingEnabled(), }) // Build the core Kubernetes event handler. From 43326d810860af774e3a8980c19e9ca6a426361d Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Wed, 25 Oct 2023 18:12:50 -0400 Subject: [PATCH 09/15] fixup default case Signed-off-by: Sotiris Nanopoulos --- apis/projectcontour/v1alpha1/contourconfig_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/projectcontour/v1alpha1/contourconfig_helpers.go b/apis/projectcontour/v1alpha1/contourconfig_helpers.go index b14ec0c892c..33de5f4e243 100644 --- a/apis/projectcontour/v1alpha1/contourconfig_helpers.go +++ b/apis/projectcontour/v1alpha1/contourconfig_helpers.go @@ -258,7 +258,7 @@ func (g *GatewayConfig) Validate() error { } func (f FeatureFlags) RouteSortingEnabled() bool { - return slices.Contains(f, featureFlagUseEndpointSlices) + return !slices.Contains(f, featureFlagUseEndpointSlices) } func (e *EnvoyLogging) Validate() error { From 38e55c1c1b5b6663248418c91fab132bc548d6f4 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Thu, 26 Oct 2023 10:08:38 -0400 Subject: [PATCH 10/15] change the configuration surface Signed-off-by: Sotiris Nanopoulos --- apis/projectcontour/v1alpha1/contourconfig.go | 5 +++++ .../v1alpha1/contourconfig_helpers.go | 6 ------ changelogs/unreleased/5772-davinci26-small.md | 4 +--- cmd/contour/serve.go | 2 +- examples/contour/01-crds.yaml | 10 ++++++++++ examples/render/contour-deployment.yaml | 10 ++++++++++ examples/render/contour-gateway-provisioner.yaml | 10 ++++++++++ examples/render/contour-gateway.yaml | 10 ++++++++++ examples/render/contour.yaml | 10 ++++++++++ site/content/docs/main/config/api-reference.html | 14 ++++++++++++++ 10 files changed, 71 insertions(+), 10 deletions(-) diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index 015bcb8b6e2..e2b7e0275e3 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -707,6 +707,11 @@ type HTTPProxyConfig struct { // use as fallback when a non-SNI request is received. // +optional FallbackCertificate *NamespacedName `json:"fallbackCertificate,omitempty"` + + // OmitRouteSorting determines if routes will be sorted by Contour before + // being sent to Envoy or the original order will be determined. + // Defaults to routing being sorted. + OmitRouteSorting bool `json:"omitRouteSorting,omitempty"` } // NetworkParameters hold various configurable network values. diff --git a/apis/projectcontour/v1alpha1/contourconfig_helpers.go b/apis/projectcontour/v1alpha1/contourconfig_helpers.go index 33de5f4e243..03e7be080f6 100644 --- a/apis/projectcontour/v1alpha1/contourconfig_helpers.go +++ b/apis/projectcontour/v1alpha1/contourconfig_helpers.go @@ -22,13 +22,11 @@ import ( ) const ( - featureFlagRouteSorting string = "omitRouteSorting" featureFlagUseEndpointSlices string = "useEndpointSlices" ) var featureFlagsMap = map[string]bool{ featureFlagUseEndpointSlices: true, - featureFlagRouteSorting: true, } // Validate configuration that is not already covered by CRD validation. @@ -257,10 +255,6 @@ func (g *GatewayConfig) Validate() error { return nil } -func (f FeatureFlags) RouteSortingEnabled() bool { - return !slices.Contains(f, featureFlagUseEndpointSlices) -} - func (e *EnvoyLogging) Validate() error { if e == nil { return nil diff --git a/changelogs/unreleased/5772-davinci26-small.md b/changelogs/unreleased/5772-davinci26-small.md index 54122a65aa0..402373eedb1 100644 --- a/changelogs/unreleased/5772-davinci26-small.md +++ b/changelogs/unreleased/5772-davinci26-small.md @@ -1,5 +1,3 @@ ## Allow cluster operators to disable route sorting with `HTTPProxy` -This change allows cluster admins to turn on a `feature-flag` that disables route sorting. When this feature flag is turned on routes are sent to Envoy in the same -order as they are described in the `HTTPProxy` CRD. This allows operators to build more complex routing tables but they need to be careful with changes since now order -becomes important. Includes are resolved in a depth first fashion. +This change allows contour administrators to turn on a `flag`, `OmitRouteSorting` that disables route sorting. When this configuration flag is turned on routes are sent to Envoy in the same order as they are described in the `HTTPProxy` CRD. This allows operators to build more complex routing tables but they need to be careful with changes since now order becomes important. Includes are resolved in a depth first fashion. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 8461df3e17d..bed898bbb20 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -551,7 +551,7 @@ func (s *Server) doServe() error { globalRateLimitService: contourConfiguration.RateLimitService, maxRequestsPerConnection: contourConfiguration.Envoy.Cluster.MaxRequestsPerConnection, perConnectionBufferLimitBytes: contourConfiguration.Envoy.Cluster.PerConnectionBufferLimitBytes, - sortHTTProxyRoutes: contourConfiguration.FeatureFlags.RouteSortingEnabled(), + sortHTTProxyRoutes: !contourConfiguration.HTTPProxy.OmitRouteSorting, }) // Build the core Kubernetes event handler. diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index 783ff7574f5..25d799097d7 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -619,6 +619,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be sorted + by Contour before being sent to Envoy or the original order + will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. @@ -4102,6 +4107,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be + sorted by Contour before being sent to Envoy or the original + order will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 2020135a384..52f02937f5a 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -838,6 +838,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be sorted + by Contour before being sent to Envoy or the original order + will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. @@ -4321,6 +4326,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be + sorted by Contour before being sent to Envoy or the original + order will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 4dc8215d1cb..180a92dc1cb 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -630,6 +630,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be sorted + by Contour before being sent to Envoy or the original order + will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. @@ -4113,6 +4118,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be + sorted by Contour before being sent to Envoy or the original + order will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 089e75b887d..11ac023ffd2 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -841,6 +841,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be sorted + by Contour before being sent to Envoy or the original order + will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. @@ -4324,6 +4329,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be + sorted by Contour before being sent to Envoy or the original + order will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 3f24568201b..8354538a739 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -838,6 +838,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be sorted + by Contour before being sent to Envoy or the original order + will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. @@ -4321,6 +4326,11 @@ spec: - name - namespace type: object + omitRouteSorting: + description: OmitRouteSorting determines if routes will be + sorted by Contour before being sent to Envoy or the original + order will be determined. Defaults to routing being sorted. + type: boolean rootNamespaces: description: Restrict Contour to searching these namespaces for root ingress routes. diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index 2c5bc508f24..1f88ae32bad 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -7625,6 +7625,20 @@

HTTPProxyConfig use as fallback when a non-SNI request is received.

+ + +omitRouteSorting +
+ +bool + + + +

OmitRouteSorting determines if routes will be sorted by Contour before +being sent to Envoy or the original order will be determined. +Defaults to routing being sorted.

+ +

HTTPVersionType From e4e6651f96bd643f5cb1512a6827a7754c1f46c7 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Fri, 27 Oct 2023 17:06:57 -0400 Subject: [PATCH 11/15] adds e2e integration test Signed-off-by: Sotiris Nanopoulos --- test/e2e/httpproxy/httpproxy_test.go | 14 ++- test/e2e/httpproxy/httpproxy_unsorted.go | 131 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 test/e2e/httpproxy/httpproxy_unsorted.go diff --git a/test/e2e/httpproxy/httpproxy_test.go b/test/e2e/httpproxy/httpproxy_test.go index 16d0bd2b335..2572884cc0e 100644 --- a/test/e2e/httpproxy/httpproxy_test.go +++ b/test/e2e/httpproxy/httpproxy_test.go @@ -529,7 +529,7 @@ descriptors: - key: customHeader rate_limit: unit: hour - requests_per_unit: 1 + requests_per_unit: 1 - key: anotherHeader rate_limit: unit: hour @@ -752,4 +752,16 @@ descriptors: f.NamespacedTest("httpproxy-global-ext-auth-tls-disabled", withGlobalExtAuth(testGlobalExternalAuthTLSAuthDisabled)) }) + f.NamespacedTest("httpproxy-omit-route-sorting", func(namespace string) { + Context("omit route sorting", func() { + BeforeEach(func() { + if !e2e.UsingContourConfigCRD() { + // Test only applies to contour config CRD. + Skip("") + } + contourConfiguration.Spec.HTTPProxy.OmitRouteSorting = true + }) + testOmitRouteSorting(namespace) + }) + }) }) diff --git a/test/e2e/httpproxy/httpproxy_unsorted.go b/test/e2e/httpproxy/httpproxy_unsorted.go new file mode 100644 index 00000000000..f1f88c54efb --- /dev/null +++ b/test/e2e/httpproxy/httpproxy_unsorted.go @@ -0,0 +1,131 @@ +// Copyright Project Contour Authors +// Licensed 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. + +//go:build e2e + +package httpproxy + +import ( + "net/http" + + . "github.com/onsi/ginkgo/v2" + contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/test/e2e" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func testOmitRouteSorting(namespace string) { + Specify("Path matching works", func() { + var ( + t = f.T() + serviceNamespace = namespace + ) + f.Fixtures.Echo.Deploy(serviceNamespace, "echo-1") + f.Fixtures.Echo.Deploy(serviceNamespace, "echo-2") + f.Fixtures.Echo.Deploy(serviceNamespace, "echo-3") + serviceProxy := &contour_api_v1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: serviceNamespace, + Name: "no-sorting", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + Fqdn: "sorting.projectcontour.io", + }, + Routes: []contour_api_v1.Route{ + { + Services: []contour_api_v1.Service{ + { + Name: "echo-1", + Port: 80, + }, + }, + Conditions: []contour_api_v1.MatchCondition{ + { + Header: &contour_api_v1.HeaderMatchCondition{ + Name: "x-experiment", + Exact: "bar", + }, + }, + }, + }, + { + Services: []contour_api_v1.Service{ + { + Name: "echo-2", + Port: 80, + }, + }, + Conditions: []contour_api_v1.MatchCondition{ + { + Exact: "/bar", + }, + }, + }, + { + Services: []contour_api_v1.Service{ + { + Name: "echo-3", + Port: 80, + }, + }, + Conditions: []contour_api_v1.MatchCondition{ + { + Prefix: "/", + }, + }, + }, + }, + }, + } + f.CreateHTTPProxyAndWaitFor(serviceProxy, e2e.HTTPProxyValid) + tests := []struct { + Path string + Headers map[string]string + wantSvc string + }{ + { + Path: "/bar", + Headers: map[string]string{"x-experiment": "bar"}, + wantSvc: "echo-1", + }, + { + Path: "/bar", + wantSvc: "echo-2", + }, + { + Path: "/", + wantSvc: "echo-3", + }, + } + for _, tt := range tests { + t.Logf("Querying path: %q, expecting service to be called: %q", tt.Path, tt.wantSvc) + + res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: serviceProxy.Spec.VirtualHost.Fqdn, + Path: tt.Path, + Condition: e2e.HasStatusCode(200), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(tt.Headers), + }, + }) + if !assert.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) { + continue + } + + assert.Equal(t, tt.wantSvc, f.GetEchoResponseBody(res.Body).Service) + } + }) +} From f9239056f5961a6e9384bae6393bed8b6ccac522 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Fri, 27 Oct 2023 17:18:37 -0400 Subject: [PATCH 12/15] fixup name Signed-off-by: Sotiris Nanopoulos --- internal/dag/httpproxy_processor.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 97a2b5facae..33c224757a6 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -1499,10 +1499,6 @@ func (p *HTTPProxyProcessor) GlobalAuthorizationContext() map[string]string { // | `/foo` | `/bar/` | `/foosball` | X `/bar/sball` | // | `/foo/` | `/bar/` | `/foo/type` | `/bar/type` | func expandPrefixMatches(routes []*Route) []*Route { - return expandPrefixMatchesOrdered(routes) -} - -func expandPrefixMatchesOrdered(routes []*Route) []*Route { expandedRoutes := []*Route{} prefixCount := make(map[string]int, 0) // First, we find the non expandable prefix routes. Those are two: From d706f85631b26aa778ace37af811aaace8aacfd3 Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 30 Oct 2023 12:37:43 -0400 Subject: [PATCH 13/15] fix golint Signed-off-by: Sotiris Nanopoulos --- .golangci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index c3c40bc5527..4f9fe5b22bf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -67,4 +67,6 @@ issues: - goimports - path: test/e2e linters: - - bodyclose \ No newline at end of file + - bodyclose + # Gikno which is used in e2e tests is dot imported + - revive From 6d4a4322e2dec9f6a1b96415a77e7f390e770f4f Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 30 Oct 2023 12:39:32 -0400 Subject: [PATCH 14/15] expose the parameter in contour config parameters Signed-off-by: Sotiris Nanopoulos --- pkg/config/parameters.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 10179cc1668..c2cae4ba968 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -675,6 +675,10 @@ type Parameters struct { // Tracing holds the relevant configuration for exporting trace data to OpenTelemetry. Tracing *Tracing `yaml:"tracing,omitempty"` + // OmitRouteSorting determines if routes will be sorted by Contour before being sent to Envoy or the original order + // will be determined. Defaults to routing being sorted. + OmitRouteSorting bool `yaml:"omitRouteSorting,omitempty"` + // FeatureFlags defines toggle to enable new contour features. // available toggles are // useEndpointSlices - configures contour to fetch endpoint data From 664068eecdb170a85ad0b44ad64c4a6f15399b4c Mon Sep 17 00:00:00 2001 From: Sotiris Nanopoulos Date: Mon, 30 Oct 2023 13:16:09 -0400 Subject: [PATCH 15/15] complete plumbing Signed-off-by: Sotiris Nanopoulos --- cmd/contour/servecontext.go | 1 + cmd/contour/servecontext_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 7d14c2a94f4..5a04a2288d3 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -583,6 +583,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha DisablePermitInsecure: &ctx.Config.DisablePermitInsecure, RootNamespaces: ctx.proxyRootNamespaces(), FallbackCertificate: fallbackCertificate, + OmitRouteSorting: ctx.Config.OmitRouteSorting, }, EnableExternalNameService: &ctx.Config.EnableExternalNameService, GlobalExternalAuthorization: globalExtAuth, diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index dd8d3bf681c..28166fe6f92 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -609,6 +609,7 @@ func TestConvertServeContext(t *testing.T) { "httpproxy": { getServeContext: func(ctx *serveContext) *serveContext { ctx.Config.DisablePermitInsecure = true + ctx.Config.OmitRouteSorting = true ctx.Config.TLS.FallbackCertificate = config.NamespacedName{ Name: "fallbackname", Namespace: "fallbacknamespace", @@ -622,6 +623,7 @@ func TestConvertServeContext(t *testing.T) { Name: "fallbackname", Namespace: "fallbacknamespace", }, + OmitRouteSorting: true, } return cfg },