Skip to content

Commit 511b3e3

Browse files
authored
Fix inbound server label ambiguity (#1947)
As the proxy discovers policies, it refers to the source of the policy configuration. This comes in two flavors: "default" (synthetic) resources and resources that actually exist in the cluster. When the proxy reads routes and authorization, the control plane uses protobuf types to differentiate these states; but server resources are described by a generic label map. When the proxy reads this label map, it doesn't properly detect when default/synthetic resources are used and it _always_ indicates that the resource is real. This can lead to duplicate metrics when, for instance, the proxy initially uses a default server and then obtains a default configuration by discovery. This change fixes this situation in two ways: 1. We now properly detect and differentiate default server configurations. 2. We update the hash and comparisons functions for the `Meta` type so that an improperly construct resource matches a default (i.e. when inserted into a label map). Fixes linkerd/linkerd2#9502 Signed-off-by: Oliver Gould <[email protected]>
1 parent 1166f08 commit 511b3e3

File tree

3 files changed

+148
-15
lines changed

3 files changed

+148
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,7 @@ dependencies = [
14891489
"ipnet",
14901490
"linkerd-http-route",
14911491
"linkerd2-proxy-api",
1492+
"maplit",
14921493
"prost-types",
14931494
"quickcheck",
14941495
"thiserror",

linkerd/server-policy/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ proto = ["linkerd-http-route/proto", "linkerd2-proxy-api", "prost-types"]
1313
ipnet = "2"
1414
http = "0.2"
1515
linkerd-http-route = { path = "../http-route" }
16-
linkerd2-proxy-api = { version = "0.7", features = ["inbound"], optional = true }
1716
prost-types = { version = "0.11", optional = true }
1817
thiserror = "1"
1918

19+
[dependencies.linkerd2-proxy-api]
20+
version = "0.7"
21+
features = ["inbound"]
22+
optional = true
23+
2024
[dev-dependencies]
25+
maplit = "1"
2126
quickcheck = { version = "1", default-features = false }

linkerd/server-policy/src/meta.rs

Lines changed: 141 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::{borrow::Cow, hash::Hash, sync::Arc};
1+
use std::{borrow::Cow, sync::Arc};
22

3-
#[derive(Debug, PartialEq, Eq, Hash)]
3+
#[derive(Debug, Eq)]
44
pub enum Meta {
55
Default {
66
name: Cow<'static, str>,
@@ -16,31 +16,47 @@ pub enum Meta {
1616

1717
impl Meta {
1818
pub fn new_default(name: impl Into<Cow<'static, str>>) -> Arc<Self> {
19-
Arc::new(Meta::Default { name: name.into() })
19+
Arc::new(Self::Default { name: name.into() })
2020
}
2121

2222
pub fn name(&self) -> &str {
2323
match self {
24-
Meta::Default { name } => name,
25-
Meta::Resource { name, .. } => name,
24+
Self::Default { name } => name,
25+
Self::Resource { name, .. } => name,
2626
}
2727
}
2828

2929
pub fn kind(&self) -> &str {
3030
match self {
31-
Meta::Default { .. } => "default",
32-
Meta::Resource { kind, .. } => kind,
31+
Self::Default { .. } => "default",
32+
Self::Resource { kind, .. } => kind,
3333
}
3434
}
3535

3636
pub fn group(&self) -> &str {
3737
match self {
38-
Meta::Default { .. } => "",
39-
Meta::Resource { group, .. } => group,
38+
Self::Default { .. } => "",
39+
Self::Resource { group, .. } => group,
4040
}
4141
}
4242
}
4343

44+
impl std::cmp::PartialEq for Meta {
45+
fn eq(&self, other: &Self) -> bool {
46+
// Resources that look like Defaults are considered equal.
47+
self.group() == other.group() && self.kind() == other.kind() && self.name() == other.name()
48+
}
49+
}
50+
51+
impl std::hash::Hash for Meta {
52+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53+
// Resources that look like Defaults are considered the same.
54+
self.group().hash(state);
55+
self.kind().hash(state);
56+
self.name().hash(state);
57+
}
58+
}
59+
4460
#[cfg(feature = "proto")]
4561
pub mod proto {
4662
use super::*;
@@ -76,19 +92,27 @@ pub mod proto {
7692
.unwrap_or_else(|| default_group.to_string());
7793

7894
if let Some(kind) = labels.remove("kind") {
79-
return Ok(Arc::new(Meta::Resource { group, kind, name }));
95+
// If the controller set a 'group' and 'kind', but it's just
96+
// indicating that we're dealing with a default, then build a
97+
// default.
98+
if group.is_empty() && kind == "default" {
99+
return Ok(Self::new_default(name));
100+
}
101+
102+
return Ok(Arc::new(Self::Resource { group, kind, name }));
80103
}
81104

82105
// Older control plane versions don't set the kind label and, instead, may
83106
// encode kinds in the name like `default:deny`.
84107
let mut parts = name.splitn(2, ':');
85108
let meta = match (parts.next().unwrap(), parts.next()) {
86-
(kind, Some(name)) => Meta::Resource {
109+
("default", Some(name)) => return Ok(Self::new_default(name.to_string())),
110+
(kind, Some(name)) => Self::Resource {
87111
group,
88112
kind: kind.into(),
89113
name: name.into(),
90114
},
91-
(name, None) => Meta::Resource {
115+
(name, None) => Self::Resource {
92116
group,
93117
kind: default_kind.into(),
94118
name: name.into(),
@@ -104,7 +128,7 @@ pub mod proto {
104128

105129
fn try_from(pb: api::Metadata) -> Result<Self, Self::Error> {
106130
match pb.kind.ok_or(InvalidMeta::Missing)? {
107-
api::metadata::Kind::Default(name) => Ok(Meta::Default { name: name.into() }),
131+
api::metadata::Kind::Default(name) => Ok(Self::Default { name: name.into() }),
108132
api::metadata::Kind::Resource(r) => r.try_into(),
109133
}
110134
}
@@ -114,11 +138,114 @@ pub mod proto {
114138
type Error = InvalidMeta;
115139

116140
fn try_from(pb: api::Resource) -> Result<Self, Self::Error> {
117-
Ok(Meta::Resource {
141+
Ok(Self::Resource {
118142
group: pb.group,
119143
kind: pb.kind,
120144
name: pb.name,
121145
})
122146
}
123147
}
148+
149+
#[cfg(test)]
150+
#[test]
151+
fn default_from_labels() {
152+
let m = Meta::try_new_with_default(
153+
maplit::hashmap! {
154+
"group".into() => "".into(),
155+
"kind".into() => "default".into(),
156+
"name".into() => "foo".into(),
157+
},
158+
"foog",
159+
"fook",
160+
)
161+
.unwrap();
162+
assert!(matches!(&*m, Meta::Default { name } if name == "foo"));
163+
164+
let m = Meta::try_new_with_default(
165+
maplit::hashmap! {
166+
"name".into() => "default:foo".into(),
167+
},
168+
"foog",
169+
"fook",
170+
)
171+
.unwrap();
172+
assert!(matches!(&*m, Meta::Default { name } if name == "foo"));
173+
}
174+
}
175+
176+
#[cfg(test)]
177+
#[test]
178+
fn cmp() {
179+
fn hash(m: &Meta) -> u64 {
180+
use std::hash::{Hash, Hasher};
181+
let mut h = std::collections::hash_map::DefaultHasher::new();
182+
m.hash(&mut h);
183+
h.finish()
184+
}
185+
186+
for (left, right, equiv) in [
187+
(
188+
Meta::Default { name: "foo".into() },
189+
Meta::Default { name: "foo".into() },
190+
true,
191+
),
192+
(
193+
Meta::Default { name: "foo".into() },
194+
Meta::Default { name: "bar".into() },
195+
false,
196+
),
197+
(
198+
Meta::Default { name: "foo".into() },
199+
Meta::Resource {
200+
group: "".into(),
201+
kind: "default".into(),
202+
name: "foo".into(),
203+
},
204+
true,
205+
),
206+
(
207+
Meta::Default { name: "foo".into() },
208+
Meta::Resource {
209+
group: "".into(),
210+
kind: "default".into(),
211+
name: "bar".into(),
212+
},
213+
false,
214+
),
215+
(
216+
Meta::Resource {
217+
group: "foog".into(),
218+
kind: "fook".into(),
219+
name: "foo".into(),
220+
},
221+
Meta::Resource {
222+
group: "foog".into(),
223+
kind: "fook".into(),
224+
name: "foo".into(),
225+
},
226+
true,
227+
),
228+
(
229+
Meta::Resource {
230+
group: "foog".into(),
231+
kind: "fook".into(),
232+
name: "foo".into(),
233+
},
234+
Meta::Resource {
235+
group: "barg".into(),
236+
kind: "fook".into(),
237+
name: "foo".into(),
238+
},
239+
false,
240+
),
241+
] {
242+
assert!(
243+
(left == right) == equiv,
244+
"expected {left:?} == {right:?} to be {equiv}",
245+
);
246+
assert!(
247+
(hash(&left) == hash(&right)) == equiv,
248+
"expected hash({left:?}) == hash({right:?}) to be {equiv}",
249+
);
250+
}
124251
}

0 commit comments

Comments
 (0)