Skip to content

Commit

Permalink
fix(parser): generate service name and route name in translator
Browse files Browse the repository at this point in the history
  • Loading branch information
jrsmroz committed Oct 17, 2022
1 parent 5c20d0a commit eb9cf86
Show file tree
Hide file tree
Showing 4 changed files with 646 additions and 479 deletions.
253 changes: 151 additions & 102 deletions internal/dataplane/parser/translate_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,39 +78,25 @@ func (p *Parser) ingressRulesFromHTTPRoute(result *ingressRules, httproute *gate
// 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 {
for _, translationMeta := range translators.TranslateHTTPRoute(httproute) {
for _, kongServiceTranslation := range translators.TranslateHTTPRoute(httproute) {
// HTTPRoute uses a wrapper HTTPBackendRef to add optional filters to its BackendRefs
backendRefs := httpBackendRefsToBackendRefs(translationMeta.BackendRefs)
backendRefs := httpBackendRefsToBackendRefs(kongServiceTranslation.BackendRefs)

// use the original index of the first rule that uses this service as the rule number
firstCombinedRuleNum := translationMeta.FirstRuleNumber
serviceName := kongServiceTranslation.Name

// create a service and attach the routes to it
service, err := generateKongServiceFromBackendRef(p.logger, p.storer, result, httproute, firstCombinedRuleNum, "http", backendRefs...)
service, err := generateKongServiceFromBackendRefWithName(p.logger, p.storer, serviceName, result, httproute, "http", backendRefs...)
if err != nil {
return err
}

// generate the routes for the service and attach them to the service
for _, matchGroup := range translationMeta.MatchGroups {
// flatten the match group into a match slice
var matches []gatewayv1beta1.HTTPRouteMatch
for _, match := range matchGroup.Matches {
matches = append(matches, *match.Match)
}

ruleNumber := matchGroup.FirstRuleNumber
rule := *matchGroup.FirstRule
routes, err := generateKongRouteFromHTTPRouteRuleWithConsolidatedMatches(httproute, ruleNumber, rule, matches, p.flagEnabledRegexPathPrefix)
for _, kongRouteTranslation := range kongServiceTranslation.KongRoutes {
routes, err := generateKongRouteFromHTTPRouteRuleFromTranslation(httproute, kongRouteTranslation, p.flagEnabledRegexPathPrefix)
if err != nil {
return err
}
// for _, route := range routes {
// val, _ := json.MarshalIndent(route.Route, "", "\t")
// fmt.Println(string(val))
// }
service.Routes = append(service.Routes, routes...)

}

// cache the service to avoid duplicates in further loop iterations
Expand Down Expand Up @@ -177,96 +163,182 @@ func generateKongRoutesFromHTTPRouteRule(
rule gatewayv1beta1.HTTPRouteRule,
addRegexPrefix bool,
) ([]kongstate.Route, error) {
if len(rule.Matches) == 0 {
// it's acceptable for an HTTPRoute to have no matches in the rulesets,
// but only backends as long as there are hostnames. In this case, we
// match all traffic based on the hostname and leave all other routing
// options default.
// however in this case there must actually be some present hostnames
// configured for the HTTPRoute or else it's not valid.
// otherwise apply the hostnames to the route
// attach the plugins to be applied to the given route
return generateKongRoutesFromHTTPRouteRuleWithNoMatches(httproute, rule)
}

// gather the k8s object information and hostnames from the httproute
objectInfo := util.FromK8sObject(httproute)
hostnames := getHTTPRouteHostnamesAsSliceOfStringPointers(httproute)

// generate kong plugins from rule.filters
plugins := generatePluginsFromHTTPRouteRuleFilters(rule)

// the HTTPRoute specification upstream specifically defines matches as
// independent (e.g. each match is an OR with other matches, not an AND).
// Therefore we treat each match rule as a separate Kong Route, so we iterate through
// all matches to determine all the routes that will be needed for the services.
var routes []kongstate.Route

for matchNumber, match := range rule.Matches {
// determine the name of the route, identify it as a route that belongs
// to a Kubernetes HTTPRoute object.
kongRouteName := fmt.Sprintf(
"httproute.%s.%s.%d.%d",
httproute.Namespace,
httproute.Name,
ruleNumber,
matchNumber,
)
// generate kong plugins from rule.filters
plugins := generatePluginsFromHTTPRouteFilters(rule.Filters)
if len(rule.Matches) > 0 {
for matchNumber, match := range rule.Matches {
// determine the name of the route, identify it as a route that belongs
// to a Kubernetes HTTPRoute object.
routeName := kong.String(fmt.Sprintf(
"httproute.%s.%s.%d.%d",
httproute.Namespace,
httproute.Name,
ruleNumber,
matchNumber,
))

// TODO: implement query param matches (https://github.com/Kong/kubernetes-ingress-controller/issues/2778)
if len(match.QueryParams) > 0 {
return nil, fmt.Errorf("query param matches are not yet supported")
}

matches := []gatewayv1beta1.HTTPRouteMatch{match}
kongRoute, err := generateKongRouteFromHTTPRouteMatches(kongRouteName, matches, objectInfo, hostnames, plugins, addRegexPrefix)
// build the route object using the method and pathing information
r := kongstate.Route{
Ingress: objectInfo,
Route: kong.Route{
Name: routeName,
Protocols: kong.StringSlice("http", "https"),
PreserveHost: kong.Bool(true),
},
}

if err != nil {
return nil, err
// attach any hostnames associated with the httproute
if len(hostnames) > 0 {
r.Hosts = hostnames
}

// configure path matching information about the route if paths matching was defined
// Kong automatically infers whether or not a path is a regular expression and uses a prefix match by
// default it it is not. For those types, we use the path value as-is and let Kong determine the type.
// For exact matches, we transform the path into a regular expression that terminates after the value
if match.Path != nil {
switch *match.Path.Type {
case gatewayv1beta1.PathMatchExact:
terminated := *match.Path.Value + "$"
if addRegexPrefix {
terminated = translators.KongPathRegexPrefix + terminated
}
r.Route.Paths = []*string{&terminated}
case gatewayv1beta1.PathMatchPathPrefix:
path := *match.Path.Value
r.Route.Paths = []*string{&path}
case gatewayv1beta1.PathMatchRegularExpression:
path := *match.Path.Value
if addRegexPrefix {
path = translators.KongPathRegexPrefix + path
}
r.Route.Paths = []*string{&path}
}
}

// configure method matching information about the route if method
// matching was defined.
if match.Method != nil {
r.Route.Methods = append(r.Route.Methods, kong.String(string(*match.Method)))
}

// convert header matching from HTTPRoute to Route format
headers, err := convertGatewayMatchHeadersToKongRouteMatchHeaders(match.Headers)
if err != nil {
return nil, err
}
if len(headers) > 0 {
r.Route.Headers = headers
}

// stripPath needs to be disabled by default to be conformant with the Gateway API
r.StripPath = kong.Bool(false)

// attach the plugins to be applied to the given route
if len(plugins) != 0 {
if r.Plugins == nil {
r.Plugins = make([]kong.Plugin, 0, len(plugins))
}
r.Plugins = append(r.Plugins, plugins...)
}

// add the route to the list of routes for the service(s)
routes = append(routes, r)
}
} else {
// it's acceptable for an HTTPRoute to have no matches in the rulesets,
// but only backends as long as there are hostnames. In this case, we
// match all traffic based on the hostname and leave all other routing
// options default.
r := kongstate.Route{
Ingress: objectInfo,
Route: kong.Route{
Name: kong.String(fmt.Sprintf("httproute.%s.%s.0.0", httproute.Namespace, httproute.Name)),
Protocols: kong.StringSlice("http", "https"),
PreserveHost: kong.Bool(true),
},
}

// however in this case there must actually be some present hostnames
// configured for the HTTPRoute or else it's not valid.
if len(hostnames) == 0 {
return nil, fmt.Errorf("no match rules or hostnames specified")
}

routes = append(routes, kongRoute)
// otherwise apply the hostnames to the route
r.Hosts = append(r.Hosts, hostnames...)

// attach the plugins to be applied to the given route
r.Plugins = append(r.Plugins, plugins...)

// add the route to the list of routes for the service(s)
routes = append(routes, r)
}

return routes, nil
}

func generateKongRouteFromHTTPRouteRuleWithConsolidatedMatches(
func generateKongRouteFromHTTPRouteRuleFromTranslation(
httproute *gatewayv1beta1.HTTPRoute,
ruleNumber int,
rule gatewayv1beta1.HTTPRouteRule,
matches []gatewayv1beta1.HTTPRouteMatch,
translation translators.KongRoute,
addRegexPrefix bool,
) ([]kongstate.Route, error) {
if len(rule.Matches) == 0 {
if len(translation.Matches) == 0 {
// it's acceptable for an HTTPRoute to have no matches in the rulesets,
// but only backends as long as there are hostnames. In this case, we
// match all traffic based on the hostname and leave all other routing
// options default.
// however in this case there must actually be some present hostnames
// configured for the HTTPRoute or else it's not valid.
// otherwise apply the hostnames to the route
// attach the plugins to be applied to the given route
return generateKongRoutesFromHTTPRouteRuleWithNoMatches(httproute, rule)

hostnames := getHTTPRouteHostnamesAsSliceOfStringPointers(httproute)
if len(hostnames) == 0 {
return nil, fmt.Errorf("no match rules or hostnames specified")
}

routeName := fmt.Sprintf("httproute.%s.%s.0.0", httproute.Namespace, httproute.Name)
objectInfo := util.FromK8sObject(httproute)
r := generateKongstateRoute(routeName, objectInfo, hostnames)

plugins := generatePluginsFromHTTPRouteFilters(translation.Filters)
r.Plugins = append(r.Plugins, plugins...)

return []kongstate.Route{r}, nil
}

// gather the k8s object information and hostnames from the httproute
objectInfo := util.FromK8sObject(httproute)
hostnames := getHTTPRouteHostnamesAsSliceOfStringPointers(httproute)

// generate kong plugins from rule.filters
plugins := generatePluginsFromHTTPRouteRuleFilters(rule)
plugins := generatePluginsFromHTTPRouteFilters(translation.Filters)

// the HTTPRoute specification upstream specifically defines matches as
// independent (e.g. each match is an OR with other matches, not an AND).
// However, we treat all matches that were already consolidated as a single Kong Route
// with multiple paths.

kongRouteName := fmt.Sprintf(
"httproute.%s.%s.%d.%d",
httproute.Namespace,
httproute.Name,
ruleNumber,
// matchNumber,
0, //TODO: should not be 0!!!
)
var matches []gatewayv1beta1.HTTPRouteMatch
for _, match := range translation.Matches {
matches = append(matches, *match.Match)
}

kongRoute, err := generateKongRouteFromHTTPRouteMatches(kongRouteName, matches, objectInfo, hostnames, plugins, addRegexPrefix)
kongRoute, err := generateKongRouteFromHTTPRouteMatches(
translation.Name,
matches,
objectInfo,
hostnames,
plugins,
addRegexPrefix,
)

if err != nil {
return nil, err
Expand All @@ -275,28 +347,6 @@ func generateKongRouteFromHTTPRouteRuleWithConsolidatedMatches(
return []kongstate.Route{kongRoute}, nil
}

func generateKongRoutesFromHTTPRouteRuleWithNoMatches(httproute *gatewayv1beta1.HTTPRoute, rule gatewayv1beta1.HTTPRouteRule) ([]kongstate.Route, error) {
hostnames := getHTTPRouteHostnamesAsSliceOfStringPointers(httproute)
if len(hostnames) == 0 {
return nil, fmt.Errorf("no match rules or hostnames specified")
}

routeName := fmt.Sprintf("httproute.%s.%s.0.0", httproute.Namespace, httproute.Name)
objectInfo := util.FromK8sObject(httproute)
r := generateKongstateRoute(routeName, objectInfo, hostnames)

// generate kong plugins from rule.filters
plugins := generatePluginsFromHTTPRouteRuleFilters(rule)
if len(plugins) != 0 {
if r.Plugins == nil {
r.Plugins = make([]kong.Plugin, 0, len(plugins))
}
r.Plugins = append(r.Plugins, plugins...)
}

return []kongstate.Route{r}, nil
}

// generateKongRouteFromHTTPRouteMatches converts an HTTPRouteMatches to a Kong Route object.
// This function assumes that the HTTPRouteMatches share the query params, headers and methods.
func generateKongRouteFromHTTPRouteMatches(
Expand Down Expand Up @@ -407,15 +457,14 @@ func generateKongstateRoute(routeName string, ingressObjectInfo util.K8sObjectIn
return r
}

// generatePluginsFromHTTPRouteRuleFilters accepts a rule as argument and converts
// HttpRouteRule.Filters into Kong filters.
func generatePluginsFromHTTPRouteRuleFilters(rule gatewayv1beta1.HTTPRouteRule) []kong.Plugin {
// generatePluginsFromHTTPRouteFilters converts HTTPRouteFilter into Kong filters.
func generatePluginsFromHTTPRouteFilters(filters []gatewayv1beta1.HTTPRouteFilter) []kong.Plugin {
kongPlugins := make([]kong.Plugin, 0)
if rule.Filters == nil {
if len(filters) == 0 {
return kongPlugins
}

for _, filter := range rule.Filters {
for _, filter := range filters {
if filter.Type == gatewayv1beta1.HTTPRouteFilterRequestHeaderModifier {
kongPlugins = append(kongPlugins, generateRequestHeaderModifierKongPlugin(filter.RequestHeaderModifier))
}
Expand Down
Loading

0 comments on commit eb9cf86

Please sign in to comment.