Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement assigning priorities to Kong routes translated from HTTPRoute #4296

Merged
merged 10 commits into from
Jul 13, 2023
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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 @@ -120,6 +124,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.2]

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
Loading