/src/ztunnel/src/baggage.rs
Line | Count | Source |
1 | | // Copyright Istio Authors |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use crate::strng::Strng; |
16 | | use hyper::{ |
17 | | header::{GetAll, ToStrError}, |
18 | | http::HeaderValue, |
19 | | }; |
20 | | |
21 | | #[derive(Debug, Default, PartialEq, Eq)] |
22 | | pub struct Baggage { |
23 | | pub cluster_id: Option<Strng>, |
24 | | pub namespace: Option<Strng>, |
25 | | pub workload_name: Option<Strng>, |
26 | | pub service_name: Option<Strng>, |
27 | | pub revision: Option<Strng>, |
28 | | pub region: Option<Strng>, |
29 | | pub zone: Option<Strng>, |
30 | | } |
31 | | |
32 | 0 | pub fn baggage_header_val(baggage: &Baggage, workload_type: &str) -> String { |
33 | | [ |
34 | 0 | baggage |
35 | 0 | .cluster_id |
36 | 0 | .as_ref() |
37 | 0 | .map(|cluster| format!("k8s.cluster.name={cluster}")), |
38 | 0 | baggage |
39 | 0 | .namespace |
40 | 0 | .as_ref() |
41 | 0 | .map(|namespace| format!("k8s.namespace.name={namespace}")), |
42 | 0 | baggage |
43 | 0 | .workload_name |
44 | 0 | .as_ref() |
45 | 0 | .map(|workload| format!("k8s.{workload_type}.name={workload}")), |
46 | 0 | baggage |
47 | 0 | .service_name |
48 | 0 | .as_ref() |
49 | 0 | .map(|service| format!("service.name={service}")), |
50 | 0 | baggage |
51 | 0 | .revision |
52 | 0 | .as_ref() |
53 | 0 | .map(|revision| format!("service.version={revision}")), |
54 | 0 | baggage |
55 | 0 | .region |
56 | 0 | .as_ref() |
57 | 0 | .map(|region| format!("cloud.region={region}")), |
58 | 0 | baggage |
59 | 0 | .zone |
60 | 0 | .as_ref() |
61 | 0 | .map(|zone| format!("cloud.availability_zone={zone}")), |
62 | | ] |
63 | 0 | .into_iter() |
64 | 0 | .flatten() |
65 | 0 | .collect::<Vec<_>>() |
66 | 0 | .join(",") |
67 | 0 | } |
68 | | |
69 | 1.12k | pub fn parse_baggage_header(headers: GetAll<HeaderValue>) -> Result<Baggage, ToStrError> { |
70 | 1.12k | let mut baggage = Baggage { |
71 | 1.12k | ..Default::default() |
72 | 1.12k | }; |
73 | 1.12k | for hv in headers.iter() { |
74 | 1.12k | let v = hv.to_str()?; |
75 | 2.63M | v.split(',').for_each(|s| { |
76 | 2.63M | let parts: Vec<&str> = s.split('=').collect(); |
77 | 2.63M | if parts.len() > 1 { |
78 | 201k | let val = match parts[1] { |
79 | 201k | "" => None, |
80 | 154k | s => Some(s.into()), |
81 | | }; |
82 | 201k | match parts[0] { |
83 | 201k | "k8s.cluster.name" => baggage.cluster_id = val, |
84 | 200k | "k8s.namespace.name" => baggage.namespace = val, |
85 | 199k | "k8s.deployment.name" |
86 | 198k | | "k8s.cronjob.name" |
87 | 196k | | "k8s.pod.name" |
88 | 195k | | "k8s.job.name" => baggage.workload_name = val, |
89 | 194k | "service.name" => baggage.service_name = val, |
90 | 193k | "service.version" => baggage.revision = val, |
91 | | // https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/ |
92 | 192k | "cloud.region" => baggage.region = val, |
93 | 192k | "cloud.availability_zone" => baggage.zone = val, |
94 | 191k | _ => {} |
95 | | } |
96 | 2.43M | } |
97 | 2.63M | }); |
98 | | } |
99 | 1.05k | Ok(baggage) |
100 | 1.12k | } |
101 | | |
102 | | #[cfg(test)] |
103 | | pub mod tests { |
104 | | use hyper::{HeaderMap, http::HeaderValue}; |
105 | | |
106 | | use crate::proxy::BAGGAGE_HEADER; |
107 | | use crate::strng::Strng; |
108 | | |
109 | | use super::{Baggage, baggage_header_val, parse_baggage_header}; |
110 | | |
111 | | #[test] |
112 | | fn baggage_parser() -> anyhow::Result<()> { |
113 | | let mut hm = HeaderMap::new(); |
114 | | let baggage_str = "k8s.cluster.name=K1,k8s.namespace.name=NS1,k8s.deployment.name=N1,service.name=N2,service.version=V1"; |
115 | | let header_value = HeaderValue::from_str(baggage_str)?; |
116 | | hm.append(BAGGAGE_HEADER, header_value); |
117 | | let baggage = parse_baggage_header(hm.get_all(BAGGAGE_HEADER))?; |
118 | | assert_eq!(baggage.cluster_id, Some("K1".into())); |
119 | | assert_eq!(baggage.namespace, Some("NS1".into())); |
120 | | assert_eq!(baggage.workload_name, Some("N1".into())); |
121 | | assert_eq!(baggage.service_name, Some("N2".into())); |
122 | | assert_eq!(baggage.revision, Some("V1".into())); |
123 | | Ok(()) |
124 | | } |
125 | | |
126 | | #[test] |
127 | | fn baggage_parser_empty_values() -> anyhow::Result<()> { |
128 | | let mut hm = HeaderMap::new(); |
129 | | let baggage_str = "k8s.cluster.name=,k8s.namespace.name=,k8s.deployment.name=,service.name=,service.version="; |
130 | | let header_value = HeaderValue::from_str(baggage_str)?; |
131 | | hm.append(BAGGAGE_HEADER, header_value); |
132 | | let baggage = parse_baggage_header(hm.get_all(BAGGAGE_HEADER))?; |
133 | | assert_eq!(baggage.cluster_id, None); |
134 | | assert_eq!(baggage.namespace, None); |
135 | | assert_eq!(baggage.workload_name, None); |
136 | | assert_eq!(baggage.service_name, None); |
137 | | assert_eq!(baggage.revision, None); |
138 | | Ok(()) |
139 | | } |
140 | | |
141 | | #[test] |
142 | | fn baggage_parser_multiline() -> anyhow::Result<()> { |
143 | | let mut hm = HeaderMap::new(); |
144 | | hm.append( |
145 | | BAGGAGE_HEADER, |
146 | | HeaderValue::from_str("k8s.cluster.name=K1")?, |
147 | | ); |
148 | | hm.append( |
149 | | BAGGAGE_HEADER, |
150 | | HeaderValue::from_str("k8s.namespace.name=NS1")?, |
151 | | ); |
152 | | hm.append( |
153 | | BAGGAGE_HEADER, |
154 | | HeaderValue::from_str("k8s.deployment.name=N1")?, |
155 | | ); |
156 | | hm.append(BAGGAGE_HEADER, HeaderValue::from_str("service.name=N2")?); |
157 | | hm.append(BAGGAGE_HEADER, HeaderValue::from_str("service.version=V1")?); |
158 | | let baggage = parse_baggage_header(hm.get_all(BAGGAGE_HEADER))?; |
159 | | assert_eq!(baggage.cluster_id, Some("K1".into())); |
160 | | assert_eq!(baggage.namespace, Some("NS1".into())); |
161 | | assert_eq!(baggage.workload_name, Some("N1".into())); |
162 | | assert_eq!(baggage.service_name, Some("N2".into())); |
163 | | assert_eq!(baggage.revision, Some("V1".into())); |
164 | | Ok(()) |
165 | | } |
166 | | |
167 | | #[test] |
168 | | fn baggage_parser_no_header() -> anyhow::Result<()> { |
169 | | let baggage = parse_baggage_header(HeaderMap::new().get_all(BAGGAGE_HEADER))?; |
170 | | assert_eq!(baggage.cluster_id, None); |
171 | | assert_eq!(baggage.namespace, None); |
172 | | assert_eq!(baggage.workload_name, None); |
173 | | assert_eq!(baggage.service_name, None); |
174 | | assert_eq!(baggage.revision, None); |
175 | | Ok(()) |
176 | | } |
177 | | |
178 | | #[test] |
179 | | fn baggage_header_val_can_be_parsed() -> anyhow::Result<()> { |
180 | | { |
181 | | let baggage = Baggage { |
182 | | ..Default::default() |
183 | | }; |
184 | | let mut hm = HeaderMap::new(); |
185 | | hm.append( |
186 | | BAGGAGE_HEADER, |
187 | | HeaderValue::from_str(&baggage_header_val(&baggage, "deployment"))?, |
188 | | ); |
189 | | let parsed = parse_baggage_header(hm.get_all(BAGGAGE_HEADER))?; |
190 | | assert_eq!(baggage, parsed); |
191 | | } |
192 | | { |
193 | | let baggage = Baggage { |
194 | | cluster_id: Some(Strng::from("cluster")), |
195 | | namespace: Some(Strng::from("default")), |
196 | | workload_name: Some(Strng::from("workload")), |
197 | | service_name: Some(Strng::from("service")), |
198 | | ..Default::default() |
199 | | }; |
200 | | let mut hm = HeaderMap::new(); |
201 | | hm.append( |
202 | | BAGGAGE_HEADER, |
203 | | HeaderValue::from_str(&baggage_header_val(&baggage, "deployment"))?, |
204 | | ); |
205 | | let parsed = parse_baggage_header(hm.get_all(BAGGAGE_HEADER))?; |
206 | | assert_eq!(baggage, parsed); |
207 | | } |
208 | | Ok(()) |
209 | | } |
210 | | } |