diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index 4a55f556187..e61d28bb546 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -55,6 +55,36 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, } + kuardService2 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kuard2", + Namespace: "projectcontour", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Name: "http", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), + }}, + }, + } + + kuardService3 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kuard3", + Namespace: "projectcontour", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Name: "http", + Protocol: "TCP", + Port: 8080, + TargetPort: intstr.FromInt(8080), + }}, + }, + } + kuardServiceCustomNs := &v1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "kuard", @@ -276,6 +306,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("kuard"), Port: gatewayPort(8080), + Weight: 1, }}, }}, }, @@ -302,10 +333,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Value: "/", }, }}, - ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ - ServiceName: pointer.StringPtr("blogsvc"), - Port: gatewayPort(80), - }}, + ForwardTo: httpRouteForwardTo("blogsvc", 80, 1), }}, }, } @@ -447,7 +475,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -463,7 +491,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -484,7 +512,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -493,7 +521,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -513,7 +541,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -535,7 +563,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -544,7 +572,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardServiceCustomNs))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), ), }, ), @@ -581,6 +609,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("kuard"), Port: gatewayPort(8080), + Weight: 1, }}, }}, }, @@ -590,7 +619,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardServiceCustomNs))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardServiceCustomNs))), ), }, ), @@ -619,7 +648,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -646,7 +675,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -687,7 +716,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -729,7 +758,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -756,10 +785,10 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }, { Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/blog"), - ForwardTo: httpRouteForwardTo("blogsvc", 80), + ForwardTo: httpRouteForwardTo("blogsvc", 80, 1), }}, }, }, @@ -769,7 +798,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Port: 80, VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - prefixroute("/", service(kuardService)), prefixroute("/blog", service(blogService))), + prefixrouteHTTPRoute("/", service(kuardService)), prefixrouteHTTPRoute("/blog", service(blogService))), ), }, ), @@ -796,7 +825,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -805,10 +834,10 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardService))), - virtualhost("test2.projectcontour.io", prefixroute("/", service(kuardService))), - virtualhost("test3.projectcontour.io", prefixroute("/", service(kuardService))), - virtualhost("test4.projectcontour.io", prefixroute("/", service(kuardService))), + 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))), ), }, ), @@ -829,7 +858,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -838,7 +867,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("*", prefixroute("/", service(kuardService))), + virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -862,7 +891,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -871,7 +900,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("*.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("*.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -895,7 +924,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -921,7 +950,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -947,7 +976,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -971,7 +1000,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1004,6 +1033,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("kuard"), Port: nil, + Weight: 1, }}, }}, }, @@ -1030,7 +1060,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchExact, "/blog"), - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1040,7 +1070,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Port: 80, VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - exactroute("/blog", service(kuardService))), + exactrouteHTTPRoute("/blog", service(kuardService))), ), }, ), @@ -1080,7 +1110,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Value: "/tech", }, }}, - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1090,9 +1120,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Port: 80, VirtualHosts: virtualhosts( virtualhost("test.projectcontour.io", - prefixroute("/", service(kuardService)), - prefixroute("/blog", service(kuardService)), - prefixroute("/tech", service(kuardService))), + prefixrouteHTTPRoute("/", service(kuardService)), + prefixrouteHTTPRoute("/blog", service(kuardService)), + prefixrouteHTTPRoute("/tech", service(kuardService))), ), }, ), @@ -1112,7 +1142,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHost: VirtualHost{ Name: "test.projectcontour.io", ListenerName: "ingress_https", - routes: routes(prefixroute("/", service(kuardService))), + routes: routes(prefixrouteHTTPRoute("/", service(kuardService))), }, Secret: secret(sec1), }, @@ -1159,7 +1189,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHost: VirtualHost{ Name: "test.projectcontour.io", ListenerName: "ingress_https", - routes: routes(prefixroute("/", service(blogService))), + routes: routes(prefixrouteHTTPRoute("/", service(blogService))), }, Secret: secret(sec1), }, @@ -1168,7 +1198,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(blogService))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(blogService))), ), }, ), @@ -1317,7 +1347,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHost: VirtualHost{ Name: "test.projectcontour.io", ListenerName: "ingress_https", - routes: routes(prefixroute("/", service(blogService))), + routes: routes(prefixrouteHTTPRoute("/", service(blogService))), }, Secret: secret(sec1), }, @@ -1326,7 +1356,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { &Listener{ Port: 80, VirtualHosts: virtualhosts( - virtualhost("test.projectcontour.io", prefixroute("/", service(kuardService))), + virtualhost("test.projectcontour.io", prefixrouteHTTPRoute("/", service(kuardService))), ), }, ), @@ -1359,7 +1389,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Values: map[string]string{"foo": "bar"}, }, }}, - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1373,7 +1403,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { HeaderMatchConditions: []HeaderMatchCondition{ {Name: "foo", Value: "bar", MatchType: "exact", Invert: false}, }, - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), }), ), }, @@ -1415,7 +1445,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, }, }, - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1426,14 +1456,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", &Route{ PathMatchCondition: prefixString("/blog"), - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), }, &Route{ PathMatchCondition: prefixString("/tech"), HeaderMatchConditions: []HeaderMatchCondition{ {Name: "foo", Value: "bar", MatchType: "exact", Invert: false}, }, - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), }, )), }, @@ -1463,7 +1493,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Values: map[string]string{"foo": "bar"}, }, }}, - ForwardTo: httpRouteForwardTo("kuard", 8080), + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), }}, }, }, @@ -1477,7 +1507,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { HeaderMatchConditions: []HeaderMatchCondition{ {Name: "foo", Value: "bar", MatchType: "exact", Invert: false}, }, - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), }, )), }, @@ -1510,10 +1540,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, }, }, - ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ - ServiceName: pointer.StringPtr("kuard"), - Port: gatewayPort(8080), - }}, + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), Filters: []gatewayapi_v1alpha1.HTTPRouteFilter{{ Type: gatewayapi_v1alpha1.HTTPRouteFilterRequestHeaderModifier, RequestHeaderModifier: &gatewayapi_v1alpha1.HTTPRequestHeaderFilter{ @@ -1531,7 +1558,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", &Route{ PathMatchCondition: prefixString("/"), - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), RequestHeadersPolicy: &HeadersPolicy{ Set: map[string]string{ "Custom-Header-Set": "foo-bar", // Verify the header key is canonicalized. @@ -1583,6 +1610,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { Add: map[string]string{"custom-header-add": "foo-bar"}, }, }}, + Weight: 1, }}, }}, }, @@ -1627,10 +1655,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, }, }, - ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ - ServiceName: pointer.StringPtr("kuard"), - Port: gatewayPort(8080), - }}, + ForwardTo: httpRouteForwardTo("kuard", 8080, 1), Filters: []gatewayapi_v1alpha1.HTTPRouteFilter{{ Type: gatewayapi_v1alpha1.HTTPRouteFilterRequestHeaderModifier, RequestHeaderModifier: &gatewayapi_v1alpha1.HTTPRequestHeaderFilter{ @@ -1648,7 +1673,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io", &Route{ PathMatchCondition: prefixString("/"), - Clusters: clusters(service(kuardService)), + Clusters: clustersWeight(service(kuardService)), RequestHeadersPolicy: &HeadersPolicy{ Set: map[string]string{"Custom-Header-Set": "foo-bar"}, Add: map[string]string{}, // Invalid header should not be set. @@ -1689,6 +1714,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("kuard"), Port: gatewayPort(8080), + Weight: 1, Filters: []gatewayapi_v1alpha1.HTTPRouteFilter{{ Type: gatewayapi_v1alpha1.HTTPRouteFilterRequestHeaderModifier, RequestHeaderModifier: &gatewayapi_v1alpha1.HTTPRequestHeaderFilter{ @@ -1713,6 +1739,193 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, + "different weights for multiple forwardTos": { + gateway: gatewayWithSelector, + objs: []interface{}{ + kuardService, + kuardService2, + kuardService3, + &gatewayapi_v1alpha1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + Labels: map[string]string{ + "app": "contour", + "type": "controller", + }, + }, + Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ + Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ + Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), + ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{ + { + ServiceName: pointer.StringPtr("kuard"), + Port: gatewayPort(8080), + Weight: 5, + }, { + ServiceName: pointer.StringPtr("kuard2"), + Port: gatewayPort(8080), + Weight: 10, + }, + { + ServiceName: pointer.StringPtr("kuard3"), + Port: gatewayPort(8080), + Weight: 15, + }, + }, + }}, + }, + }, + }, + want: listeners( + &Listener{ + Port: 80, + VirtualHosts: virtualhosts( + virtualhost("*", prefixrouteHTTPRoute("/", + &Service{ + Weighted: WeightedService{ + Weight: 5, + ServiceName: kuardService.Name, + ServiceNamespace: kuardService.Namespace, + ServicePort: kuardService.Spec.Ports[0], + }, + }, + &Service{ + Weighted: WeightedService{ + Weight: 10, + ServiceName: kuardService2.Name, + ServiceNamespace: kuardService2.Namespace, + ServicePort: kuardService2.Spec.Ports[0], + }, + }, + &Service{ + Weighted: WeightedService{ + Weight: 15, + ServiceName: kuardService3.Name, + ServiceNamespace: kuardService3.Namespace, + ServicePort: kuardService3.Spec.Ports[0], + }, + }, + )), + ), + }, + ), + }, + "one service weight zero w/weights for other forwardTos": { + gateway: gatewayWithSelector, + objs: []interface{}{ + kuardService, + kuardService2, + kuardService3, + &gatewayapi_v1alpha1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + Labels: map[string]string{ + "app": "contour", + "type": "controller", + }, + }, + Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ + Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ + Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), + ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{ + { + ServiceName: pointer.StringPtr("kuard"), + Port: gatewayPort(8080), + Weight: 5, + }, { + ServiceName: pointer.StringPtr("kuard2"), + Port: gatewayPort(8080), + Weight: 0, + }, + { + ServiceName: pointer.StringPtr("kuard3"), + Port: gatewayPort(8080), + Weight: 15, + }, + }, + }}, + }, + }, + }, + want: listeners( + &Listener{ + Port: 80, + VirtualHosts: virtualhosts( + virtualhost("*", prefixrouteHTTPRoute("/", + &Service{ + Weighted: WeightedService{ + Weight: 5, + ServiceName: kuardService.Name, + ServiceNamespace: kuardService.Namespace, + ServicePort: kuardService.Spec.Ports[0], + }, + }, + &Service{ + Weighted: WeightedService{ + Weight: 0, + ServiceName: kuardService2.Name, + ServiceNamespace: kuardService2.Namespace, + ServicePort: kuardService2.Spec.Ports[0], + }, + }, + &Service{ + Weighted: WeightedService{ + Weight: 15, + ServiceName: kuardService3.Name, + ServiceNamespace: kuardService3.Namespace, + ServicePort: kuardService3.Spec.Ports[0], + }, + }, + )), + ), + }, + ), + }, + "weight of zero for a single forwardTo results in 503": { + gateway: gatewayWithSelector, + objs: []interface{}{ + kuardService, + kuardService2, + kuardService3, + &gatewayapi_v1alpha1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "projectcontour", + Labels: map[string]string{ + "app": "contour", + "type": "controller", + }, + }, + Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ + Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ + Matches: httpRouteMatch(gatewayapi_v1alpha1.PathMatchPrefix, "/"), + ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ + ServiceName: pointer.StringPtr("kuard"), + Port: gatewayPort(8080), + Weight: 0, + }}, + }}, + }, + }, + }, + want: listeners( + &Listener{ + Port: 80, + VirtualHosts: virtualhosts( + virtualhost("*", directResponseRouteService("/", http.StatusServiceUnavailable, &Service{ + Weighted: WeightedService{ + Weight: 0, + ServiceName: kuardService.Name, + ServiceNamespace: kuardService.Namespace, + ServicePort: kuardService.Spec.Ports[0], + }, + })), + ), + }, + ), + }, } for name, tc := range tests { @@ -10266,6 +10479,15 @@ func directResponseRoute(prefix string, statusCode uint32) *Route { } } +func directResponseRouteService(prefix string, statusCode uint32, first *Service, rest ...*Service) *Route { + services := append([]*Service{first}, rest...) + return &Route{ + PathMatchCondition: prefixString(prefix), + DirectResponse: &DirectResponse{StatusCode: statusCode}, + Clusters: clustersWeight(services...), + } +} + func httpRouteMatch(pathType gatewayapi_v1alpha1.PathMatchType, value string) []gatewayapi_v1alpha1.HTTPRouteMatch { return []gatewayapi_v1alpha1.HTTPRouteMatch{{ Path: gatewayapi_v1alpha1.HTTPPathMatch{ @@ -10275,10 +10497,11 @@ func httpRouteMatch(pathType gatewayapi_v1alpha1.PathMatchType, value string) [] }} } -func httpRouteForwardTo(serviceName string, port int) []gatewayapi_v1alpha1.HTTPRouteForwardTo { +func httpRouteForwardTo(serviceName string, port int, weight int32) []gatewayapi_v1alpha1.HTTPRouteForwardTo { return []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr(serviceName), Port: gatewayPort(port), + Weight: weight, }} } @@ -10290,11 +10513,19 @@ func prefixroute(prefix string, first *Service, rest ...*Service) *Route { } } -func exactroute(path string, first *Service, rest ...*Service) *Route { +func prefixrouteHTTPRoute(prefix string, first *Service, rest ...*Service) *Route { + services := append([]*Service{first}, rest...) + return &Route{ + PathMatchCondition: prefixString(prefix), + Clusters: clustersWeight(services...), + } +} + +func exactrouteHTTPRoute(path string, first *Service, rest ...*Service) *Route { services := append([]*Service{first}, rest...) return &Route{ PathMatchCondition: &ExactMatchCondition{Path: path}, - Clusters: clusters(services...), + Clusters: clustersWeight(services...), } } @@ -10354,6 +10585,7 @@ func clusterHeaders(requestSet map[string]string, requestAdd map[string]string, Remove: requestRemove, HostRewrite: hostRewrite, }, + Weight: s.Weighted.Weight, }) } return c @@ -10369,6 +10601,17 @@ func clusters(services ...*Service) (c []*Cluster) { return c } +func clustersWeight(services ...*Service) (c []*Cluster) { + for _, s := range services { + c = append(c, &Cluster{ + Upstream: s, + Protocol: s.Protocol, + Weight: s.Weighted.Weight, + }) + } + return c +} + func service(s *v1.Service) *Service { return &Service{ Weighted: WeightedService{ diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index acc68462729..d6335cbd4a4 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -309,6 +309,7 @@ func (p *GatewayAPIProcessor) computeHTTPRoute(route *gatewayapi_v1alpha1.HTTPRo var clusters []*Cluster // Validate the ForwardTos. + totalWeight := int32(0) for _, forward := range rule.ForwardTo { // Verify the service is valid @@ -346,8 +347,13 @@ func (p *GatewayAPIProcessor) computeHTTPRoute(route *gatewayapi_v1alpha1.HTTPRo } } - cluster := p.cluster(headerPolicy, service) - clusters = append(clusters, cluster) + // Keep track of all the weights for this set of forwardTos. This will be + // used later to understand if all the weights are set to zero. + totalWeight += forward.Weight + + // https://github.com/projectcontour/contour/issues/3593 + service.Weighted.Weight = uint32(forward.Weight) + clusters = append(clusters, p.cluster(headerPolicy, service, uint32(forward.Weight))) } var headerPolicy *HeadersPolicy @@ -367,8 +373,9 @@ func (p *GatewayAPIProcessor) computeHTTPRoute(route *gatewayapi_v1alpha1.HTTPRo routes := p.routes(matchconditions, headerPolicy, clusters) for _, host := range hosts { for _, route := range routes { - - if len(clusters) == 0 { + // If there aren't any valid services, or the total weight of all of + // them equal zero, then return 503 responses to the caller. + if len(clusters) == 0 || totalWeight == 0 { // Configure a direct response HTTP status code of 503 so the // route still matches the configured conditions since the // service is missing or invalid. @@ -419,9 +426,10 @@ func (p *GatewayAPIProcessor) routes(matchConditions []*matchConditions, headerP } // cluster builds a *dag.Cluster for the supplied set of headerPolicy and service. -func (p *GatewayAPIProcessor) cluster(headerPolicy *HeadersPolicy, service *Service) *Cluster { +func (p *GatewayAPIProcessor) cluster(headerPolicy *HeadersPolicy, service *Service, weight uint32) *Cluster { return &Cluster{ Upstream: service, + Weight: weight, Protocol: service.Protocol, RequestHeadersPolicy: headerPolicy, } diff --git a/internal/featuretests/v3/gatewayapi_test.go b/internal/featuretests/v3/gatewayapi_test.go index c2c69c940d2..93eb15f80b2 100644 --- a/internal/featuretests/v3/gatewayapi_test.go +++ b/internal/featuretests/v3/gatewayapi_test.go @@ -17,12 +17,12 @@ import ( "testing" envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" - - "github.com/projectcontour/contour/internal/dag" - envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" + + "github.com/projectcontour/contour/internal/dag" + "github.com/projectcontour/contour/internal/featuretests" "github.com/projectcontour/contour/internal/fixture" v1 "k8s.io/api/core/v1" @@ -32,6 +32,41 @@ import ( gatewayapi_v1alpha1 "sigs.k8s.io/gateway-api/apis/v1alpha1" ) +var gateway = &gatewayapi_v1alpha1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "contour", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha1.GatewaySpec{ + Listeners: []gatewayapi_v1alpha1.Listener{{ + Port: 80, + Protocol: "HTTP", + Routes: gatewayapi_v1alpha1.RouteBindingSelector{ + Namespaces: gatewayapi_v1alpha1.RouteNamespaces{ + From: gatewayapi_v1alpha1.RouteSelectAll, + }, + Kind: dag.KindHTTPRoute, + }, + }, { + Port: 443, + Protocol: "HTTPS", + TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{ + CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{ + Group: "core", + Kind: "Secret", + Name: "tlscert", + }, + }, + Routes: gatewayapi_v1alpha1.RouteBindingSelector{ + Namespaces: gatewayapi_v1alpha1.RouteNamespaces{ + From: gatewayapi_v1alpha1.RouteSelectAll, + }, + Kind: dag.KindHTTPRoute, + }, + }}, + }, +} + func TestGateway_TLS(t *testing.T) { rh, c, done := setup(t) defer done() @@ -55,40 +90,7 @@ func TestGateway_TLS(t *testing.T) { rh.OnAdd(sec1) - rh.OnAdd(&gatewayapi_v1alpha1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "contour", - Namespace: "projectcontour", - }, - Spec: gatewayapi_v1alpha1.GatewaySpec{ - Listeners: []gatewayapi_v1alpha1.Listener{{ - Port: 80, - Protocol: "HTTP", - Routes: gatewayapi_v1alpha1.RouteBindingSelector{ - Namespaces: gatewayapi_v1alpha1.RouteNamespaces{ - From: gatewayapi_v1alpha1.RouteSelectAll, - }, - Kind: dag.KindHTTPRoute, - }, - }, { - Port: 443, - Protocol: "HTTPS", - TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{ - CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{ - Group: "core", - Kind: "Secret", - Name: "tlscert", - }, - }, - Routes: gatewayapi_v1alpha1.RouteBindingSelector{ - Namespaces: gatewayapi_v1alpha1.RouteNamespaces{ - From: gatewayapi_v1alpha1.RouteSelectAll, - }, - Kind: dag.KindHTTPRoute, - }, - }}, - }, - }) + rh.OnAdd(gateway) rh.OnAdd(&gatewayapi_v1alpha1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ @@ -113,6 +115,7 @@ func TestGateway_TLS(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("svc2"), Port: gatewayPort(80), + Weight: 1, }}, }, { Matches: []gatewayapi_v1alpha1.HTTPRouteMatch{{ @@ -124,6 +127,7 @@ func TestGateway_TLS(t *testing.T) { ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ ServiceName: pointer.StringPtr("svc1"), Port: gatewayPort(80), + Weight: 10, }}, }}, }, diff --git a/internal/featuretests/v3/routeweight_test.go b/internal/featuretests/v3/routeweight_test.go index c69954f68fe..e158f88bd66 100644 --- a/internal/featuretests/v3/routeweight_test.go +++ b/internal/featuretests/v3/routeweight_test.go @@ -17,6 +17,10 @@ package v3 import ( "testing" + "github.com/projectcontour/contour/internal/dag" + "k8s.io/utils/pointer" + gatewayapi_v1alpha1 "sigs.k8s.io/gateway-api/apis/v1alpha1" + envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" @@ -32,7 +36,7 @@ type weightedcluster struct { weight uint32 } -func TestHTTPProxyRouteWithAServiceWeight(t *testing.T) { +func TestHTTPProxy_RouteWithAServiceWeight(t *testing.T) { rh, c, done := setup(t) defer done() @@ -102,6 +106,123 @@ func TestHTTPProxyRouteWithAServiceWeight(t *testing.T) { ), nil) } +func TestHTTPRoute_RouteWithAServiceWeight(t *testing.T) { + rh, c, done := setup(t) + defer done() + + rh.OnAdd(fixture.NewService("svc1"). + WithPorts(v1.ServicePort{Port: 80, TargetPort: intstr.FromInt(8080)})) + + rh.OnAdd(fixture.NewService("svc2"). + WithPorts(v1.ServicePort{Port: 80, TargetPort: intstr.FromInt(8080)})) + + rh.OnAdd(&gatewayapi_v1alpha1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "contour", + Namespace: "projectcontour", + }, + Spec: gatewayapi_v1alpha1.GatewaySpec{ + Listeners: []gatewayapi_v1alpha1.Listener{{ + Port: 80, + Protocol: "HTTP", + Routes: gatewayapi_v1alpha1.RouteBindingSelector{ + Namespaces: gatewayapi_v1alpha1.RouteNamespaces{ + From: gatewayapi_v1alpha1.RouteSelectAll, + }, + Kind: dag.KindHTTPRoute, + }, + }}, + }, + }) + + // HTTPRoute with a single weight. + route1 := &gatewayapi_v1alpha1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "default", + Labels: map[string]string{ + "app": "contour", + "type": "controller", + }, + }, + Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ + Hostnames: []gatewayapi_v1alpha1.Hostname{ + "test.projectcontour.io", + }, + Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ + Matches: []gatewayapi_v1alpha1.HTTPRouteMatch{{ + Path: gatewayapi_v1alpha1.HTTPPathMatch{ + Type: "Prefix", + Value: "/blog", + }, + }}, + ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ + ServiceName: pointer.StringPtr("svc1"), + Port: gatewayPort(80), + Weight: 1, + }}, + }}, + }, + } + + rh.OnAdd(route1) + + assertRDS(t, c, "1", virtualhosts( + envoy_v3.VirtualHost("test.projectcontour.io", + &envoy_route_v3.Route{ + Match: routePrefix("/blog"), + Action: routecluster("default/svc1/80/da39a3ee5e"), + }, + ), + ), nil) + + // HTTPRoute with multiple weights. + route2 := &gatewayapi_v1alpha1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basic", + Namespace: "default", + Labels: map[string]string{ + "app": "contour", + "type": "controller", + }, + }, + Spec: gatewayapi_v1alpha1.HTTPRouteSpec{ + Hostnames: []gatewayapi_v1alpha1.Hostname{ + "test.projectcontour.io", + }, + Rules: []gatewayapi_v1alpha1.HTTPRouteRule{{ + Matches: []gatewayapi_v1alpha1.HTTPRouteMatch{{ + Path: gatewayapi_v1alpha1.HTTPPathMatch{ + Type: "Prefix", + Value: "/blog", + }, + }}, + ForwardTo: []gatewayapi_v1alpha1.HTTPRouteForwardTo{{ + ServiceName: pointer.StringPtr("svc1"), + Port: gatewayPort(80), + Weight: 60, + }, { + ServiceName: pointer.StringPtr("svc2"), + Port: gatewayPort(80), + Weight: 90, + }}, + }}, + }, + } + + rh.OnUpdate(route1, route2) + assertRDS(t, c, "2", virtualhosts( + envoy_v3.VirtualHost("test.projectcontour.io", + &envoy_route_v3.Route{ + Match: routePrefix("/blog"), + Action: routeweightedcluster( + weightedcluster{"default/svc1/80/da39a3ee5e", 60}, + weightedcluster{"default/svc2/80/da39a3ee5e", 90}), + }, + ), + ), nil) +} + func routeweightedcluster(clusters ...weightedcluster) *envoy_route_v3.Route_Route { return &envoy_route_v3.Route_Route{ Route: &envoy_route_v3.RouteAction{