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

feat(translator): combine services across httproutes expression router #6766

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Adding a new version? You'll need three changes:
where `<hash>` is the hash result of the calculated name, like
`httproute.default.svc.default.a-long-long-long-service-name.80_combined.00001111222233334444aaaabbbbcccc`.
[#6711](https://github.com/Kong/kubernetes-ingress-controller/pull/6711)
[#6766](https://github.com/Kong/kubernetes-ingress-controller/pull/6766)
- The new tag `k8s-named-route-rule` is added to a Kong Route, in the case when mapped `HTTPRoute`, `GRPCRoute`,
`TCPRoute`, `TLSRoute` or `UDPRoute` has one or many route rules named (filled `spec.rules[*].name` field),
those names will be propagated to one or many instances of aforementioned tag.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httproute-testing._.3
id: 23683d37-44ee-559b-add2-a0b650ad36c0
name: httproute.default.httproute-testing._.3
host: httproute.default.httproute-testing.3
id: efb4a7d7-791f-504e-b005-0bd38bf1435e
name: httproute.default.httproute-testing.3
port: 8080
protocol: http
read_timeout: 60000
Expand All @@ -29,9 +29,9 @@ services:
- k8s-version:v1
write_timeout: 60000
- connect_timeout: 60000
host: httproute.default.httproute-testing._.2
id: ff054481-b58a-588f-a8c4-e6cedb6cb489
name: httproute.default.httproute-testing._.2
host: httproute.default.httproute-testing.1
id: 672dc964-a7f8-5a80-a518-96fc39500cc8
name: httproute.default.httproute-testing.1
port: 80
protocol: http
read_timeout: 60000
Expand All @@ -51,21 +51,6 @@ services:
- k8s-group:gateway.networking.k8s.io
- k8s-version:v1
- k8s-named-route-rule:content
tags:
- k8s-name:httpbin
- k8s-namespace:default
- k8s-kind:Service
- k8s-version:v1
write_timeout: 60000
- connect_timeout: 60000
host: httproute.default.httproute-testing._.1
id: 13be00e6-9c94-561f-9598-8896cd755468
name: httproute.default.httproute-testing._.1
port: 80
protocol: http
read_timeout: 60000
retries: 5
routes:
- expression: (http.path == "/echo") || (http.path ^= "/echo/")
https_redirect_status_code: 426
id: 88d36cfe-fbb0-5d7a-93c1-df18d1db3a12
Expand All @@ -87,9 +72,9 @@ services:
- k8s-version:v1
write_timeout: 60000
- connect_timeout: 60000
host: httproute.default.httproute-testing._.0
id: 2fad71d1-7599-5c6e-9d4f-4afd44f99587
name: httproute.default.httproute-testing._.0
host: httproute.default.httproute-testing.0
id: 4e3cb785-a8d0-5866-aa05-117f7c64f24d
name: httproute.default.httproute-testing.0
port: 8080
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -118,28 +103,21 @@ services:
write_timeout: 60000
upstreams:
- algorithm: round-robin
name: httproute.default.httproute-testing._.3
name: httproute.default.httproute-testing.3
tags:
- k8s-name:nginx
- k8s-namespace:default
- k8s-kind:Service
- k8s-version:v1
- algorithm: round-robin
name: httproute.default.httproute-testing._.2
tags:
- k8s-name:httpbin
- k8s-namespace:default
- k8s-kind:Service
- k8s-version:v1
- algorithm: round-robin
name: httproute.default.httproute-testing._.1
name: httproute.default.httproute-testing.1
tags:
- k8s-name:httpbin
- k8s-namespace:default
- k8s-kind:Service
- k8s-version:v1
- algorithm: round-robin
name: httproute.default.httproute-testing._.0
name: httproute.default.httproute-testing.0
tags:
- k8s-name:httproute-testing
- k8s-namespace:default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httproute-testing._.0
id: 2fad71d1-7599-5c6e-9d4f-4afd44f99587
name: httproute.default.httproute-testing._.0
host: httproute.default.httproute-testing.0
id: 4e3cb785-a8d0-5866-aa05-117f7c64f24d
name: httproute.default.httproute-testing.0
port: 8080
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -42,7 +42,7 @@ services:
write_timeout: 60000
upstreams:
- algorithm: round-robin
name: httproute.default.httproute-testing._.0
name: httproute.default.httproute-testing.0
tags:
- k8s-name:httproute-testing
- k8s-namespace:default
Expand Down
57 changes: 49 additions & 8 deletions internal/dataplane/translator/subtranslator/httproute.go
randmonkey marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,52 @@ type HTTPRoutesTranslationResult struct {
HTTPRouteNameToTranslationErrors map[k8stypes.NamespacedName][]error
}

type splitHTTPRouteMatchesWithPrioritiesGroupedByRule map[string][]SplitHTTPRouteMatchToKongRoutePriority

// TranslateHTTPRoutesToKongstateServices translates a set of HTTPRoutes to kongstate services,
// and collect the translation errors in the process of translating.
func TranslateHTTPRoutesToKongstateServices(
logger logr.Logger,
storer store.Storer,
routes []*gatewayapi.HTTPRoute,
combinedServicesFromDifferentHTTPRoutes bool,
expressionRoutes bool,
) HTTPRoutesTranslationResult {
serviceNameToRules := groupRulesFromHTTPRoutesByKongServiceName(routes, combinedServicesFromDifferentHTTPRoutes)

// When feature flag expression routes is enabled, we need first split the matches and assign priorities to them
// to set proper priorities to the translated Kong routes for satisfying the specification of priorities of HTTPRoute matches:
// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule
var ruleToSplitMatchesWithPriorities splitHTTPRouteMatchesWithPrioritiesGroupedByRule
if expressionRoutes {
ruleToSplitMatchesWithPriorities = groupHTTPRouteMatchesWithPrioritiesByRule(logger, routes)
}

kongstateServiceCache := map[string]kongstate.Service{}
routeTranslationErrors := map[k8stypes.NamespacedName][]error{}
for serviceName, rulesMeta := range serviceNameToRules {
if len(rulesMeta) == 0 {
continue
}
service, err := translateHTTPRouteRulesMetaToKongstateService(logger, storer, serviceName, rulesMeta)

// Sort the rules according to the namespace, name and rule number to guarantee a fixed order.
sort.SliceStable(rulesMeta, func(i, j int) bool {
return rulesMeta[i].getRuleKey() < rulesMeta[j].getRuleKey()
})

var matchesWithPriorities []SplitHTTPRouteMatchToKongRoutePriority
if expressionRoutes {
for _, ruleMeta := range rulesMeta {
ruleKey := ruleMeta.getRuleKey()
matchesWithPriorities = append(matchesWithPriorities, ruleToSplitMatchesWithPriorities[ruleKey]...)
}
}
service, err := translateHTTPRouteRulesMetaToKongstateService(
logger, storer, serviceName, rulesMeta,
expressionRoutes,
matchesWithPriorities,
)
// Set translation errors for involved HTTPRoutes on failure of translation.
if err != nil {
httpRoutes := extractUniqueHTTPRoutes(rulesMeta)
for _, route := range httpRoutes {
Expand All @@ -82,6 +111,7 @@ func TranslateHTTPRoutesToKongstateServices(
}
continue
}

kongstateServiceCache[serviceName] = service
}

Expand Down Expand Up @@ -165,6 +195,8 @@ func translateHTTPRouteRulesMetaToKongstateService(
storer store.Storer,
serviceName string,
rulesMeta []httpRouteRuleMeta,
expressionRoutes bool,
matchesWithPriorities []SplitHTTPRouteMatchToKongRoutePriority,
) (kongstate.Service, error) {
// Fill in the common fields of the kongstate.Service.
service := kongstate.Service{
Expand Down Expand Up @@ -235,13 +267,19 @@ func translateHTTPRouteRulesMetaToKongstateService(
applyTimeoutToServiceFromHTTPRouteRule(&service, ruleMeta.Rule)
}

routes, err := translateHTTPRouteRulesMetaToKongstateRoutes(rulesMeta)
if err != nil {
return kongstate.Service{}, err
if expressionRoutes {
routes, err := translateSplitHTTPRouteMatchesToKongstateRoutesWithExpression(matchesWithPriorities)
if err != nil {
return kongstate.Service{}, err
}
service.Routes = routes
} else {
routes, err := translateHTTPRouteRulesMetaToKongstateRoutes(rulesMeta)
if err != nil {
return kongstate.Service{}, err
}
service.Routes = routes
}
// preallocate slice for the routes.
service.Routes = make([]kongstate.Route, 0, len(routes))
service.Routes = append(service.Routes, routes...)

return service, nil
}
Expand Down Expand Up @@ -428,6 +466,10 @@ func (m httpRouteRuleMeta) getHTTPBackendRefsKey() string {
return getSortedItemsString(m.Rule.BackendRefs)
}

func (m httpRouteRuleMeta) getRuleKey() string {
return fmt.Sprintf("%s/%s.%d", m.parentRoute.Namespace, m.parentRoute.Name, m.RuleNumber)
}

// getFiltersKey computes a key from a list of filters.
// The order of the filters is not important.
func (m httpRouteRuleMeta) getFiltersKey() string {
Expand Down Expand Up @@ -490,7 +532,6 @@ func (m httpRouteRuleMeta) getKongServiceNameByBackendRefs() string {
hash := sha256.Sum256([]byte(name))
// We have already returned when there are no backends in the rule, so it is safe to use backendNames[0].
trimmedName := fmt.Sprintf("httproute.%s.svc.%s_combined.%x", m.parentRoute.Namespace, backendNames[0], hash)
// REVIEW: should we emit a log here to tell that the name is trimmed? And should we record the original long name?
return trimmedName
}
return name
Expand Down
65 changes: 43 additions & 22 deletions internal/dataplane/translator/subtranslator/httproute_atc.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,6 @@ func pathMatcherFromHTTPPathMatch(pathMatch *gatewayapi.HTTPPathMatch) atc.Match
atc.NewPredicateHTTPPath(atc.OpPrefixMatch, path+"/"),
)
case gatewayapi.PathMatchRegularExpression:
// TODO: for compatibility with kong traditional routes, here we append the ^ prefix to match the path from beginning.
// Could we allow the regex to match any part of the path?
// https://github.com/Kong/kubernetes-ingress-controller/issues/3983
return atc.NewPredicateHTTPPath(atc.OpRegexMatch, appendRegexBeginIfNotExist(path))
}

Expand Down Expand Up @@ -454,14 +451,14 @@ func (t HTTPRoutePriorityTraits) EncodeToPriority() RoutePriorityType {
return priority
}

// AssignRoutePriorityToSplitHTTPRouteMatches assigns priority to
// assignRoutePriorityToSplitHTTPRouteMatches assigns priority to
// ALL split matches from ALL HTTPRoutes in the cache.
// Firstly assign "fixed" bits by the following fields of the match:
// hostname, path type, path length, method match, number of header matches, number of query param matches.
// If ties exists in the first step, where multiple matches has the same priority
// calculated from the fields, we run a sort for the matches in the tie
// and assign the bits for "relative order" according to the sorting result of these matches.
func AssignRoutePriorityToSplitHTTPRouteMatches(
func assignRoutePriorityToSplitHTTPRouteMatches(
logger logr.Logger,
splitHTTPRouteMatches []SplitHTTPRouteMatch,
) []SplitHTTPRouteMatchToKongRoutePriority {
Expand Down Expand Up @@ -544,9 +541,9 @@ func compareSplitHTTPRouteMatchesRelativePriority(match1, match2 SplitHTTPRouteM
return true
}

// KongExpressionRouteFromHTTPRouteMatchWithPriority translates a split HTTPRoute match into expression
// kongExpressionRouteFromHTTPRouteMatchWithPriority translates a split HTTPRoute match into expression
// based kong route with assigned priority.
func KongExpressionRouteFromHTTPRouteMatchWithPriority(
func kongExpressionRouteFromHTTPRouteMatchWithPriority(
httpRouteMatchWithPriority SplitHTTPRouteMatchToKongRoutePriority,
) (*kongstate.Route, error) {
match := httpRouteMatchWithPriority.Match
Expand Down Expand Up @@ -610,20 +607,44 @@ func KongExpressionRouteFromHTTPRouteMatchWithPriority(
return r, nil
}

// KongServiceNameFromSplitHTTPRouteMatch generates service name from split HTTPRoute match.
// since one HTTPRoute may be split by hostname and rule, the service name will be generated
// in the format "httproute.<namespace>.<name>.<hostname>.<rule index>".
// For example: `httproute.default.example.foo.com.0`.
func KongServiceNameFromSplitHTTPRouteMatch(match SplitHTTPRouteMatch) string {
httproute := match.Source
hostname := "_"
if len(match.Hostname) > 0 {
hostname = strings.ReplaceAll(match.Hostname, "*", "_")
// groupHTTPRouteMatchesWithPrioritiesByRule groups split HTTPRoute matches that has priorities assigned by the source HTTPRoute rule,.
func groupHTTPRouteMatchesWithPrioritiesByRule(
logger logr.Logger, routes []*gatewayapi.HTTPRoute,
) splitHTTPRouteMatchesWithPrioritiesGroupedByRule {
splitHTTPRouteMatches := []SplitHTTPRouteMatch{}
for _, route := range routes {
splitHTTPRouteMatches = append(splitHTTPRouteMatches, SplitHTTPRoute(route)...)
}
// assign priorities to split HTTPRoutes.
splitHTTPRouteMatchesWithPriorities := assignRoutePriorityToSplitHTTPRouteMatches(logger, splitHTTPRouteMatches)

// group the matches with priorities by its source HTTPRoute rule.
ruleToSplitMatchesWithPriorities := splitHTTPRouteMatchesWithPrioritiesGroupedByRule{}
for _, matchWithPriority := range splitHTTPRouteMatchesWithPriorities {
sourceRoute := matchWithPriority.Match.Source
ruleKey := fmt.Sprintf("%s/%s.%d", sourceRoute.Namespace, sourceRoute.Name, matchWithPriority.Match.RuleIndex)
ruleToSplitMatchesWithPriorities[ruleKey] = append(ruleToSplitMatchesWithPriorities[ruleKey], matchWithPriority)
}
return ruleToSplitMatchesWithPriorities
}

// translateSplitHTTPRouteMatchesToKongstateRoutesWithExpression translates a list of split HTTPRoute matches with assigned priorities
// that are poiting to the same service to list of kongstate route with expressions.
func translateSplitHTTPRouteMatchesToKongstateRoutesWithExpression(
matchesWithPriorities []SplitHTTPRouteMatchToKongRoutePriority,
) ([]kongstate.Route, error) {
routes := make([]kongstate.Route, 0, len(matchesWithPriorities))
for _, matchWithPriority := range matchesWithPriorities {
// Since each match is assigned a deterministic priority, we have to generate one route for each split match
// because every match have a different priority.
// TODO: update the algorithm to assign priorities to matches to make it possible to consolidate some matches.
// For example, we can assign the same priority to multiple matches from the same rule if they tie on the priority from the fixed fields:
// https://github.com/Kong/kubernetes-ingress-controller/issues/6807
route, err := kongExpressionRouteFromHTTPRouteMatchWithPriority(matchWithPriority)
if err != nil {
return []kongstate.Route{}, err
}
routes = append(routes, *route)
}
return fmt.Sprintf("httproute.%s.%s.%s.%d",
httproute.Namespace,
httproute.Name,
hostname,
match.RuleIndex,
)
return routes, nil
}
Loading
Loading