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
VirtualService simple merging of http routes at Gateway #8113
Conversation
Signed-off-by: Shriram Rajagopalan <shriramr@vmware.com>
@@ -225,7 +225,7 @@ func (configgen *ConfigGeneratorImpl) buildGatewayHTTPRouteConfig(env *model.Env | |||
// NOTE: WE DO NOT SUPPORT two gateways on same workload binding to same virtual service | |||
virtualServices := push.VirtualServices(merged.Names) | |||
virtualHosts := make([]route.VirtualHost, 0, len(virtualServices)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be *VirtualHost - since it gets changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See code below. I am copying from the map to this array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Envoy wants values and not pointers for some reason. or its probably gogo
) | ||
|
||
// CombineVHostRoutes will concatenate route blocks from two virtual hosts | ||
func CombineVHostRoutes(first []route.Route, second []route.Route) []route.Route { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to have a nasty side effect of even reordering match clauses that come from the same resource, where the user has intentionally got it right. Would it be better to try to leave order that the user has explicitly set intact?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. We would have to change our docs that routes will be reordered by paths, prefixes and then regexes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we prioritize user intent over heuristics? If the user has a specific order in mind (with regex first, to blackhole something, for example), and writes it down in a single resource, I think it should be respected.
/test istio-unit-tests |
// longest regex before shorter regex. it doesn't make any difference, but | ||
// we do it anyway to get a stable ordering | ||
if iType == jType { | ||
return len(iVal) < len(jVal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be
return len(iVal) > len(jVal)
based on the comments above which describes the merge logic.
Codecov Report
@@ Coverage Diff @@
## release-1.0 #8113 +/- ##
============================================
- Coverage 72% 72% -<1%
============================================
Files 358 358
Lines 31137 31228 +91
============================================
+ Hits 22235 22252 +17
- Misses 7951 8023 +72
- Partials 951 953 +2
Continue to review full report at Codecov.
|
Signed-off-by: Shriram Rajagopalan <shriramr@vmware.com>
Signed-off-by: Shriram Rajagopalan <shriramr@vmware.com>
Signed-off-by: Shriram Rajagopalan <shriramr@vmware.com>
} | ||
} | ||
|
||
if len(virtualHosts) == 0 { | ||
if len(vHostDedupMap) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move virtualHosts := make([]route.VirtualHost, 0, len(virtualServices))
down here to line 259 so it is clear we don't use it until we convert vHostDedupMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
var iType, jType envoyRouteType | ||
var iVal, jVal string | ||
|
||
switch iR := allroutes[i].Match.PathSpecifier.(type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be nice to pull this out into a helper to re-use, would improve readability here quite a bit I think:
func routeTypeAndVal(r route.Route) (envoyRouteType, string) {
var ttype envoyRouteType
var val string
switch rr := route.Match.PathSpecifier.(type) {
case *route.RouteMatch_Path:
ttype = envoyPath
val = rr.Path
case *route.RouteMatch_Prefix:
if rr.Prefix == "/" {
ttype = envoyCatchAll
} else {
ttype = envoyPrefix
}
val = rr.Prefix
case *route.RouteMatch_Regex:
if rr.Regex == "*" {
ttype = envoyCatchAll
} else {
ttype = envoyRegex
}
val = rr.Regex
}
return ttype, val
}
func SortRoutes(allroutes []route.Route) {
sort.SliceStable(allroutes, func(i, j int) bool {
iType, iVal := routeTypeAndValue(allroutes[i])
jType, jVal := routeTypeAndValue(allroutes[j])
if iType == jType {
if len(iVal) == len(jVal) {
if len(allroutes[i].Match.Headers) == len(allroutes[j].Match.Headers) {
return iVal > jVal
}
return len(allroutes[i].Match.Headers) > len(allroutes[j].Match.Headers)
}
return len(iVal) > len(jVal)
}
return iType < jType
})
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(oh and maybe routeTypeAndVal(r *route.Route)
instead to avoid copying)
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: ZackButcher If they are not already assigned, you can assign the PR to them by writing The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
// we have to maintain the same ordering semantics for non-merged vhost routes as well. | ||
// So, sort each virtual host's routes based on our custom sort criteria and build the virtual host array | ||
for _, v := range vHostDedupMap { | ||
istio_route.SortRoutes(v.Routes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not backward compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that may be a problem - if you have a single VHost the order should be preserved.
But on the other side - it may be good to update the API to specify a better/more scalable order.
// regex before catchall prefix / or catch all regex * | ||
// longest path before shorter path | ||
// longest prefix before shorter prefix | ||
// longest regex before shorter regex. it doesn't make any difference, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For regex - I think it makes a difference, we should preserve the original order ( or not allow merging - i.e. just one virtual service should be allowed to have regex ).
I think the safest option remains to only allow one 'main' VirtualService to be merged with path+prefix only rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to that. As I said in issue, we should put regexes in creation time order (which necessarily keeps regexes from the same VS together as a single block).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a good start - but for backward compat I would only sort if there are at least 2 VirtualServices. So users with a single VS keep the order, and if they have 2 VS - we sort.
And I'm not very comfortable with the regex story.
Signed-off-by: Shriram Rajagopalan <shriramr@vmware.com>
New changes are detected. LGTM label has been removed. |
@ZackButcher / @costinm dumped the reordering completely. After discussion with @frankbu , here is what we have now:
where catchAll is a route with no header match and a prefix match of / or regex match of *. This technique preserves the relative ordering of routes within a VirtualService, eliminates the need to compare regular expressions. The burden is now on the user to ensure that the blocks of routes in two virtual services don't conflict with each other |
This gets back to my point in the issue, there is no one winning way to do this. Like we discussed in the Networking WG, simply concatenating the clauses together is not good enough, it doesn't solve the user problem at hand meaningfully. If we're going to do this merging stuff, we should disregard the user order and reorder ourselves. We likely want to do this anyway in the long run, since it allows for optimizations in Envoy like a hashmap exact URI match followed by trie based prefix matching, etc. cc @louiscryan |
if rType == envoyCatchAll { | ||
// We have a catch all route. No point building other routes, with match conditions | ||
break allroutes | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add something to pushcontext - if the user has other routes after, it's likely a bad config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we could update the doc and change behavior - so default route is applied last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think adding to push context would be a lot ? If anything, we should catch this in validation and warn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a start :-)
I think we'll need to iterate - but this should move us forward.
In particular - we should still extract all the exact and prefix, and move them at the top, reverse sorted by length.
I think this is a good and safe thing to try in 1.0.1, to get some feedback. Given that this only works in cases where there really are no conflicts, we aren't setting ourselves up for backward compatibility problems if we decide on doing some reordering in the future release. |
|
||
// A route is catch all if and only if it has no header/query param match | ||
// and has a prefix / or regex *. | ||
if (iVal == "/" && iType == envoyPrefix) || (iVal == "*" && iType == envoyRegex) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want do the absolute minimum, to keep all options open for how things are reordered in the future, I would even reduce this check to only the prefix / case. It's the legitimate VirtualService-without-a-match-clause catch-all case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. User could also create a match clause with just prefix /
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point. bottom line is moving these rules could cause breakage, but I think it's a pretty unlikely corner case.
Lets merge and iterate. Would like to have some variant in the release tomorrow. and need some time for this to soak, and test. |
If we decide to fully reorder, please let me know by EOD today (after consensus) |
The more I think about it, the more I think that ANY merge algorithm, other than the simple concatenate and preserve user's order, will not be backward compatible (or break things when a second resource is merged) if we do it by default (without explicit user request). I think something like @rshriram's mergeKey suggestion, which lets the user control merge ordering, might be the right future enhancement, but for 1.0.1 the simple concatenate is the best and safest way to go. I think the latest comments from @ZackButcher and @bmarshall13 in #7793 support this thinking. @ZackButcher, I do think the the simple concatenate does solve some users problem in a meaningful way, the problem of clearly separating the host into contextually independent parts, presumably managed by different teams. I think the feedback from 1.0.1 will then help us understand where we need to go from there. |
SGTM |
Are there any notes about how one would test this? |
@christian-posta A little bit of documentation is here: https://istio.io/help/ops/traffic-management/deploy-guidelines/#multiple-virtual-services-and-destination-rules-for-the-same-host |
@rshriram
Why is that and whats different about the gateways ? I know this is an old PR, but i didnt find any other references to VirtualService merging.. |
Fixes #7793 for Gateways only.
This is the rudimentary merge of http routes, pushing
the wildcard/catch all routes to the end.
We already support merging of TCP and TLS routes for gateways.
Merging virtual services at the sidecar requires a lot of intrusive changes to the RDS code.
Signed-off-by: Shriram Rajagopalan shriramr@vmware.com