saluki_env/workload/
entity.rs1use std::{cmp::Ordering, fmt};
2
3use stringtheory::MetaString;
4
5const ENTITY_PREFIX_POD_UID: &str = "kubernetes_pod_uid://";
6const ENTITY_PREFIX_CONTAINER_ID: &str = "container_id://";
7const ENTITY_PREFIX_CONTAINER_INODE: &str = "container_inode://";
8const ENTITY_PREFIX_CONTAINER_PID: &str = "container_pid://";
9
10const RAW_CONTAINER_ID_PREFIX_INODE: &str = "in-";
11const RAW_CONTAINER_ID_PREFIX_CID: &str = "ci-";
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq)]
15pub enum EntityId {
16 Global,
22
23 PodUid(MetaString),
27
28 Container(MetaString),
32
33 ContainerInode(u64),
37
38 ContainerPid(u32),
42}
43
44impl EntityId {
45 pub fn from_raw_container_id<S>(raw_container_id: S) -> Option<Self>
58 where
59 S: AsRef<str> + Into<MetaString>,
60 {
61 if raw_container_id.as_ref().starts_with(RAW_CONTAINER_ID_PREFIX_INODE) {
62 let raw_inode = raw_container_id
66 .as_ref()
67 .trim_start_matches(RAW_CONTAINER_ID_PREFIX_INODE);
68 let inode = raw_inode.parse().ok()?;
69 Some(Self::ContainerInode(inode))
70 } else if raw_container_id.as_ref().starts_with(RAW_CONTAINER_ID_PREFIX_CID) {
71 let raw_cid = raw_container_id
74 .as_ref()
75 .trim_start_matches(RAW_CONTAINER_ID_PREFIX_CID);
76 Some(Self::Container(raw_cid.into()))
77 } else {
78 Some(Self::Container(raw_container_id.into()))
79 }
80 }
81
82 pub fn from_pod_uid<S>(pod_uid: S) -> Option<Self>
86 where
87 S: AsRef<str> + Into<MetaString>,
88 {
89 if pod_uid.as_ref() == "none" {
90 return None;
91 }
92 Some(Self::PodUid(pod_uid.into()))
93 }
94
95 pub fn try_into_container(self) -> Option<MetaString> {
99 match self {
100 Self::Container(container_id) => Some(container_id),
101 _ => None,
102 }
103 }
104
105 fn precedence_value(&self) -> usize {
106 match self {
107 Self::Global => 0,
108 Self::PodUid(_) => 1,
109 Self::Container(_) => 2,
110 Self::ContainerInode(_) => 3,
111 Self::ContainerPid(_) => 4,
112 }
113 }
114}
115
116impl fmt::Display for EntityId {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 match self {
119 Self::Global => write!(f, "system://global"),
120 Self::PodUid(pod_uid) => write!(f, "{}{}", ENTITY_PREFIX_POD_UID, pod_uid),
121 Self::Container(container_id) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_ID, container_id),
122 Self::ContainerInode(inode) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_INODE, inode),
123 Self::ContainerPid(pid) => write!(f, "{}{}", ENTITY_PREFIX_CONTAINER_PID, pid),
124 }
125 }
126}
127
128impl serde::Serialize for EntityId {
129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130 where
131 S: serde::Serializer,
132 {
133 serializer.collect_str(self)
136 }
137}
138
139#[derive(Eq, PartialEq)]
153pub struct HighestPrecedenceEntityIdRef<'a>(&'a EntityId);
154
155impl<'a> From<&'a EntityId> for HighestPrecedenceEntityIdRef<'a> {
156 fn from(entity_id: &'a EntityId) -> Self {
157 Self(entity_id)
158 }
159}
160
161impl PartialOrd for HighestPrecedenceEntityIdRef<'_> {
162 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
163 Some(self.cmp(other))
164 }
165}
166
167impl Ord for HighestPrecedenceEntityIdRef<'_> {
168 fn cmp(&self, other: &Self) -> Ordering {
169 let self_precedence = self.0.precedence_value();
171 let other_precedence = other.0.precedence_value();
172 if self_precedence != other_precedence {
173 return self_precedence.cmp(&other_precedence);
174 }
175
176 match (self.0, other.0) {
178 (EntityId::Global, EntityId::Global) => Ordering::Equal,
180 (EntityId::PodUid(self_pod_uid), EntityId::PodUid(other_pod_uid)) => self_pod_uid.cmp(other_pod_uid),
181 (EntityId::Container(self_container_id), EntityId::Container(other_container_id)) => {
182 self_container_id.cmp(other_container_id)
183 }
184 (EntityId::ContainerInode(self_inode), EntityId::ContainerInode(other_inode)) => {
185 self_inode.cmp(other_inode)
186 }
187 (EntityId::ContainerPid(self_pid), EntityId::ContainerPid(other_pid)) => self_pid.cmp(other_pid),
188 _ => unreachable!("entities with different precedence should not be compared"),
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn raw_container_id_inode_valid() {
199 let container_inode = 123456;
200 let raw_container_id = format!("{}{}", RAW_CONTAINER_ID_PREFIX_INODE, container_inode);
201 let entity_id = EntityId::from_raw_container_id(raw_container_id).unwrap();
202 assert_eq!(entity_id, EntityId::ContainerInode(container_inode));
203 }
204
205 #[test]
206 fn raw_container_id_inode_invalid() {
207 let raw_container_id = format!("{}invalid", RAW_CONTAINER_ID_PREFIX_INODE);
208 let entity_id = EntityId::from_raw_container_id(raw_container_id);
209 assert!(entity_id.is_none());
210 }
211
212 #[test]
213 fn raw_container_id_cid() {
214 let container_id = "abcdef1234567890";
215 let raw_container_id = format!("{}{}", RAW_CONTAINER_ID_PREFIX_CID, container_id);
216 let entity_id = EntityId::from_raw_container_id(raw_container_id).unwrap();
217 assert_eq!(entity_id, EntityId::Container(MetaString::from(container_id)));
218 }
219
220 #[test]
221 fn pod_uid_valid() {
222 let pod_uid = "abcdef1234567890";
223 let entity_id = EntityId::from_pod_uid(pod_uid).unwrap();
224 assert_eq!(entity_id, EntityId::PodUid(MetaString::from(pod_uid)));
225 }
226
227 #[test]
228 fn pod_uid_none() {
229 let pod_uid = "none";
230 let entity_id = EntityId::from_pod_uid(pod_uid);
231 assert!(entity_id.is_none());
232 }
233}