Skip to content

Commit

Permalink
implement assigning priorities to Kong routes translated from `HTTPRo…
Browse files Browse the repository at this point in the history
…ute` (#4296)

* add traits

* split HTTPRoutes and assign priorities to the splitted

* add unit tests

* add CHANGELOG

* fix lint

* address comments: splitted->split

* address comments again

* use traits.encode for expected priorities in tests

* Update internal/dataplane/parser/translators/httproute_atc.go

Co-authored-by: Mattia Lavacca <lavacca.mattia@gmail.com>

---------

Co-authored-by: Mattia Lavacca <lavacca.mattia@gmail.com>
  • Loading branch information
randmonkey and mlavacca committed Jul 13, 2023
1 parent 5b4f886 commit ed49854
Show file tree
Hide file tree
Showing 6 changed files with 1,961 additions and 4 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ Adding a new version? You'll need three changes:
method is adopted to keep the compatibility with traditional router on
maximum effort.
[#4240](https://github.com/Kong/kubernetes-ingress-controller/pull/4240)
- Assign priorities to routes translated from HTTPRoutes when parser translates
them to expression based Kong routes. The assigning method follows the
[specification on priorities of matches in `HTTPRoute`][httproute-specification].
[#4296](https://github.com/Kong/kubernetes-ingress-controller/pull/4296)
- When a translated Kong configuration is empty in DB-less mode, the controller
will now send the configuration with a single empty `Upstream`. This is to make
Gateways using `/status/ready` as their health check ready after receiving the
Expand All @@ -121,6 +125,7 @@ Adding a new version? You'll need three changes:
[#4222](https://github.com/Kong/kubernetes-ingress-controller/pull/4222)

[gojson]: https://github.com/goccy/go-json
[httproute-specification]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute

## [2.10.3]

Expand Down
97 changes: 97 additions & 0 deletions internal/dataplane/parser/translate_httproute.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package parser

import (
"errors"
"fmt"
"strings"

"github.com/blang/semver/v4"
"github.com/bombsimon/logrusr/v2"
"github.com/kong/go-kong/kong"
"github.com/samber/lo"
k8stypes "k8s.io/apimachinery/pkg/types"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
Expand All @@ -29,6 +32,11 @@ func (p *Parser) ingressRulesFromHTTPRoutes() ingressRules {
return result
}

if p.featureFlags.ExpressionRoutes {
p.ingressRulesFromHTTPRoutesUsingExpressionRoutes(httpRouteList, &result)
return result
}

for _, httproute := range httpRouteList {
if err := p.ingressRulesFromHTTPRoute(&result, httproute); err != nil {
p.registerTranslationFailure(fmt.Sprintf("HTTPRoute can't be routed: %s", err), httproute)
Expand Down Expand Up @@ -68,6 +76,54 @@ func validateHTTPRoute(httproute *gatewayv1beta1.HTTPRoute) error {
return nil
}

// ingressRulesFromHTTPRoutesUsingExpressionRoutes translates HTTPRoutes to expression based routes
// when ExpressionRoutes feature flag is enabled.
// Because we need to assign different priorities based on the hostname and match in the specification of HTTPRoutes,
// We need to split the HTTPRoutes into ones with only one hostname and one match, then assign priority to them
// and finally translate the split HTTPRoutes into Kong services and routes with assigned priorities.
func (p *Parser) ingressRulesFromHTTPRoutesUsingExpressionRoutes(httpRoutes []*gatewayv1beta1.HTTPRoute, result *ingressRules) {
// first, split HTTPRoutes by hostnames and matches.
splitHTTPRoutes := []*gatewayv1beta1.HTTPRoute{}
for _, httproute := range httpRoutes {
if err := validateHTTPRoute(httproute); err != nil {
p.registerTranslationFailure(fmt.Sprintf("HTTPRoute can't be routed: %s", err), httproute)
continue
}
splitHTTPRoutes = append(splitHTTPRoutes, translators.SplitHTTPRoute(httproute)...)
}
// assign priorities to split HTTPRoutes.
splitHTTPRoutesWithPriorities := translators.AssignRoutePriorityToSplitHTTPRoutes(logrusr.New(p.logger), splitHTTPRoutes)
httpRouteNameToTranslationFailure := map[k8stypes.NamespacedName][]error{}

// translate split HTTPRoutes to ingress rules, including services, routes, upstreams.
for _, httpRouteWithPriority := range splitHTTPRoutesWithPriorities {
err := p.ingressRulesFromSplitHTTPRouteWithPriority(result, httpRouteWithPriority)
if err != nil {
nsName := k8stypes.NamespacedName{
Namespace: httpRouteWithPriority.HTTPRoute.Namespace,
Name: httpRouteWithPriority.HTTPRoute.Name,
}
httpRouteNameToTranslationFailure[nsName] = append(httpRouteNameToTranslationFailure[nsName], err)
}
}
// Register successful parsed objects and translation failures.
// Because one HTTPRoute may be split into multiple HTTPRoutes, we need to de-duplicate by namespace and name.
for _, httproute := range httpRoutes {
nsName := k8stypes.NamespacedName{
Namespace: httproute.Namespace,
Name: httproute.Name,
}
if translationFailures, ok := httpRouteNameToTranslationFailure[nsName]; ok {
p.registerTranslationFailure(
fmt.Sprintf("HTTPRoute can't be routed: %v", errors.Join(translationFailures...)),
httproute,
)
continue
}
p.registerSuccessfullyParsedObject(httproute)
}
}

// ingressRulesFromHTTPRouteWithCombinedServiceRoutes generates a set of proto-Kong routes (ingress rules) from an HTTPRoute.
// If multiple rules in the HTTPRoute use the same Service, it combines them into a single Kong route.
func (p *Parser) ingressRulesFromHTTPRouteWithCombinedServiceRoutes(httproute *gatewayv1beta1.HTTPRoute, result *ingressRules) error {
Expand Down Expand Up @@ -470,3 +526,44 @@ func httpBackendRefsToBackendRefs(httpBackendRef []gatewayv1beta1.HTTPBackendRef
}
return backendRefs
}

func (p *Parser) ingressRulesFromSplitHTTPRouteWithPriority(
rules *ingressRules,
httpRouteWithPriority translators.SplitHTTPRouteToKongRoutePriority,
) error {
httpRoute := httpRouteWithPriority.HTTPRoute
if len(httpRoute.Spec.Rules) == 0 {
return translators.ErrRouteValidationNoRules
}

httpRouteRule := httpRoute.Spec.Rules[0]
if len(httpRoute.Spec.Hostnames) == 0 && len(httpRouteRule.Matches) == 0 {
return translators.ErrRouteValidationNoMatchRulesOrHostnamesSpecified
}

backendRefs := httpBackendRefsToBackendRefs(httpRouteRule.BackendRefs)

serviceName := translators.KongServiceNameFromHTTPRouteWithPriority(httpRouteWithPriority)

kongService, err := generateKongServiceFromBackendRefWithName(
p.logger,
p.storer,
rules,
serviceName,
httpRoute,
"http",
backendRefs...,
)
if err != nil {
return err
}

kongService.Routes = append(
kongService.Routes,
translators.KongExpressionRouteFromHTTPRouteWithPriority(httpRouteWithPriority),
)
// cache the service to avoid duplicates in further loop iterations
rules.ServiceNameToServices[serviceName] = kongService
rules.ServiceNameToParent[serviceName] = httpRoute
return nil
}
Loading

0 comments on commit ed49854

Please sign in to comment.