Skip to content

Commit a29c639

Browse files
authored
[otap-dataflow ] support additional validation methods in the validation framework (open-telemetry#2027)
# Change Summary Updated the validation exporter to perform different validation checks (equivalence, signal drop, attribute, batch size) - users can configure the validation framework to perform a list of validation checks for their testing - Defined ValidationKind enum to describe various validations to make 1. ValidationKind::SignalDrop -> checks for any change in signal count 2. ValidationKind::Attribute -> checks for existence of key or key values and can check for nonexistent keys 3. ValidationKind::Batch -> check for correct batch sizes 4. ValidationKind::Equivalence -> checks that message hasn't been transformed ## What issue does this PR close? related to open-telemetry#2008 ## How are these changes tested? Added tests for each of the new validation check methods. Added example validation framework tests for filter processor and attribute processor pipelines ## Are there any user-facing changes? No
1 parent 17999b7 commit a29c639

23 files changed

Lines changed: 1644 additions & 138 deletions

File tree

rust/otap-dataflow/crates/pdata/src/otap/filter.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ pub enum AnyValue {
6464
/// struct that represents attributes and other key value pairs
6565
#[derive(Debug, Clone, PartialEq, Deserialize)]
6666
pub struct KeyValue {
67-
key: String,
68-
value: AnyValue,
67+
/// Attribute key.
68+
pub key: String,
69+
/// Attribute value.
70+
pub value: AnyValue,
6971
}
7072

7173
impl KeyValue {

rust/otap-dataflow/crates/pdata/src/testing/equiv/canonical.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,38 @@ pub(crate) fn assert_equivalent<T, F, G>(
161161
message_name
162162
);
163163
}
164+
165+
/// Generic equivalence assertion for any OTLP signal type.
166+
pub(crate) fn validate_equivalent<T, F, G>(
167+
left: &[T],
168+
right: &[T],
169+
split_fn: F,
170+
canonicalize_fn: G,
171+
) -> bool
172+
where
173+
T: Message + Clone + std::fmt::Debug + PartialEq,
174+
F: Fn(&T) -> Vec<T>,
175+
G: Fn(&mut T),
176+
{
177+
// Split into singletons from all messages in the slices
178+
let mut left_singletons: Vec<T> = left.iter().flat_map(&split_fn).collect();
179+
let mut right_singletons: Vec<T> = right.iter().flat_map(&split_fn).collect();
180+
181+
// Canonicalize each singleton
182+
for singleton in &mut left_singletons {
183+
canonicalize_fn(singleton);
184+
}
185+
for singleton in &mut right_singletons {
186+
canonicalize_fn(singleton);
187+
}
188+
189+
// Encode to bytes and collect into BTreeSets
190+
let left_set: BTreeSet<Vec<u8>> = left_singletons.iter().map(canonicalize_message).collect();
191+
let right_set: BTreeSet<Vec<u8>> = right_singletons.iter().map(canonicalize_message).collect();
192+
193+
// Use pretty_assertions for nice diff output
194+
if left_set == right_set {
195+
return true;
196+
}
197+
false
198+
}

rust/otap-dataflow/crates/pdata/src/testing/equiv/logs.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::proto::opentelemetry::logs::v1::{LogsData, ResourceLogs, ScopeLogs};
77
use crate::testing::equiv::canonical::{
88
assert_equivalent, canonicalize_any_value, canonicalize_resource, canonicalize_scope,
9-
canonicalize_vec,
9+
canonicalize_vec, validate_equivalent,
1010
};
1111

1212
/// Split a LogsData into individual singleton LogsData messages (one per log record).
@@ -73,6 +73,16 @@ pub fn assert_logs_equivalent(left: &[LogsData], right: &[LogsData]) {
7373
);
7474
}
7575

76+
/// Validate that two collections of `LogsData` instances are equivalent.
77+
pub fn validate_logs_equivalent(left: &[LogsData], right: &[LogsData]) -> bool {
78+
validate_equivalent(
79+
left,
80+
right,
81+
logs_split_into_singletons,
82+
logs_canonicalize_singleton_in_place,
83+
)
84+
}
85+
7686
#[cfg(test)]
7787
mod tests {
7888
use super::*;
@@ -254,6 +264,10 @@ mod tests {
254264
}],
255265
};
256266

267+
assert!(validate_logs_equivalent(
268+
std::slice::from_ref(&request1),
269+
std::slice::from_ref(&request2)
270+
));
257271
assert_logs_equivalent(&[request1], &[request2]);
258272
}
259273
}

rust/otap-dataflow/crates/pdata/src/testing/equiv/metrics.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::proto::opentelemetry::metrics::v1::{
1010
};
1111
use crate::testing::equiv::canonical::{
1212
assert_equivalent, canonicalize_any_value, canonicalize_resource, canonicalize_scope,
13-
canonicalize_vec,
13+
canonicalize_vec, validate_equivalent,
1414
};
1515

1616
/// Split a MetricsData into individual singleton MetricsData messages (one per data point).
@@ -287,6 +287,16 @@ pub fn assert_metrics_equivalent(left: &[MetricsData], right: &[MetricsData]) {
287287
);
288288
}
289289

