Files
codex/codex-rs/otel/tests/suite/buffered_operations_tests.rs
T
Abhinav 154e8736a5 Support managed OpenTelemetry metadata and PEM client identities (#57658)
## Why

Managed OpenTelemetry requirements could not specify span attributes or tracestate metadata.

## What changed

- Accept `otel.span_attributes` and `otel.tracestate` in requirements, merge them by attribute or member field, and expose them through app-server configuration and requirements reads.
- Protect required entries and their ancestors from configuration writes while preserving unmanaged sibling values and their origins. For example, requiring `otel.tracestate.vendor.tenant` leaves `vendor.region` writable. Empty metadata maps impose no requirements.
- Select Rustls when configuring an OTLP HTTP client certificate and private key so PEM client identities are accepted.

## Testing

Add coverage for metadata precedence, effective configuration, field-level write protection, and preservation of unmanaged values. Add blocking and asynchronous HTTP client construction tests using generated PEM certificates and keys.

GitOrigin-RevId: ada6000cc5e575b9a33451ec4aa58b6686dc20f7
2026-10-28 06:24:24 +00:00

127 lines
4.1 KiB
Rust

//! Exercises global operation buffering across shutdown and rejected configuration changes.
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_otel::MetricsClient;
use codex_otel::MetricsConfig;
use codex_otel::OtelExporter;
use codex_otel::OtelHttpProtocol;
use codex_otel::OtelProvider;
use codex_otel::OtelSettings;
use codex_otel::install_global_metrics;
use codex_otel::record_global_operation;
use opentelemetry_sdk::metrics::data::AggregatedMetrics;
use opentelemetry_sdk::metrics::data::MetricData;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::time::Duration;
fn install() -> MetricsClient {
install_global_metrics(
MetricsClient::new(
MetricsConfig::in_memory("test", "test", "1", Default::default()).with_runtime_reader(),
)
.expect("build in-memory metrics"),
)
}
fn observe() {
record_global_operation(
"test.operations",
"test.duration",
Duration::from_millis(/*millis*/ 7),
&[],
)
.expect("record an operation");
}
fn totals(metrics: &MetricsClient) -> (u64, u64, f64) {
let snapshot = metrics.snapshot().expect("collect runtime metrics");
let mut totals = (0, 0, 0.0);
for metric in snapshot
.scope_metrics()
.flat_map(opentelemetry_sdk::metrics::data::ScopeMetrics::metrics)
{
match metric.data() {
AggregatedMetrics::U64(MetricData::Sum(sum)) => {
totals.0 += sum
.data_points()
.map(opentelemetry_sdk::metrics::data::SumDataPoint::value)
.sum::<u64>();
}
AggregatedMetrics::F64(MetricData::Histogram(histogram)) => {
for point in histogram.data_points() {
totals.1 += point.count();
totals.2 += point.sum();
}
}
_ => panic!("unexpected metric"),
}
}
totals
}
#[test]
fn shutdown_buffers_until_replacement_and_preserves_newer_installations() {
let first = install();
first.shutdown().unwrap();
observe();
let second = install();
assert_eq!(totals(&second), (1, 1, 7.0));
let third = install();
second.shutdown().unwrap();
observe();
assert_eq!(totals(&third), (1, 1, 7.0));
third.shutdown().unwrap();
}
#[test]
fn rejected_opt_out_preserves_recording_and_accepted_opt_out_survives_shutdown() {
let metrics = install();
let mut settings = OtelSettings {
http_client_factory: HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
environment: "test".to_string(),
service_name: "test".to_string(),
service_version: "1".to_string(),
codex_home: ".".into(),
exporter: OtelExporter::None,
trace_exporter: OtelExporter::OtlpHttp {
endpoint: "http://127.0.0.1:1/v1/traces".to_string(),
headers: Default::default(),
protocol: OtelHttpProtocol::Json,
tls: None,
},
metrics_exporter: OtelExporter::None,
runtime_metrics: false,
span_attributes: HashMap::from([(String::new(), "invalid".to_string())]),
tracestate: BTreeMap::new(),
};
assert_eq!(
OtelProvider::try_new(&settings).err().unwrap().to_string(),
"configured span attribute key must not be empty"
);
observe();
assert_eq!(totals(&metrics), (1, 1, 7.0));
metrics.shutdown().unwrap();
observe();
assert!(OtelProvider::try_new(&settings).is_err());
let replacement = install();
assert_eq!(totals(&replacement), (1, 1, 7.0));
replacement.shutdown().unwrap();
settings.span_attributes.clear();
for trace_exporter in [settings.trace_exporter.clone(), OtelExporter::None] {
let metrics = install();
settings.trace_exporter = trace_exporter;
let _provider = OtelProvider::try_new(&settings).unwrap();
metrics.shutdown().unwrap();
observe();
let replacement = install();
assert_eq!(totals(&replacement), (0, 0, 0.0));
replacement.shutdown().unwrap();
}
}