Coverage Report

Created: 2026-02-14 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}