290+
/// Validate that two collections of `MetricsData` instances are equivalent.
291+
pub fn validate_metrics_equivalent(left: &[MetricsData], right: &[MetricsData]) -> bool {
292+
validate_equivalent(
293+
left,
294+
right,
295+
metrics_split_into_singletons,
296+
metrics_canonicalize_singleton,
297+
)
298+
}
299+
290300
#[cfg(test)]
291301
mod tests {
292302
use super::*;
@@ -377,6 +387,10 @@ mod tests {
377387
}],
378388
};
379389

390+
assert!(validate_metrics_equivalent(
391+
std::slice::from_ref(&request1),
392+
std::slice::from_ref(&request2)
393+
));
380394
assert_metrics_equivalent(&[request1], &[request2]);
381395
}
382396
}

rust/otap-dataflow/crates/pdata/src/testing/equiv/mod.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ use crate::proto::opentelemetry::logs::v1::LogsData;
2525
use crate::proto::opentelemetry::metrics::v1::MetricsData;
2626
use crate::proto::opentelemetry::trace::v1::TracesData;
2727

28-
use logs::assert_logs_equivalent;
29-
use metrics::assert_metrics_equivalent;
30-
use traces::assert_traces_equivalent;
28+
use logs::{assert_logs_equivalent, validate_logs_equivalent};
29+
use metrics::{assert_metrics_equivalent, validate_metrics_equivalent};
30+
use traces::{assert_traces_equivalent, validate_traces_equivalent};
3131

3232
fn otap_to_otlp_logs(msg: &OtlpProtoMessage) -> LogsData {
3333
match msg {
@@ -70,3 +70,24 @@ pub fn assert_equivalent(left: &[OtlpProtoMessage], right: &[OtlpProtoMessage])
7070
),
7171
}
7272
}
73+
74+
/// Validate that two OTLP protocol message slices contain equivalent data.
75+
/// Requires the inputs to have a single signal type.
76+
pub fn validate_equivalent(left: &[OtlpProtoMessage], right: &[OtlpProtoMessage]) -> bool {
77+
let signal_type = left.first().expect("at least one input").signal_type();
78+
79+
match signal_type {
80+
SignalType::Logs => validate_logs_equivalent(
81+
&left.iter().map(otap_to_otlp_logs).collect::<Vec<_>>(),
82+
&right.iter().map(otap_to_otlp_logs).collect::<Vec<_>>(),
83+
),
84+
SignalType::Metrics => validate_metrics_equivalent(
85+
&left.iter().map(otap_to_otlp_metrics).collect::<Vec<_>>(),
86+
&right.iter().map(otap_to_otlp_metrics).collect::<Vec<_>>(),
87+
),
88+
SignalType::Traces => validate_traces_equivalent(
89+
&left.iter().map(otap_to_otlp_traces).collect::<Vec<_>>(),
90+
&right.iter().map(otap_to_otlp_traces).collect::<Vec<_>>(),
91+
),
92+
}
93+
}

rust/otap-dataflow/crates/pdata/src/testing/equiv/traces.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::proto::opentelemetry::trace::v1::{ResourceSpans, ScopeSpans, Status, TracesData};
77
use crate::testing::equiv::canonical::{
88
assert_equivalent, canonicalize_any_value, canonicalize_idvec, canonicalize_resource,
9-
canonicalize_scope, canonicalize_vec,
9+
canonicalize_scope, canonicalize_vec, validate_equivalent,
1010
};
1111

1212
/// Split a TracesData into individual singleton TracesData messages (one per span).
@@ -96,6 +96,16 @@ pub fn assert_traces_equivalent(left: &[TracesData], right: &[TracesData]) {
9696
);
9797
}
9898

99+
/// Validate that two collections of `TracesData` instances are equivalent.
100+
pub fn validate_traces_equivalent(left: &[TracesData], right: &[TracesData]) -> bool {
101+
validate_equivalent(
102+
left,
103+
right,
104+
traces_split_into_singletons,
105+
traces_canonicalize_singleton_in_place,
106+
)
107+
}
108+
99109
#[cfg(test)]
100110
mod tests {
101111
use super::*;
@@ -278,6 +288,10 @@ mod tests {
278288
}],
279289
};
280290

291+
assert!(validate_traces_equivalent(
292+
std::slice::from_ref(&request1),
293+
std::slice::from_ref(&request2)
294+
));
281295
assert_traces_equivalent(&[request1], &[request2]);
282296
}
283297
}

rust/otap-dataflow/crates/validation/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde_json = { workspace = true }
2121
tokio-util = { workspace = true }
2222
tokio = { workspace = true }
2323
portpicker = { workspace = true }
24+
humantime-serde = { workspace = true }
2425

2526
minijinja = "1"
2627
reqwest = { version = "0.12", features = ["json"] }
@@ -39,6 +40,7 @@ otap-df-telemetry = { path = "../telemetry" }
3940
otap-df-telemetry-macros = { path = "../telemetry-macros" }
4041
otap-df-otap = { path = "../otap" }
4142
otap-df-controller = { path = "../controller" }
43+
prost = { workspace = true }
4244

4345
[dev-dependencies]
4446
tempfile = "3"

0 commit comments

Comments
 (0)