Skip to content

Commit cc71917

Browse files
authored
restack: remediation architecture + test stack (c0-c3) (#83)
* chore: add tracing migration anchor commit Apply a deliberately small, mechanical first step by migrating only the fully-finalizable logging call sites and seeding workspace tracing dependencies needed by follow-on commits. This boundary keeps entangled architectural and smoke-oriented rewrites out of the migration anchor so later commits remain reviewable by concern. Key review files: Cargo.toml, example-streamer-uses/Cargo.toml, utils/integration-test-utils/Cargo.toml, example-streamer-uses/src/bin/common/mod.rs, utils/integration-test-utils/src/integration_test_listeners.rs, utils/integration-test-utils/src/up_client_failing_register.rs, up-streamer/src/endpoint.rs. Mechanical vs behavioral: mechanical only; logging imports move to tracing where retained, and endpoint startup logging is removed because the final behavior no longer emits those startup logs. * feat: add structured observability vocabulary module Introduce a dedicated observability vocabulary leaf module so event names and field keys are centralized before broader domain rewiring. This boundary stays intentionally narrow to give reviewers a fast pass over schema definitions independent of behavior changes. Key review files: up-streamer/src/observability/events.rs, up-streamer/src/observability/fields.rs, up-streamer/src/observability/mod.rs. Mechanical vs behavioral: mostly mechanical schema introduction; behavior wiring is intentionally deferred to the architectural commit. * refactor: extract domain modules and fold subscription-cache into streamer Collapse the architecture rewrite into one compile-safe boundary that moves route lifecycle, routing, data plane, and runtime ownership behind explicit modules while updating streamer entrypoints/plugins to the new API and tracing setup. This keeps the high-coupling rewire in a single review unit and removes the standalone subscription-cache crate and path dependency in the same step to avoid half-migrated ownership. Key review files: up-streamer/src/ustreamer.rs, up-streamer/src/control_plane/*, up-streamer/src/data_plane/*, up-streamer/src/routing/*, up-streamer/src/runtime/*, up-streamer/src/lib.rs, up-streamer/Cargo.toml, Cargo.toml, configurable-streamer/src/main.rs, example-streamer-implementations/src/bin/zenoh_someip.rs, up-linux-streamer-plugin/src/lib.rs, utils/usubscription-static-file/Cargo.toml, and minimal compile-safe test API churn in up-streamer/tests/*. Mechanical vs behavioral: primarily architectural and wiring refactor; behavior-affecting changes are limited to ownership boundaries, add_route API rename alignment, reserved live_usubscription mode fail-fast handling, and mechanical test compile updates required to keep all-target checks green at this commit boundary. * test: finalize deterministic suite and static subscription behavior Advance the test-focused phase by finalizing deterministic client-run assertions in up-streamer tests, introducing explicit API contract coverage, and completing integration helper updates while migrating usubscription-static-file to tracing with cache-aware static snapshot behavior. This boundary keeps remaining churn concentrated in tests/helpers and aligns manifests with the planned async-std retirement timing. Key review files: up-streamer/tests/api_contract_forwarding_rules.rs, up-streamer/tests/support/mod.rs, up-streamer/tests/single_local_*.rs, utils/integration-test-utils/src/integration_test_utils.rs, utils/integration-test-utils/src/up_client_foo.rs, utils/usubscription-static-file/src/lib.rs, utils/integration-test-utils/Cargo.toml, utils/usubscription-static-file/Cargo.toml, Cargo.toml, Cargo.lock. Mechanical vs behavioral: includes both mechanical import/path migrations and behavioral test determinism updates; overlap on five legacy up-streamer test files is intentional and pre-approved in pr-32-approved-source-overlap.txt as commit-2 gate remediation. * refactor: align streamer code with up-rust idioms Adopt up-rust accessors and helpers across runtime code, test utilities, examples, and doctest scaffolding to standardize behavior and reduce manual protobuf traversal. Retained direct-field usage is intentional where helper equivalents are missing or less efficient. - Owned authority moves in From<UUri> conversions keep uri.authority_name to avoid extra allocation because authority_name() returns a cloned String. - Integration test ID regeneration keeps msg.attributes.as_mut() and attributes.id.0 mutation because up-rust 0.9.0 has no mutable ID setter path. - Full ue_id reads remain where up-rust only exposes split component helpers (uentity_type_id/uentity_instance_id). - Static subscription canonicalization keeps direct resource_id writes because no setter helper exists.
1 parent b6bc245 commit cc71917

59 files changed

Lines changed: 5553 additions & 2983 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 86 additions & 265 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ members = [
1919
"configurable-streamer",
2020
"up-linux-streamer-plugin",
2121
"up-streamer",
22-
"subscription-cache",
2322
"utils/usubscription-static-file",
2423
]
2524

@@ -33,7 +32,6 @@ keywords = ["uProtocol", "SDV", "routing", "streamer"]
3332
license = "Apache-2.0"
3433

3534
[workspace.dependencies]
36-
async-std = { version = "1.12.0", features = ["attributes"] }
3735
async-trait = { version = "0.1" }
3836
clap = { version = "4.5" }
3937
env_logger = { version = "0.10.1" }
@@ -43,6 +41,8 @@ log = { version = "0.4.20" }
4341
json5 = { version = "0.4.1" }
4442
serde = { version = "1.0.154", features = ["derive"] }
4543
serde_json = { version = "1.0.94" }
44+
tracing = { version = "0.1" }
45+
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
4646
uuid = { version = "1.7.0" }
4747
tokio = { version = "1.44", default-features = false, features = [
4848
"rt",

configurable-streamer/CONFIG.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
ue_version_major: 1
1717
},
1818
usubscription_config: {
19+
// Provider mode: static_file (default) or reserved live_usubscription
20+
mode: "static_file",
1921
// Lists the path to the subscription file when using static file
2022
file_path: "subscription_data.json"
2123
},

configurable-streamer/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ license.workspace = true
2222
[dependencies]
2323
async-trait = { workspace = true }
2424
clap = { workspace = true }
25-
env_logger = { workspace = true }
26-
log = { workspace = true }
2725
json5 = { workspace = true }
2826
protobuf = { workspace = true }
2927
serde = { workspace = true }
30-
tokio = { workspace = true }
28+
tokio = { workspace = true, features = ["signal"] }
29+
tracing = { workspace = true }
30+
tracing-subscriber = { workspace = true }
3131
up-rust = { workspace = true }
3232
up-streamer = { path = "../up-streamer" }
3333
up-transport-zenoh = { workspace = true }

configurable-streamer/src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,19 @@ pub struct StreamerUuri {
3939
#[derive(Deserialize, Serialize, Debug, Clone)]
4040
#[serde(deny_unknown_fields)]
4141
pub struct USubscriptionConfig {
42+
#[serde(default)]
43+
pub(crate) mode: SubscriptionProviderMode,
4244
pub(crate) file_path: String,
4345
}
4446

47+
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
48+
#[serde(rename_all = "snake_case")]
49+
pub enum SubscriptionProviderMode {
50+
#[default]
51+
StaticFile,
52+
LiveUsubscription,
53+
}
54+
4555
#[derive(Deserialize, Serialize, Debug, Clone)]
4656
#[serde(deny_unknown_fields)]
4757
pub struct Transports {

configurable-streamer/src/main.rs

Lines changed: 140 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
mod config;
1515

16-
use crate::config::Config;
16+
use crate::config::{Config, EndpointConfig, SubscriptionProviderMode};
1717
use clap::Parser;
18-
use log::info;
1918
use std::io::Read;
2019
use std::sync::Arc;
21-
use std::thread;
2220
use std::{collections::HashMap, fs::File};
21+
use tracing::info;
22+
use up_rust::core::usubscription::USubscription;
2323
use up_rust::{UCode, UStatus, UTransport};
2424
use up_streamer::{Endpoint, UStreamer};
2525
use up_transport_mqtt5::{Mqtt5Transport, Mqtt5TransportOptions, MqttClientOptions};
@@ -33,9 +33,79 @@ struct StreamerArgs {
3333
config: String,
3434
}
3535

36+
fn register_transport_endpoints(
37+
endpoints: &mut HashMap<String, Endpoint>,
38+
endpoint_configs: &[EndpointConfig],
39+
transport: Arc<dyn UTransport>,
40+
) -> Result<(), UStatus> {
41+
for endpoint_config in endpoint_configs {
42+
let endpoint = Endpoint::new(
43+
&endpoint_config.endpoint,
44+
&endpoint_config.authority,
45+
transport.clone(),
46+
);
47+
48+
if endpoints
49+
.insert(endpoint_config.endpoint.clone(), endpoint)
50+
.is_some()
51+
{
52+
return Err(UStatus::fail_with_code(
53+
UCode::INVALID_ARGUMENT,
54+
format!(
55+
"Duplicate endpoint name found: {}",
56+
endpoint_config.endpoint
57+
),
58+
));
59+
}
60+
}
61+
62+
Ok(())
63+
}
64+
65+
async fn wire_forwarding_rules(
66+
streamer: &mut UStreamer,
67+
endpoints: &HashMap<String, Endpoint>,
68+
endpoint_configs: &[EndpointConfig],
69+
) -> Result<(), UStatus> {
70+
for endpoint_config in endpoint_configs {
71+
for forwarding_target in &endpoint_config.forwarding {
72+
let left_endpoint = endpoints.get(&endpoint_config.endpoint).ok_or_else(|| {
73+
UStatus::fail_with_code(
74+
UCode::INVALID_ARGUMENT,
75+
format!(
76+
"Unknown endpoint in forwarding rules: {}",
77+
endpoint_config.endpoint
78+
),
79+
)
80+
})?;
81+
let right_endpoint = endpoints.get(forwarding_target).ok_or_else(|| {
82+
UStatus::fail_with_code(
83+
UCode::INVALID_ARGUMENT,
84+
format!("Unknown forwarding target endpoint: {forwarding_target}"),
85+
)
86+
})?;
87+
88+
streamer
89+
.add_route_ref(left_endpoint, right_endpoint)
90+
.await?;
91+
}
92+
}
93+
94+
Ok(())
95+
}
96+
97+
async fn wait_for_shutdown_signal() -> Result<(), UStatus> {
98+
tokio::signal::ctrl_c().await.map_err(|error| {
99+
UStatus::fail_with_code(
100+
UCode::INTERNAL,
101+
format!("Unable to wait for shutdown signal: {error:?}"),
102+
)
103+
})
104+
}
105+
36106
#[tokio::main]
37107
async fn main() -> Result<(), UStatus> {
38-
env_logger::init();
108+
let _ = tracing_subscriber::fmt::try_init();
39109

40110
info!("Started up-linux-streamer-configurable");
41111

@@ -57,49 +127,71 @@ async fn main() -> Result<(), UStatus> {
57127
format!("Unable to parse config file: {e:?}"),
58128
)
59129
})?;
60-
config.transports.mqtt.load_mqtt_details().unwrap();
130+
config.transports.mqtt.load_mqtt_details().map_err(|e| {
131+
UStatus::fail_with_code(
132+
UCode::INVALID_ARGUMENT,
133+
format!("Unable to load MQTT transport details: {e:?}"),
134+
)
135+
})?;
61136

62-
let subscription_path = config.usubscription_config.file_path;
63-
let usubscription = Arc::new(USubscriptionStaticFile::new(subscription_path));
137+
let usubscription: Arc<dyn USubscription> = match config.usubscription_config.mode {
138+
SubscriptionProviderMode::StaticFile => Arc::new(USubscriptionStaticFile::new(
139+
config.usubscription_config.file_path.clone(),
140+
)),
141+
SubscriptionProviderMode::LiveUsubscription => {
142+
return Err(UStatus::fail_with_code(
143+
UCode::UNIMPLEMENTED,
144+
"live_usubscription mode is reserved in this phase; live runtime integration is deferred (see reports/usubscription-decoupled-pubsub-migration/05-live-integration-deferred.md)",
145+
));
146+
}
147+
};
64148

65149
// Start the streamer instance.
66150
let mut streamer = UStreamer::new(
67151
"up-streamer",
68152
config.up_streamer_config.message_queue_size,
69153
usubscription,
70154
)
71-
.expect("Failed to create uStreamer");
155+
.await?;
72156

73157
let mut endpoints: HashMap<String, Endpoint> = HashMap::new();
74158

75159
// build the zenoh transport
76-
let zenoh_config = ZenohConfig::from_file(config.transports.zenoh.config_file).unwrap();
160+
let zenoh_config =
161+
ZenohConfig::from_file(config.transports.zenoh.config_file).map_err(|e| {
162+
UStatus::fail_with_code(
163+
UCode::INVALID_ARGUMENT,
164+
format!("Unable to load Zenoh config file: {e:?}"),
165+
)
166+
})?;
77167
let zenoh_transport: Arc<dyn UTransport> = Arc::new(
78168
UPTransportZenoh::builder(config.streamer_uuri.authority.clone())
79-
.expect("Unable to create Zenoh transport builder")
169+
.map_err(|e| {
170+
UStatus::fail_with_code(
171+
UCode::INTERNAL,
172+
format!("Unable to create Zenoh transport builder: {e:?}"),
173+
)
174+
})?
80175
.with_config(zenoh_config)
81176
.build()
82177
.await
83-
.expect("Unable to initialize Zenoh UTransport"),
178+
.map_err(|e| {
179+
UStatus::fail_with_code(
180+
UCode::INTERNAL,
181+
format!("Unable to initialize Zenoh UTransport: {e:?}"),
182+
)
183+
})?,
84184
);
85185

86186
// build the mqtt5 transport
187+
let mqtt_details = config.transports.mqtt.mqtt_details.clone().ok_or_else(|| {
188+
UStatus::fail_with_code(
189+
UCode::INVALID_ARGUMENT,
190+
"MQTT transport details are missing after load_mqtt_details",
191+
)
192+
})?;
87193
let mqtt_client_options = MqttClientOptions {
88-
broker_uri: config
89-
.transports
90-
.mqtt
91-
.mqtt_details
92-
.clone()
93-
.unwrap()
94-
.hostname
95-
+ ":"
96-
+ &config
97-
.transports
98-
.mqtt
99-
.mqtt_details
100-
.unwrap()
101-
.port
102-
.to_string(),
194+
broker_uri: format!("{}:{}", mqtt_details.hostname, mqtt_details.port),
103195
..Default::default()
104196
};
105197
let mqtt_transport_options = Mqtt5TransportOptions {
@@ -114,73 +206,29 @@ async fn main() -> Result<(), UStatus> {
114206
mqtt5_transport.connect().await?;
115207
let mqtt5_transport: Arc<dyn UTransport> = Arc::new(mqtt5_transport);
116208

117-
// build all zenoh endpoints
118-
for zenoh_endpoint_config in config.transports.zenoh.endpoints.clone() {
119-
let endpoint = Endpoint::new(
120-
&zenoh_endpoint_config.endpoint,
121-
&zenoh_endpoint_config.authority,
122-
zenoh_transport.clone(),
123-
);
124-
if endpoints
125-
.insert(zenoh_endpoint_config.endpoint.clone(), endpoint)
126-
.is_some()
127-
{
128-
return Err(UStatus::fail_with_code(
129-
UCode::INVALID_ARGUMENT,
130-
format!(
131-
"Duplicate endpoint name found: {}",
132-
zenoh_endpoint_config.endpoint
133-
),
134-
));
135-
}
136-
}
137-
138-
// build all mqtt endpoints
139-
for mqtt_endpoint_config in config.transports.mqtt.endpoints.clone() {
140-
let endpoint = Endpoint::new(
141-
&mqtt_endpoint_config.endpoint,
142-
&mqtt_endpoint_config.authority,
143-
mqtt5_transport.clone(),
144-
);
145-
if endpoints
146-
.insert(mqtt_endpoint_config.endpoint.clone(), endpoint)
147-
.is_some()
148-
{
149-
return Err(UStatus::fail_with_code(
150-
UCode::INVALID_ARGUMENT,
151-
format!(
152-
"Duplicate endpoint name found: {}",
153-
mqtt_endpoint_config.endpoint
154-
),
155-
));
156-
}
157-
}
158-
159-
// set up the endpoint forwarding for zenoh
160-
for zenoh_endpoint in config.transports.zenoh.endpoints {
161-
for forwarding in zenoh_endpoint.forwarding {
162-
let left_endpoint = endpoints.get(&zenoh_endpoint.endpoint).unwrap();
163-
let right_endpoint = endpoints.get(&forwarding).unwrap();
164-
streamer
165-
.add_forwarding_rule(left_endpoint.to_owned(), right_endpoint.to_owned())
166-
.await
167-
.expect("Could not add forwarding rule from {zenoh.endpoint} to {forwarding}");
168-
}
169-
}
170-
171-
// set up the endpoint forwarding for mqtt
172-
for mqtt5_endpoint in config.transports.mqtt.endpoints {
173-
for forwarding in mqtt5_endpoint.forwarding {
174-
let left_endpoint = endpoints.get(&mqtt5_endpoint.endpoint).unwrap();
175-
let right_endpoint = endpoints.get(&forwarding).unwrap();
176-
streamer
177-
.add_forwarding_rule(left_endpoint.to_owned(), right_endpoint.to_owned())
178-
.await
179-
.expect("Could not add forwarding rule from {mqtt.endpoint} to {forwarding}");
180-
}
181-
}
209+
register_transport_endpoints(
210+
&mut endpoints,
211+
&config.transports.zenoh.endpoints,
212+
zenoh_transport,
213+
)?;
214+
register_transport_endpoints(
215+
&mut endpoints,
216+
&config.transports.mqtt.endpoints,
217+
mqtt5_transport,
218+
)?;
219+
220+
wire_forwarding_rules(
221+
&mut streamer,
222+
&endpoints,
223+
&config.transports.zenoh.endpoints,
224+
)
225+
.await?;
226+
wire_forwarding_rules(&mut streamer, &endpoints, &config.transports.mqtt.endpoints).await?;
182227

183-
thread::park();
228+
println!("READY streamer_initialized");
229+
info!("Streamer initialized; waiting for shutdown signal");
230+
wait_for_shutdown_signal().await?;
231+
info!("Shutdown signal received; exiting");
184232

185233
Ok(())
186234
}

example-streamer-implementations/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ bundled-vsomeip = ["up-transport-vsomeip/bundled"]
3333
async-trait = { workspace = true }
3434
chrono = { version = "0.4" }
3535
clap = { version = "4.5.9", features = ["derive"] }
36-
env_logger = { version = "0.10.2" }
37-
log = { workspace = true }
36+
tracing = { workspace = true }
37+
tracing-subscriber = { workspace = true }
3838
json5 = { workspace = true }
3939
protobuf = { workspace = true }
4040
serde = { workspace = true }
41-
tokio = { workspace = true }
41+
tokio = { workspace = true, features = ["signal"] }
4242
up-rust = { workspace = true }
4343
up-streamer = { path = "../up-streamer" }
4444
up-transport-zenoh = { workspace = true, optional = true }

example-streamer-implementations/DEFAULT_CONFIG.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
ue_version_major: 1
2121
},
2222
usubscription_config: {
23+
// Provider mode: static_file (default) or reserved live_usubscription
24+
mode: "static_file",
2325
// Lists the path to the subscription file when using static file
2426
file_path: "../example-streamer-implementations/subscription_data.json"
2527
},

0 commit comments

Comments
 (0)