-
Notifications
You must be signed in to change notification settings - Fork 167
canonical wds service #1704
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
canonical wds service #1704
Changes from all commits
e434f42
5017b60
b4c518a
3ef43a0
4f5bc7d
82e9903
9c7a24e
fc0b04f
252f1de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -187,6 +187,26 @@ impl Server { | |
| } | ||
| } | ||
|
|
||
| enum MatchReason<'a> { | ||
| Canonical(&'a Arc<Service>), | ||
| First(&'a Arc<Service>), | ||
| Namespace(&'a Arc<Service>), | ||
| PreferredNamespace(&'a Arc<Service>), | ||
| None, | ||
| } | ||
|
Comment on lines
+190
to
+196
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is definitely not a blocker, but more of a neat idea I had. If you implemement impl<'a> Ord for MatchReason<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use MatchReason::*;
let self_rank: i32 = match self {
Canonical(_) => 3,
PreferredNamespace(_) => 2,
Namespace(_) => 1,
First(_) => 0,
};
let other_rank: i32 = match other {
Canonical(_) => 3,
PreferredNamespace(_) => 2,
Namespace(_) => 1,
First(_) => 0,
};
self_rank.cmp(&other_rank)
}
}then you can do let service: Option<&Arc<Service>> = services Vec<Arc<Service>>
.iter() Iter<'_, Arc<Service>>
.map(|s: &Arc<Service>| {
if s.namespace == client.namespace {
MatchReason::Namespace(s)
} else if s.canonical {
MatchReason::Canonical(s)
} else if let Some(preferred_ns: &String) = &self.prefered_service_namespace
&& s.namespace == *preferred_ns
{
MatchReason::PreferredNamespace(s)
} else {
MatchReason::First(s)
}
}) impl Iterator<Item = MatchReason<'_>>
.max() Option<MatchReason<'_>>
.map(|mr: MatchReason<'_>| mr.into());It also lets you get rid of the |
||
|
|
||
| impl<'a> From<MatchReason<'a>> for Option<&'a Arc<Service>> { | ||
| fn from(value: MatchReason<'a>) -> Option<&'a Arc<Service>> { | ||
| match value { | ||
| MatchReason::Canonical(s) | ||
| | MatchReason::First(s) | ||
| | MatchReason::Namespace(s) | ||
| | MatchReason::PreferredNamespace(s) => Some(s), | ||
| MatchReason::None => None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A DNS [Resolver] backed by the ztunnel [DemandProxyState]. | ||
| struct Store { | ||
| state: DemandProxyState, | ||
|
|
@@ -390,21 +410,35 @@ impl Store { | |
| .cloned() | ||
| .collect(); | ||
|
|
||
| // TODO: ideally we'd sort these by creation time so that the oldest would be used if there are no namespace matches | ||
| // presently service doesn't have creation time in WDS, but we could add it | ||
| // TODO: if the local namespace doesn't define a service, kube service should be prioritized over se | ||
| let service = match services | ||
| let service: Option<&Arc<Service>> = services | ||
| .iter() | ||
| .find(|service| service.namespace == client.namespace) | ||
| { | ||
| Some(service) => Some(service), | ||
| None => match self.prefered_service_namespace.as_ref() { | ||
| Some(prefered_namespace) => services.iter().find_or_first(|service| { | ||
| service.namespace == prefered_namespace.as_str() | ||
| }), | ||
| None => services.first(), | ||
| }, | ||
| }; | ||
| .fold_while(MatchReason::None, |r, s| { | ||
| if s.namespace == client.namespace { | ||
|
Stevenjin8 marked this conversation as resolved.
|
||
| itertools::FoldWhile::Done(MatchReason::Namespace(s)) | ||
| } else if s.canonical { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. having multiple marked canonical is UB right? do we have sorting or anything on the list so it would be consistent? A good control plane would probably not send multiple marked canonical of course.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, UB and a good control plane should not send this. We don't have consistent sorting so IF you have two services marked as canonical (control plane bug) and IF you have two ztunnels who's wds input ordering is differed enough that their data structures are ordered differently you could have inconsistent behaviors node-to-node. This isn't actually any worse than how it was when we just did "match namespace or first" since "first" was essentially the same UB. I think it's probably ok but maybe we can add a debug_assertion? Would our integ test builds run debug assertions or are they optimized builds already? |
||
| itertools::FoldWhile::Continue(MatchReason::Canonical(s)) | ||
| } else { | ||
| // TODO: deprecate preferred_service_namespace | ||
| // https://github.com/istio/ztunnel/issues/1709 | ||
| if let Some(preferred_namespace) = | ||
| self.prefered_service_namespace.as_ref() | ||
| && preferred_namespace.as_str() == s.namespace | ||
| && !matches!(r, MatchReason::Canonical(_)) | ||
| { | ||
| return itertools::FoldWhile::Continue( | ||
| MatchReason::PreferredNamespace(s), | ||
| ); | ||
| } | ||
| match r { | ||
| MatchReason::None => { | ||
| itertools::FoldWhile::Continue(MatchReason::First(s)) | ||
| } | ||
| _ => itertools::FoldWhile::Continue(r), | ||
| } | ||
| } | ||
| }) | ||
| .into_inner() | ||
| .into(); | ||
|
|
||
| // First, lookup the host as a service. | ||
| if let Some(service) = service { | ||
|
|
@@ -963,6 +997,7 @@ mod tests { | |
|
|
||
| const NS1: &str = "ns1"; | ||
| const NS2: &str = "ns2"; | ||
| const NS3: &str = "ns3"; | ||
| const PREFERRED: &str = "preferred-ns"; | ||
| const NW1: Strng = strng::literal!("nw1"); | ||
| const NW2: Strng = strng::literal!("nw2"); | ||
|
|
@@ -1394,6 +1429,18 @@ mod tests { | |
| expect_records: vec![a(n("everywhere.io."), ipv4("10.10.10.112"))], | ||
| ..Default::default() | ||
| }, | ||
| Case { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should have a test case that NS local can win out over one marked canonical |
||
| name: "success: canonical services are preferred when no ns-local hostname is present", | ||
| host: "canonical.svc", | ||
| expect_records: vec![a(n("canonical.svc."), ipv4("10.10.10.141"))], | ||
| ..Default::default() | ||
| }, | ||
| Case { | ||
| name: "success: namespace-local service should be preferred over canonical", | ||
| host: "canonical.with.local", | ||
| expect_records: vec![a(n("canonical.with.local."), ipv4("10.10.10.150"))], | ||
| ..Default::default() | ||
| }, | ||
| ]; | ||
|
|
||
| // Create and start the proxy. | ||
|
|
@@ -1713,6 +1760,24 @@ mod tests { | |
| // Service with the same name in the same namespace | ||
| // Client in NS1 should use this service | ||
| xds_namespaced_external_service("everywhere.io", NS1, &[na(NW1, "10.10.10.112")]), | ||
| // Service that is canonical should be preferrred when no ns-local definition | ||
| xds_namespaced_external_service("canonical.svc", NS2, &[na(NW1, "10.10.10.140")]), | ||
| xds_namespaced_external_canonical_service( | ||
| "canonical.svc", | ||
| NS3, | ||
| &[na(NW1, "10.10.10.141")], | ||
| ), | ||
| // Client in NS1 should prefer local over canonical | ||
| xds_namespaced_external_service( | ||
| "canonical.with.local", | ||
| NS1, | ||
| &[na(NW1, "10.10.10.150")], | ||
| ), | ||
| xds_namespaced_external_canonical_service( | ||
| "canonical.with.local", | ||
| NS2, | ||
| &[na(NW1, "10.10.10.151")], | ||
| ), | ||
| with_fqdn( | ||
| "details.ns2.svc.cluster.remote", | ||
| xds_service( | ||
|
|
@@ -1838,6 +1903,11 @@ mod tests { | |
| svc | ||
| } | ||
|
|
||
| fn with_canonical(canonical: bool, mut svc: XdsService) -> XdsService { | ||
| svc.canonical = canonical; | ||
| svc | ||
| } | ||
|
|
||
| fn xds_service<S1: AsRef<str>, S2: AsRef<str>>( | ||
| name: S1, | ||
| ns: S2, | ||
|
|
@@ -1877,6 +1947,14 @@ mod tests { | |
| ) | ||
| } | ||
|
|
||
| fn xds_namespaced_external_canonical_service<S1: AsRef<str>, S2: AsRef<str>>( | ||
| hostname: S1, | ||
| ns: S2, | ||
| vips: &[NetworkAddress], | ||
| ) -> XdsService { | ||
| with_canonical(true, xds_namespaced_external_service(hostname, ns, vips)) | ||
| } | ||
|
|
||
| fn xds_workload( | ||
| name: &str, | ||
| ns: &str, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,9 @@ pub struct Service { | |
|
|
||
| #[serde(default, skip_serializing_if = "is_default")] | ||
| pub ip_families: Option<IpFamily>, | ||
|
|
||
| #[serde(default)] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this also have skip serializing on default?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To my mind it would be nice to serialize |
||
| pub canonical: bool, | ||
| } | ||
|
|
||
| /// EndpointSet is an abstraction over a set of endpoints. | ||
|
|
@@ -328,6 +331,7 @@ impl TryFrom<&XdsService> for Service { | |
| waypoint, | ||
| load_balancer: lb, | ||
| ip_families, | ||
| canonical: s.canonical, | ||
| }; | ||
| Ok(svc) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.