From 98ce8024237c2d895762071f10e148062c9e0397 Mon Sep 17 00:00:00 2001 From: Krzysztof Piotrowski Date: Tue, 31 Oct 2023 19:46:37 +0000 Subject: [PATCH 1/5] Add ID generator for command operation Signed-off-by: Krzysztof Piotrowski --- crates/core/tedge_api/src/mqtt_topics.rs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/core/tedge_api/src/mqtt_topics.rs b/crates/core/tedge_api/src/mqtt_topics.rs index dc104f1cfc2..4fcc61357e4 100644 --- a/crates/core/tedge_api/src/mqtt_topics.rs +++ b/crates/core/tedge_api/src/mqtt_topics.rs @@ -8,6 +8,8 @@ use std::convert::Infallible; use std::fmt::Display; use std::fmt::Formatter; use std::str::FromStr; +use time::format_description; +use time::OffsetDateTime; const ENTITY_ID_SEGMENTS: usize = 4; @@ -644,6 +646,32 @@ pub enum ChannelFilter { CommandMetadata(OperationType), } +pub struct IdGenerator { + prefix: String, +} + +impl IdGenerator { + pub fn new(prefix: &str) -> Self { + IdGenerator { + prefix: prefix.into(), + } + } + + pub fn new_id(&self) -> String { + format!( + "{}-{}", + self.prefix, + OffsetDateTime::now_utc() + .format(&format_description::well_known::Rfc3339) + .unwrap(), + ) + } + + pub fn is_generator_of(&self, cmd_id: &str) -> bool { + cmd_id.contains(&self.prefix) + } +} + #[cfg(test)] mod tests { use super::*; From dccd6aad84d358e3db5f780ab7acd685ddc7db70 Mon Sep 17 00:00:00 2001 From: Krzysztof Piotrowski Date: Fri, 27 Oct 2023 14:19:30 +0000 Subject: [PATCH 2/5] Add requester prefix with timestamp to log and config operations' topics Signed-off-by: Krzysztof Piotrowski --- Cargo.lock | 1 - crates/extensions/c8y_mapper_ext/Cargo.toml | 1 - .../c8y_mapper_ext/src/config_operations.rs | 67 +++++++++---------- .../c8y_mapper_ext/src/converter.rs | 12 +++- .../c8y_mapper_ext/src/log_upload.rs | 3 +- crates/extensions/c8y_mapper_ext/src/tests.rs | 24 +++---- 6 files changed, 55 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9bf03a1cc9..5ca3d8d919c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,7 +769,6 @@ dependencies = [ "clock", "json-writer", "logged_command", - "nanoid", "plugin_sm", "proptest", "rand", diff --git a/crates/extensions/c8y_mapper_ext/Cargo.toml b/crates/extensions/c8y_mapper_ext/Cargo.toml index cc1cd47b622..e32c56b164d 100644 --- a/crates/extensions/c8y_mapper_ext/Cargo.toml +++ b/crates/extensions/c8y_mapper_ext/Cargo.toml @@ -19,7 +19,6 @@ camino = { workspace = true } clock = { workspace = true } json-writer = { workspace = true } logged_command = { workspace = true } -nanoid = { workspace = true } plugin_sm = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/extensions/c8y_mapper_ext/src/config_operations.rs b/crates/extensions/c8y_mapper_ext/src/config_operations.rs index 4b3ae37e412..30ba35b041d 100644 --- a/crates/extensions/c8y_mapper_ext/src/config_operations.rs +++ b/crates/extensions/c8y_mapper_ext/src/config_operations.rs @@ -10,7 +10,6 @@ use c8y_api::smartrest::smartrest_serializer::SmartRestSerializer; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToExecuting; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToFailed; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToSuccessful; -use nanoid::nanoid; use sha256::digest; use std::fs; use std::io; @@ -78,7 +77,7 @@ impl CumulocityConverter { .entity_store .try_get_by_external_id(&snapshot_request.device.clone().into())?; - let cmd_id = nanoid!(); + let cmd_id = self.command_id.new_id(); let channel = Channel::Command { operation: OperationType::ConfigSnapshot, cmd_id: cmd_id.clone(), @@ -232,7 +231,7 @@ impl CumulocityConverter { .entity_store .try_get_by_external_id(&smartrest.device.clone().into())?; - let cmd_id = nanoid!(); + let cmd_id = self.command_id.new_id(); let remote_url = smartrest.url.as_str(); let file_cache_key = digest(remote_url); let file_cache_path = self.config.data_dir.cache_dir().join(file_cache_key); @@ -773,10 +772,10 @@ mod tests { // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/typeA-c8y-mapper-1234", "type": "typeA", }) .to_string(), @@ -789,10 +788,10 @@ mod tests { // Simulate config_snapshot command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/typeA-c8y-mapper-1234", "type": "typeA", "reason": "Something went wrong" }) @@ -832,10 +831,10 @@ mod tests { // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-c8y-mapper-1234", "type": "typeA", }) .to_string(), @@ -849,10 +848,10 @@ mod tests { // Simulate config_snapshot command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-c8y-mapper-1234"), "type": "typeA", "reason": "Something went wrong" }) @@ -885,14 +884,14 @@ mod tests { ttd.dir("file-transfer") .dir("test-device") .dir("config_snapshot") - .file("path:type:A-1234"); + .file("path:type:A-c8y-mapper-1234"); // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/path:type:A-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_snapshot/path:type:A-c8y-mapper-1234", "type": "path/type/A", }) .to_string(), @@ -931,14 +930,14 @@ mod tests { ttd.dir("file-transfer") .dir("child1") .dir("config_snapshot") - .file("typeA-1234"); + .file("typeA-c8y-mapper-1234"); // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_snapshot/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_snapshot/typeA-c8y-mapper-1234", "type": "typeA", }) .to_string(), @@ -1112,18 +1111,18 @@ mod tests { ttd.dir("file-transfer") .dir("test-device") .dir("config_update") - .file("typeA-1234"); + .file("typeA-c8y-mapper-1234"); assert!(ttd .path() - .join("file-transfer/test-device/config_update/typeA-1234") + .join("file-transfer/test-device/config_update/typeA-c8y-mapper-1234") .exists()); // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_update/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/typeA-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "typeA", }) @@ -1137,10 +1136,10 @@ mod tests { // Simulate config_update command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_update/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/typeA-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "typeA", "reason": "Something went wrong" @@ -1163,7 +1162,7 @@ mod tests { // Assert symlink is removed assert!(!ttd .path() - .join("file-transfer/test-device/config_update/typeA-1234") + .join("file-transfer/test-device/config_update/typeA-c8y-mapper-1234") .exists()); } @@ -1187,10 +1186,10 @@ mod tests { // Simulate config_snapshot command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_update/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "typeA", }) @@ -1208,10 +1207,10 @@ mod tests { // Simulate config_update command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_update/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "typeA", "reason": "Something went wrong" @@ -1244,18 +1243,18 @@ mod tests { ttd.dir("file-transfer") .dir("test-device") .dir("config_update") - .file("path:type:A-1234"); + .file("path:type:A-c8y-mapper-1234"); assert!(ttd .path() - .join("file-transfer/test-device/config_update/path:type:A-1234") + .join("file-transfer/test-device/config_update/path:type:A-c8y-mapper-1234") .exists()); // Simulate config_update command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/main///cmd/config_update/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/path:type:A-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/test-device/config_update/path:type:A-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "path/type/A", }) @@ -1294,10 +1293,10 @@ mod tests { // Simulate config_update command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/config_update/1234"), + &Topic::new_unchecked("te/device/child1///cmd/config_update/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-1234", + "tedgeUrl": "http://localhost:8888/tedge/file-transfer/child1/config_update/typeA-c8y-mapper-1234", "remoteUrl": "http://www.my.url", "type": "typeA", }) diff --git a/crates/extensions/c8y_mapper_ext/src/converter.rs b/crates/extensions/c8y_mapper_ext/src/converter.rs index 23d9ff87c5c..21b2c3f38b9 100644 --- a/crates/extensions/c8y_mapper_ext/src/converter.rs +++ b/crates/extensions/c8y_mapper_ext/src/converter.rs @@ -72,6 +72,7 @@ use tedge_api::messages::SoftwareListCommand; use tedge_api::messages::SoftwareUpdateCommand; use tedge_api::mqtt_topics::Channel; use tedge_api::mqtt_topics::EntityTopicId; +use tedge_api::mqtt_topics::IdGenerator; use tedge_api::mqtt_topics::MqttSchema; use tedge_api::mqtt_topics::OperationType; use tedge_api::DownloadInfo; @@ -98,6 +99,7 @@ const TEDGE_AGENT_LOG_DIR: &str = "tedge/agent"; const CREATE_EVENT_SMARTREST_CODE: u16 = 400; const DEFAULT_EVENT_TYPE: &str = "ThinEdgeEvent"; const FORBIDDEN_ID_CHARS: [char; 3] = ['/', '+', '#']; +const REQUESTER_NAME: &str = "c8y-mapper"; #[derive(Debug)] pub struct MapperConfig { @@ -177,6 +179,7 @@ pub struct CumulocityConverter { pub auth_proxy: ProxyUrlGenerator, pub downloader_sender: LoggingSender, pub pending_operations: HashMap, + pub command_id: IdGenerator, } impl CumulocityConverter { @@ -229,6 +232,8 @@ impl CumulocityConverter { ) .unwrap(); + let command_id = IdGenerator::new(REQUESTER_NAME); + Ok(CumulocityConverter { size_threshold, config, @@ -251,6 +256,7 @@ impl CumulocityConverter { auth_proxy, downloader_sender, pending_operations: HashMap::new(), + command_id, }) } @@ -954,7 +960,7 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::LogUpload, cmd_id, - } => { + } if self.command_id.is_generator_of(cmd_id) => { self.handle_log_upload_state_change(&source, cmd_id, message) .await? } @@ -965,7 +971,7 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::ConfigSnapshot, cmd_id, - } => { + } if self.command_id.is_generator_of(cmd_id) => { self.handle_config_snapshot_state_change(&source, cmd_id, message) .await? } @@ -976,7 +982,7 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::ConfigUpdate, cmd_id, - } => { + } if self.command_id.is_generator_of(cmd_id) => { self.handle_config_update_state_change(&source, cmd_id, message) .await? } diff --git a/crates/extensions/c8y_mapper_ext/src/log_upload.rs b/crates/extensions/c8y_mapper_ext/src/log_upload.rs index d543a6d368f..ccc27a9d57b 100644 --- a/crates/extensions/c8y_mapper_ext/src/log_upload.rs +++ b/crates/extensions/c8y_mapper_ext/src/log_upload.rs @@ -8,7 +8,6 @@ use c8y_api::smartrest::smartrest_serializer::SmartRestSerializer; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToExecuting; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToFailed; use c8y_api::smartrest::smartrest_serializer::SmartRestSetOperationToSuccessful; -use nanoid::nanoid; use tedge_api::entity_store::EntityType; use tedge_api::messages::CommandStatus; use tedge_api::messages::LogMetadata; @@ -57,7 +56,7 @@ impl CumulocityConverter { .entity_store .try_get_by_external_id(&device_external_id)?; - let cmd_id = nanoid!(); + let cmd_id = self.command_id.new_id(); let channel = Channel::Command { operation: OperationType::LogUpload, cmd_id: cmd_id.clone(), diff --git a/crates/extensions/c8y_mapper_ext/src/tests.rs b/crates/extensions/c8y_mapper_ext/src/tests.rs index acac890e403..a3f7247647e 100644 --- a/crates/extensions/c8y_mapper_ext/src/tests.rs +++ b/crates/extensions/c8y_mapper_ext/src/tests.rs @@ -2431,10 +2431,10 @@ async fn handle_log_upload_executing_and_failed_cmd_for_main_device() { // Simulate log_upload command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/main///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", @@ -2451,10 +2451,10 @@ async fn handle_log_upload_executing_and_failed_cmd_for_main_device() { // Simulate log_upload command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/main///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", @@ -2488,10 +2488,10 @@ async fn handle_log_upload_executing_and_failed_cmd_for_child_device() { // Simulate log_upload command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/child1///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "executing", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", @@ -2534,10 +2534,10 @@ async fn handle_log_upload_executing_and_failed_cmd_for_child_device() { // Simulate log_upload command with "failed" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/child1///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "failed", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", @@ -2579,10 +2579,10 @@ async fn handle_log_upload_successful_cmd_for_main_device() { // Simulate log_upload command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/main///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/main///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/main/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", @@ -2620,10 +2620,10 @@ async fn handle_log_upload_successful_cmd_for_child_device() { // Simulate log_upload command with "executing" state mqtt.send(MqttMessage::new( - &Topic::new_unchecked("te/device/child1///cmd/log_upload/1234"), + &Topic::new_unchecked("te/device/child1///cmd/log_upload/c8y-mapper-1234"), json!({ "status": "successful", - "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-1234"), + "tedgeUrl": format!("http://localhost:8888/tedge/file-transfer/child1/log_upload/typeA-c8y-mapper-1234"), "type": "typeA", "dateFrom": "2013-06-22T17:03:14.123+02:00", "dateTo": "2013-06-23T18:03:14.123+02:00", From 468726085a3ede30c2996c13aa688a2d071adf49 Mon Sep 17 00:00:00 2001 From: Krzysztof Piotrowski Date: Mon, 30 Oct 2023 18:06:52 +0000 Subject: [PATCH 3/5] Add system test for requester prefix check Signed-off-by: Krzysztof Piotrowski --- .../tests/cumulocity/log/log_operation.robot | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/RobotFramework/tests/cumulocity/log/log_operation.robot b/tests/RobotFramework/tests/cumulocity/log/log_operation.robot index 54ef5f5482b..d87880d8ef1 100644 --- a/tests/RobotFramework/tests/cumulocity/log/log_operation.robot +++ b/tests/RobotFramework/tests/cumulocity/log/log_operation.robot @@ -30,13 +30,26 @@ Request with non-existing log type ... failure_reason=.*No such file or directory for log type: example1 ... timeout=120 +Manual log operation request + Execute Command sudo -u tedge mkdir -p /var/tedge/file-transfer/${DEVICE_SN}/log_upload + Execute Command sudo -u tedge touch /var/tedge/file-transfer/${DEVICE_SN}/log_upload/example-1234 + ${start_timestamp}= Get Current Date UTC -24 hours result_format=%Y-%m-%dT%H:%M:%SZ + ${end_timestamp}= Get Current Date UTC +60 seconds result_format=%Y-%m-%dT%H:%M:%SZ + Execute Command + ... sudo tedge mqtt pub --retain 'te/device/main///cmd/log_upload/example-1234' '{"status":"init","tedgeUrl":"http://127.0.0.1:8000/tedge/file-transfer/${DEVICE_SN}/log_upload/example-1234","type":"example","dateFrom":"${start_timestamp}","dateTo":"${end_timestamp}","searchText":"first","lines":10}' + ${messages}= Should Have MQTT Messages + ... te/device/main///cmd/log_upload/example-1234 + ... minimum=3 + ... maximum=3 + *** Keywords *** Setup LogFiles ThinEdgeIO.Transfer To Device ${CURDIR}/tedge-log-plugin.toml /etc/tedge/plugins/tedge-log-plugin.toml ThinEdgeIO.Transfer To Device ${CURDIR}/example.log /var/log/example/ # touch file again to change last modified timestamp, otherwise the logfile retrieval could be outside of the requested range - Execute Command chown root:root /etc/tedge/plugins/tedge-log-plugin.toml /var/log/example/example.log && touch /var/log/example/example.log + Execute Command + ... chown root:root /etc/tedge/plugins/tedge-log-plugin.toml /var/log/example/example.log && touch /var/log/example/example.log ThinEdgeIO.Service Health Status Should Be Up tedge-log-plugin ThinEdgeIO.Service Health Status Should Be Up tedge-mapper-c8y From 4049e2f18659b7ee893a2f3df2e7d98938011442 Mon Sep 17 00:00:00 2001 From: Didier Wenzek Date: Wed, 1 Nov 2023 11:46:42 +0100 Subject: [PATCH 4/5] Use command id with mapper specific prefix for all commands - restart - software_list - software_update Signed-off-by: Didier Wenzek --- Cargo.lock | 2 - .../src/smartrest/smartrest_deserializer.rs | 16 +++---- .../tedge_agent/src/software_manager/actor.rs | 4 +- .../tedge_agent/src/software_manager/tests.rs | 4 +- .../src/tedge_operation_converter/tests.rs | 10 ++--- crates/core/tedge_api/Cargo.toml | 1 - crates/core/tedge_api/src/lib.rs | 43 +++++-------------- crates/core/tedge_api/src/messages.rs | 12 +----- crates/core/tedge_watchdog/Cargo.toml | 1 - .../src/compatibility_adapter.rs | 5 ++- .../c8y_mapper_ext/src/converter.rs | 17 +++++--- crates/extensions/c8y_mapper_ext/src/tests.rs | 8 ++-- 12 files changed, 44 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ca3d8d919c..08c9ff69d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3687,7 +3687,6 @@ dependencies = [ "freedesktop_entry_parser", "futures", "mqtt_channel", - "nanoid", "serde", "serde_json", "tedge_api", @@ -3726,7 +3725,6 @@ dependencies = [ "maplit", "mockall", "mqtt_channel", - "nanoid", "regex", "serde", "serde_json", diff --git a/crates/core/c8y_api/src/smartrest/smartrest_deserializer.rs b/crates/core/c8y_api/src/smartrest/smartrest_deserializer.rs index 5220ec8633e..c4411c40828 100644 --- a/crates/core/c8y_api/src/smartrest/smartrest_deserializer.rs +++ b/crates/core/c8y_api/src/smartrest/smartrest_deserializer.rs @@ -88,12 +88,9 @@ impl SmartRestUpdateSoftware { pub fn into_software_update_command( &self, target: &EntityTopicId, - cmd_id: Option<&str>, + cmd_id: String, ) -> Result { - let mut request = match cmd_id { - None => SoftwareUpdateCommand::new(target), - Some(cmd_id) => SoftwareUpdateCommand::new_with_id(target, cmd_id.to_string()), - }; + let mut request = SoftwareUpdateCommand::new(target, cmd_id); for module in self.modules() { match module.action.clone().try_into()? { CumulocitySoftwareUpdateActions::Install => { @@ -478,7 +475,7 @@ mod tests { String::from("528,external_id,software1,version1,url1,action,software2,,,remove"); assert!(SmartRestUpdateSoftware::from_smartrest(&smartrest) .unwrap() - .into_software_update_command(&device, Some("123")) + .into_software_update_command(&device, "123".to_string()) .is_err()); } @@ -504,11 +501,10 @@ mod tests { }; let device = EntityTopicId::default_main_device(); let thin_edge_json = smartrest_obj - .into_software_update_command(&device, Some("123")) + .into_software_update_command(&device, "123".to_string()) .unwrap(); - let mut expected_thin_edge_json = - SoftwareUpdateCommand::new_with_id(&device, "123".to_string()); + let mut expected_thin_edge_json = SoftwareUpdateCommand::new(&device, "123".to_string()); expected_thin_edge_json.add_update(SoftwareModuleUpdate::install(SoftwareModule { module_type: Some("debian".to_string()), name: "software1".to_string(), @@ -535,7 +531,7 @@ mod tests { nginx,1.21.0::docker,,install,mongodb,4.4.6::docker,,delete"); let software_update_request = SmartRestUpdateSoftware::from_smartrest(&smartrest) .unwrap() - .into_software_update_command(&EntityTopicId::default_main_device(), Some("123")) + .into_software_update_command(&EntityTopicId::default_main_device(), "123".to_string()) .unwrap(); let output_json = software_update_request.payload.to_json(); diff --git a/crates/core/tedge_agent/src/software_manager/actor.rs b/crates/core/tedge_agent/src/software_manager/actor.rs index 2196905ee23..3db6c44c6a0 100644 --- a/crates/core/tedge_agent/src/software_manager/actor.rs +++ b/crates/core/tedge_agent/src/software_manager/actor.rs @@ -177,14 +177,14 @@ impl SoftwareManagerActor { { match operation { StateStatus::Software(SoftwareOperationVariants::Update) => { - let response = SoftwareUpdateCommand::new_with_id(&self.config.device, cmd_id) + let response = SoftwareUpdateCommand::new(&self.config.device, cmd_id) .with_error( "Software Update command cancelled on agent restart".to_string(), ); self.output_sender.send(response.into()).await?; } StateStatus::Software(SoftwareOperationVariants::List) => { - let response = SoftwareListCommand::new_with_id(&self.config.device, cmd_id) + let response = SoftwareListCommand::new(&self.config.device, cmd_id) .with_error("Software List request cancelled on agent restart".to_string()); self.output_sender.send(response.into()).await?; } diff --git a/crates/core/tedge_agent/src/software_manager/tests.rs b/crates/core/tedge_agent/src/software_manager/tests.rs index b91ea97b71f..02f350212e0 100644 --- a/crates/core/tedge_agent/src/software_manager/tests.rs +++ b/crates/core/tedge_agent/src/software_manager/tests.rs @@ -104,7 +104,7 @@ async fn test_pending_software_list_operation() -> Result<(), DynError> { let mut converter_box = spawn_software_manager(&temp_dir).await?; let software_request_response = - SoftwareListCommand::new_with_id(&EntityTopicId::default_main_device(), "1234".to_string()) + SoftwareListCommand::new(&EntityTopicId::default_main_device(), "1234".to_string()) .with_error("Software List request cancelled on agent restart".to_string()); converter_box .assert_received([software_request_response]) @@ -122,7 +122,7 @@ async fn test_new_software_list_operation() -> Result<(), DynError> { let mut converter_box = spawn_software_manager(&temp_dir).await?; let command = - SoftwareListCommand::new_with_id(&EntityTopicId::default_main_device(), "1234".to_string()); + SoftwareListCommand::new(&EntityTopicId::default_main_device(), "1234".to_string()); converter_box.send(command.clone().into()).await?; let executing_response = command.clone().with_status(CommandStatus::Executing); diff --git a/crates/core/tedge_agent/src/tedge_operation_converter/tests.rs b/crates/core/tedge_agent/src/tedge_operation_converter/tests.rs index 8a8fb56749e..40559315be8 100644 --- a/crates/core/tedge_agent/src/tedge_operation_converter/tests.rs +++ b/crates/core/tedge_agent/src/tedge_operation_converter/tests.rs @@ -41,7 +41,7 @@ async fn convert_incoming_software_list_request() -> Result<(), DynError> { // Assert SoftwareListCommand software_box - .assert_received([SoftwareListCommand::new_with_id( + .assert_received([SoftwareListCommand::new( &EntityTopicId::default_main_device(), "some-cmd-id".to_string(), )]) @@ -130,7 +130,7 @@ async fn convert_outgoing_software_list_response() -> Result<(), DynError> { // Simulate SoftwareList response message received. let software_list_request = - SoftwareListCommand::new_with_id(&EntityTopicId::default_main_device(), "1234".to_string()); + SoftwareListCommand::new(&EntityTopicId::default_main_device(), "1234".to_string()); let software_list_response = software_list_request .clone() .with_status(CommandStatus::Executing); @@ -189,10 +189,8 @@ async fn convert_outgoing_software_update_response() -> Result<(), DynError> { skip_capability_messages(&mut mqtt_box, "device/main//").await; // Simulate SoftwareUpdate response message received. - let software_update_request = SoftwareUpdateCommand::new_with_id( - &EntityTopicId::default_main_device(), - "1234".to_string(), - ); + let software_update_request = + SoftwareUpdateCommand::new(&EntityTopicId::default_main_device(), "1234".to_string()); let software_update_response = software_update_request.with_status(CommandStatus::Executing); software_box.send(software_update_response.into()).await?; diff --git a/crates/core/tedge_api/Cargo.toml b/crates/core/tedge_api/Cargo.toml index af19328456d..6df3733bc75 100644 --- a/crates/core/tedge_api/Cargo.toml +++ b/crates/core/tedge_api/Cargo.toml @@ -16,7 +16,6 @@ download = { workspace = true } json-writer = { workspace = true } log = { workspace = true } mqtt_channel = { workspace = true } -nanoid = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/core/tedge_api/src/lib.rs b/crates/core/tedge_api/src/lib.rs index f7106149119..62c9dde390e 100644 --- a/crates/core/tedge_api/src/lib.rs +++ b/crates/core/tedge_api/src/lib.rs @@ -37,7 +37,6 @@ mod tests { use mqtt_channel::Message; use mqtt_channel::QoS; use mqtt_channel::Topic; - use regex::Regex; #[test] fn topic_names() { @@ -52,7 +51,7 @@ mod tests { Topic::new_unchecked("te/device/main///cmd/software_list") ); assert_eq!( - SoftwareListCommand::new_with_id(&device, cmd_id.clone()) + SoftwareListCommand::new(&device, cmd_id.clone()) .command_message(&mqtt_schema) .topic, Topic::new_unchecked("te/device/main///cmd/software_list/abc") @@ -63,7 +62,7 @@ mod tests { Topic::new_unchecked("te/device/main///cmd/software_update") ); assert_eq!( - SoftwareUpdateCommand::new_with_id(&device, cmd_id.clone()) + SoftwareUpdateCommand::new(&device, cmd_id.clone()) .command_message(&mqtt_schema) .topic, Topic::new_unchecked("te/device/main///cmd/software_update/abc") @@ -74,7 +73,7 @@ mod tests { Topic::new_unchecked("te/device/main///cmd/restart") ); assert_eq!( - RestartCommand::new_with_id(&device, cmd_id.clone()) + RestartCommand::new(&device, cmd_id.clone()) .command_message(&mqtt_schema) .topic, Topic::new_unchecked("te/device/main///cmd/restart/abc") @@ -85,7 +84,7 @@ mod tests { fn creating_a_software_list_request() { let mqtt_schema = MqttSchema::default(); let device = EntityTopicId::default_child_device("abc").unwrap(); - let request = SoftwareListCommand::new_with_id(&device, "1".to_string()); + let request = SoftwareListCommand::new(&device, "1".to_string()); let expected_msg = Message { topic: Topic::new_unchecked("te/device/abc///cmd/software_list/1"), @@ -97,17 +96,6 @@ mod tests { assert_eq!(actual_msg, expected_msg); } - #[test] - fn creating_a_software_list_request_with_generated_id() { - let device = EntityTopicId::default_child_device("abc").unwrap(); - let request = SoftwareListCommand::new(&device); - let generated_id = request.cmd_id; - - // The generated id is a nanoid of 21 characters from A-Za-z0-9_~ - let re = Regex::new(r"[A-Za-z0-9_~-]{21,21}").unwrap(); - assert!(re.is_match(&generated_id)); - } - #[test] fn using_a_software_list_request() { let device = EntityTopicId::default_child_device("abc").unwrap(); @@ -137,7 +125,7 @@ mod tests { #[test] fn creating_a_software_list_response() { let device = EntityTopicId::default_child_device("abc").unwrap(); - let mut response = SoftwareListCommand::new_with_id(&device, "1".to_string()) + let mut response = SoftwareListCommand::new(&device, "1".to_string()) .with_status(CommandStatus::Successful); response.add_modules( @@ -276,7 +264,7 @@ mod tests { #[test] fn creating_a_software_list_error() { let device = EntityTopicId::default_child_device("abc").unwrap(); - let response = SoftwareListCommand::new_with_id(&device, "123".to_string()); + let response = SoftwareListCommand::new(&device, "123".to_string()); let response = response.with_error("Request_timed-out".to_string()); let message = response.command_message(&MqttSchema::default()); @@ -316,7 +304,7 @@ mod tests { fn creating_a_software_update_request() { let device = EntityTopicId::default_child_device("abc").unwrap(); let cmd_id = "123".to_string(); - let mut request = SoftwareUpdateCommand::new_with_id(&device, cmd_id); + let mut request = SoftwareUpdateCommand::new(&device, cmd_id); request.add_updates( "debian", @@ -405,7 +393,7 @@ mod tests { fn creating_a_software_update_request_grouping_updates_per_plugin() { let device = EntityTopicId::default_child_device("abc").unwrap(); let cmd_id = "123".to_string(); - let mut request = SoftwareUpdateCommand::new_with_id(&device, cmd_id); + let mut request = SoftwareUpdateCommand::new(&device, cmd_id); request.add_update(SoftwareModuleUpdate::install(SoftwareModule { module_type: Some("debian".to_string()), @@ -482,7 +470,7 @@ mod tests { fn creating_a_software_update_request_grouping_updates_per_plugin_using_default() { let device = EntityTopicId::default_child_device("abc").unwrap(); let cmd_id = "123".to_string(); - let mut request = SoftwareUpdateCommand::new_with_id(&device, cmd_id); + let mut request = SoftwareUpdateCommand::new(&device, cmd_id); request.add_update(SoftwareModuleUpdate::install(SoftwareModule { module_type: None, // I.e. default @@ -552,17 +540,6 @@ mod tests { assert_eq!(actual_json, remove_whitespace(expected_json)); } - #[test] - fn creating_a_software_update_request_with_generated_id() { - let device = EntityTopicId::default_child_device("abc").unwrap(); - let request = SoftwareUpdateCommand::new(&device); - let generated_id = request.cmd_id; - - // The generated id is a nanoid of 21 characters from A-Za-z0-9_~ - let re = Regex::new(r"[A-Za-z0-9_~-]{21,21}").unwrap(); - assert!(re.is_match(&generated_id)); - } - #[test] fn using_a_software_update_request() { let mqtt_schema = MqttSchema::default(); @@ -669,7 +646,7 @@ mod tests { fn creating_a_software_update_response() { let device = EntityTopicId::default_child_device("abc").unwrap(); let cmd_id = "123".to_string(); - let request = SoftwareUpdateCommand::new_with_id(&device, cmd_id); + let request = SoftwareUpdateCommand::new(&device, cmd_id); let response = request.with_status(CommandStatus::Executing); diff --git a/crates/core/tedge_api/src/messages.rs b/crates/core/tedge_api/src/messages.rs index fd2cd8a0923..8728aa5a397 100644 --- a/crates/core/tedge_api/src/messages.rs +++ b/crates/core/tedge_api/src/messages.rs @@ -8,7 +8,6 @@ use download::DownloadInfo; use mqtt_channel::Message; use mqtt_channel::QoS; use mqtt_channel::Topic; -use nanoid::nanoid; use serde::Deserialize; use serde::Serialize; use time::OffsetDateTime; @@ -25,17 +24,8 @@ impl Command where Payload: Default, { - /// Build a new command with a random id - pub fn new(target: &EntityTopicId) -> Self { - Command { - target: target.clone(), - cmd_id: nanoid!(), - payload: Default::default(), - } - } - /// Build a new command with a given id - pub fn new_with_id(target: &EntityTopicId, cmd_id: String) -> Self { + pub fn new(target: &EntityTopicId, cmd_id: String) -> Self { Command { target: target.clone(), cmd_id, diff --git a/crates/core/tedge_watchdog/Cargo.toml b/crates/core/tedge_watchdog/Cargo.toml index d1079b60158..2b53453c950 100644 --- a/crates/core/tedge_watchdog/Cargo.toml +++ b/crates/core/tedge_watchdog/Cargo.toml @@ -15,7 +15,6 @@ clap = { workspace = true } freedesktop_entry_parser = { workspace = true } futures = { workspace = true } mqtt_channel = { workspace = true } -nanoid = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tedge_api = { workspace = true } diff --git a/crates/extensions/c8y_mapper_ext/src/compatibility_adapter.rs b/crates/extensions/c8y_mapper_ext/src/compatibility_adapter.rs index e11b0e79da5..07411357d9f 100644 --- a/crates/extensions/c8y_mapper_ext/src/compatibility_adapter.rs +++ b/crates/extensions/c8y_mapper_ext/src/compatibility_adapter.rs @@ -149,7 +149,10 @@ fn convert_from_old_agent_response( ) -> Result, String> { if let Ok(Value::Object(response)) = serde_json::from_slice(payload) { if let Some(Value::String(cmd_id)) = response.get("id") { - if let Ok(topic) = Topic::new(&format!("te/device/main///cmd/{cmd_type}/{cmd_id}")) { + // The new mapper expects command ids with a specific prefix + if let Ok(topic) = Topic::new(&format!( + "te/device/main///cmd/{cmd_type}/c8y-mapper-{cmd_id}" + )) { return Ok(Some( MqttMessage::new(&topic, payload) .with_retain() diff --git a/crates/extensions/c8y_mapper_ext/src/converter.rs b/crates/extensions/c8y_mapper_ext/src/converter.rs index 21b2c3f38b9..45b6ea07158 100644 --- a/crates/extensions/c8y_mapper_ext/src/converter.rs +++ b/crates/extensions/c8y_mapper_ext/src/converter.rs @@ -587,7 +587,8 @@ impl CumulocityConverter { let update_software = SmartRestUpdateSoftware::from_smartrest(smartrest)?; let device_id = &update_software.external_id.clone().into(); let target = self.entity_store.try_get_by_external_id(device_id)?; - let mut command = update_software.into_software_update_command(&target.topic_id, None)?; + let cmd_id = self.command_id.new_id(); + let mut command = update_software.into_software_update_command(&target.topic_id, cmd_id)?; command.payload.update_list.iter_mut().for_each(|modules| { modules.modules.iter_mut().for_each(|module| { @@ -614,13 +615,15 @@ impl CumulocityConverter { let request = SmartRestRestartRequest::from_smartrest(smartrest)?; let device_id = &request.device.into(); let target = self.entity_store.try_get_by_external_id(device_id)?; - let command = RestartCommand::new(&target.topic_id); + let cmd_id = self.command_id.new_id(); + let command = RestartCommand::new(&target.topic_id, cmd_id); let message = command.command_message(&self.mqtt_schema); Ok(vec![message]) } fn request_software_list(&self, target: &EntityTopicId) -> Message { - let request = SoftwareListCommand::new(target); + let cmd_id = self.command_id.new_id(); + let request = SoftwareListCommand::new(target, cmd_id); request.command_message(&self.mqtt_schema) } @@ -929,7 +932,7 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::Restart, cmd_id, - } => { + } if self.command_id.is_generator_of(cmd_id) => { self.publish_restart_operation_status(&source, cmd_id, message) .await? } @@ -940,7 +943,9 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::SoftwareList, cmd_id, - } => self.publish_software_list(&source, cmd_id, message).await?, + } if self.command_id.is_generator_of(cmd_id) => { + self.publish_software_list(&source, cmd_id, message).await? + } Channel::CommandMetadata { operation: OperationType::SoftwareUpdate, @@ -948,7 +953,7 @@ impl CumulocityConverter { Channel::Command { operation: OperationType::SoftwareUpdate, cmd_id, - } => { + } if self.command_id.is_generator_of(cmd_id) => { self.publish_software_update_status(&source, cmd_id, message) .await? } diff --git a/crates/extensions/c8y_mapper_ext/src/tests.rs b/crates/extensions/c8y_mapper_ext/src/tests.rs index a3f7247647e..ae2dd068b45 100644 --- a/crates/extensions/c8y_mapper_ext/src/tests.rs +++ b/crates/extensions/c8y_mapper_ext/src/tests.rs @@ -338,7 +338,7 @@ async fn mapper_publishes_software_update_status_onto_c8y_topic() { // Prepare and publish a software update status response message `executing` on `te/device/main///cmd/software_update/123`. let mqtt_schema = MqttSchema::default(); let device = EntityTopicId::default_main_device(); - let request = SoftwareUpdateCommand::new_with_id(&device, "123".to_string()); + let request = SoftwareUpdateCommand::new(&device, "c8y-mapper-123".to_string()); let response = request.with_status(CommandStatus::Executing); mqtt.send(response.command_message(&mqtt_schema)) .await @@ -359,7 +359,7 @@ async fn mapper_publishes_software_update_status_onto_c8y_topic() { // The successful state is cleared assert_received_contains_str( &mut mqtt, - [("te/device/main///cmd/software_update/123", "")], + [("te/device/main///cmd/software_update/c8y-mapper-123", "")], ) .await; @@ -386,7 +386,7 @@ async fn mapper_publishes_software_update_failed_status_onto_c8y_topic() { // The agent publish an error let mqtt_schema = MqttSchema::default(); let device = EntityTopicId::default_main_device(); - let response = SoftwareUpdateCommand::new_with_id(&device, "123".to_string()) + let response = SoftwareUpdateCommand::new(&device, "c8y-mapper-123".to_string()) .with_error("Partial failure: Couldn't install collectd and nginx".to_string()); mqtt.send(response.command_message(&mqtt_schema)) .await @@ -405,7 +405,7 @@ async fn mapper_publishes_software_update_failed_status_onto_c8y_topic() { // The failed state is cleared assert_received_contains_str( &mut mqtt, - [("te/device/main///cmd/software_update/123", "")], + [("te/device/main///cmd/software_update/c8y-mapper-123", "")], ) .await; From 673135cac7ce86cecc4380699234141ead537675 Mon Sep 17 00:00:00 2001 From: Reuben Miller Date: Wed, 1 Nov 2023 16:35:48 +0100 Subject: [PATCH 5/5] extend system tests to cover all command types Signed-off-by: Reuben Miller --- .../configuration_operation.robot | 33 +++++++++++++++++++ .../tests/cumulocity/log/log_operation.robot | 27 +++++++++++---- .../cumulocity/restart/restart_device.robot | 24 ++++++++++++++ .../software_management/software.robot | 31 +++++++++++++++++ 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/tests/RobotFramework/tests/cumulocity/configuration/configuration_operation.robot b/tests/RobotFramework/tests/cumulocity/configuration/configuration_operation.robot index cc493a0ee93..a613d9a7ff2 100644 --- a/tests/RobotFramework/tests/cumulocity/configuration/configuration_operation.robot +++ b/tests/RobotFramework/tests/cumulocity/configuration/configuration_operation.robot @@ -99,6 +99,24 @@ Update configuration plugin config via local filesystem move (same directory) Main Device ${PARENT_SN} ${PARENT_SN} Child Device ${CHILD_SN} ${PARENT_SN}:device:${CHILD_SN} +Manual config_snapshot operation request + Set Device Context ${PARENT_SN} + Publish and Verify Local Command + ... topic=te/device/main///cmd/config_snapshot/local-1111 + ... payload={"status":"init","tedgeUrl":"http://${PARENT_IP}:8000/tedge/file-transfer/${PARENT_SN}/config_snapshot/local-1111","type":"tedge-configuration-plugin"} + ... expected_status=successful + ... c8y_fragment=c8y_UploadConfigFile + +Manual config_update operation request + Set Device Context ${PARENT_SN} + # Don't worry about the command failing, that is expected since the tedgeUrl path does not exist + Publish and Verify Local Command + ... topic=te/device/main///cmd/config_update/local-2222 + ... payload={"status":"init","tedgeUrl":"http://${PARENT_IP}:8000/tedge/file-transfer/${PARENT_SN}/config_update/local-2222","remoteUrl":"","type":"tedge-configuration-plugin"} + ... expected_status=failed + ... c8y_fragment=c8y_DownloadConfigFile + + *** Keywords *** Set Configuration from Device @@ -283,3 +301,18 @@ Copy Configuration Files # Execute Command chown root:root /etc/tedge/plugins/tedge-configuration-plugin.toml /etc/config1.json # Uncomment once https://github.com/thin-edge/thin-edge.io/issues/2253 is resolved # ThinEdgeIO.Service Health Status Should Be Up tedge-configuration-plugin device=child1 + +Publish and Verify Local Command + [Arguments] ${topic} ${payload} ${expected_status}=successful ${c8y_fragment}= + [Teardown] Execute Command tedge mqtt pub --retain '${topic}' '' + Execute Command tedge mqtt pub --retain '${topic}' '${payload}' + ${messages}= Should Have MQTT Messages ${topic} minimum=1 maximum=1 message_contains="status":"${expected_status}" + + Sleep 5s reason=Given mapper a chance to react, if it does not react with 5 seconds it never will + ${retained_message} Execute Command timeout 1 tedge mqtt sub --no-topic '${topic}' ignore_exit_code=${True} strip=${True} + Should Be Equal ${messages[0]} ${retained_message} msg=MQTT message should be unchanged + + IF "${c8y_fragment}" + # There should not be any c8y related operation transition messages sent: https://cumulocity.com/guides/reference/smartrest-two/#updating-operations + Should Have MQTT Messages c8y/s/ds message_pattern=^(501|502|503),${c8y_fragment}.* minimum=0 maximum=0 + END diff --git a/tests/RobotFramework/tests/cumulocity/log/log_operation.robot b/tests/RobotFramework/tests/cumulocity/log/log_operation.robot index d87880d8ef1..8e93ca3aad2 100644 --- a/tests/RobotFramework/tests/cumulocity/log/log_operation.robot +++ b/tests/RobotFramework/tests/cumulocity/log/log_operation.robot @@ -30,17 +30,15 @@ Request with non-existing log type ... failure_reason=.*No such file or directory for log type: example1 ... timeout=120 -Manual log operation request +Manual log_upload operation request Execute Command sudo -u tedge mkdir -p /var/tedge/file-transfer/${DEVICE_SN}/log_upload Execute Command sudo -u tedge touch /var/tedge/file-transfer/${DEVICE_SN}/log_upload/example-1234 ${start_timestamp}= Get Current Date UTC -24 hours result_format=%Y-%m-%dT%H:%M:%SZ ${end_timestamp}= Get Current Date UTC +60 seconds result_format=%Y-%m-%dT%H:%M:%SZ - Execute Command - ... sudo tedge mqtt pub --retain 'te/device/main///cmd/log_upload/example-1234' '{"status":"init","tedgeUrl":"http://127.0.0.1:8000/tedge/file-transfer/${DEVICE_SN}/log_upload/example-1234","type":"example","dateFrom":"${start_timestamp}","dateTo":"${end_timestamp}","searchText":"first","lines":10}' - ${messages}= Should Have MQTT Messages - ... te/device/main///cmd/log_upload/example-1234 - ... minimum=3 - ... maximum=3 + Publish and Verify Local Command + ... topic=te/device/main///cmd/log_upload/example-1234 + ... payload={"status":"init","tedgeUrl":"http://127.0.0.1:8000/tedge/file-transfer/${DEVICE_SN}/log_upload/example-1234","type":"example","dateFrom":"${start_timestamp}","dateTo":"${end_timestamp}","searchText":"first","lines":10} + ... c8y_fragment=c8y_DownloadConfigFile *** Keywords *** @@ -59,3 +57,18 @@ Custom Setup Device Should Exist ${DEVICE_SN} Setup LogFiles + +Publish and Verify Local Command + [Arguments] ${topic} ${payload} ${expected_status}=successful ${c8y_fragment}= + [Teardown] Execute Command tedge mqtt pub --retain '${topic}' '' + Execute Command tedge mqtt pub --retain '${topic}' '${payload}' + ${messages}= Should Have MQTT Messages ${topic} minimum=1 maximum=1 message_contains="status":"${expected_status}" + + Sleep 5s reason=Given mapper a chance to react, if it does not react with 5 seconds it never will + ${retained_message} Execute Command timeout 1 tedge mqtt sub --no-topic '${topic}' ignore_exit_code=${True} strip=${True} + Should Be Equal ${messages[0]} ${retained_message} msg=MQTT message should be unchanged + + IF "${c8y_fragment}" + # There should not be any c8y related operation transition messages sent: https://cumulocity.com/guides/reference/smartrest-two/#updating-operations + Should Have MQTT Messages c8y/s/ds message_pattern=^(501|502|503),${c8y_fragment}.* minimum=0 maximum=0 + END diff --git a/tests/RobotFramework/tests/cumulocity/restart/restart_device.robot b/tests/RobotFramework/tests/cumulocity/restart/restart_device.robot index 281ddaaf01c..cff705c8f73 100644 --- a/tests/RobotFramework/tests/cumulocity/restart/restart_device.robot +++ b/tests/RobotFramework/tests/cumulocity/restart/restart_device.robot @@ -21,6 +21,15 @@ Supports restarting the device without sudo and running as root ${operation}= Cumulocity.Restart Device Operation Should Be SUCCESSFUL ${operation} timeout=180 +# Note: Sending an actual local restart operation to trigger a restart (e.g. status=init) is not feasible +# as the assertion would fail due to the device/container being unresponsive during the actual restart. +# Just checking that the messages do not trigger any c8y messages and does not clear the retained message +tedge-mapper-c8y does not react to local restart operations transitions + [Template] Publish and Verify Local Command + topic=te/device/main///cmd/restart/local-1111 payload={"status":"executing"} expected_status=executing c8y_fragment=c8y_Restart + topic=te/device/main///cmd/restart/local-2222 payload={"status":"failed"} expected_status=failed c8y_fragment=c8y_Restart + topic=te/device/main///cmd/restart/local-3333 payload={"status":"successful"} expected_status=successful c8y_fragment=c8y_Restart + *** Keywords *** @@ -45,3 +54,18 @@ Custom Teardown # Restore sudo in case if the tests are run on a device (and not in a container) Execute Command [ -f /usr/bin/sudo.bak ] && mv /usr/bin/sudo.bak /usr/bin/sudo || true Get Logs + +Publish and Verify Local Command + [Arguments] ${topic} ${payload} ${expected_status}=successful ${c8y_fragment}= + [Teardown] Execute Command tedge mqtt pub --retain '${topic}' '' + Execute Command tedge mqtt pub --retain '${topic}' '${payload}' + ${messages}= Should Have MQTT Messages ${topic} minimum=1 maximum=1 message_contains="status":"${expected_status}" + + Sleep 2s reason=Given mapper a chance to react, if it does not react with 2 seconds it never will + ${retained_message} Execute Command timeout 1 tedge mqtt sub --no-topic '${topic}' ignore_exit_code=${True} strip=${True} + Should Be Equal ${messages[0]} ${retained_message} msg=MQTT message should be unchanged + + IF "${c8y_fragment}" + # There should not be any c8y related operation transition messages sent: https://cumulocity.com/guides/reference/smartrest-two/#updating-operations + Should Have MQTT Messages c8y/s/ds message_pattern=^(501|502|503),${c8y_fragment}.* minimum=0 maximum=0 + END diff --git a/tests/RobotFramework/tests/cumulocity/software_management/software.robot b/tests/RobotFramework/tests/cumulocity/software_management/software.robot index ad2c90c60d9..07ddb92344c 100644 --- a/tests/RobotFramework/tests/cumulocity/software_management/software.robot +++ b/tests/RobotFramework/tests/cumulocity/software_management/software.robot @@ -34,6 +34,22 @@ Software list should only show currently installed software and not candidates ${VERSION}= Regexp Escape ${EXPECTED_VERSION} Device Should Have Installed Software tedge,^${VERSION}::apt$ timeout=120 +Manual software_list operation request + # Note: There isn't a Cumulocity IoT operation related to getting the software list, so no need to check for operation transitions + Publish and Verify Local Command + ... topic=te/device/main///cmd/software_list/local-1111 + ... payload={"status":"init"} + ... expected_status=successful + +Manual software_update operation request + # Don't worry about the command failing, that is expected since the package to be installed does not exist + Publish and Verify Local Command + ... topic=te/device/main///cmd/software_update/local-2222 + ... payload={"status":"init","updateList":[{"type":"apt","modules":[{"name":"package-does-not-exist","version":"latest","action":"install"}]}]} + ... expected_status=failed + ... c8y_fragment=c8y_SoftwareUpdate + + *** Keywords *** Custom Setup @@ -50,3 +66,18 @@ Stop tedge-agent Custom Teardown Execute Command sudo stop-http-server.sh Get Logs + +Publish and Verify Local Command + [Arguments] ${topic} ${payload} ${expected_status}=successful ${c8y_fragment}= + [Teardown] Execute Command tedge mqtt pub --retain '${topic}' '' + Execute Command tedge mqtt pub --retain '${topic}' '${payload}' + ${messages}= Should Have MQTT Messages ${topic} minimum=1 maximum=1 message_contains="status":"${expected_status}" + + Sleep 5s reason=Given mapper a chance to react, if it does not react with 5 seconds it never will + ${retained_message} Execute Command timeout 1 tedge mqtt sub --no-topic '${topic}' ignore_exit_code=${True} strip=${True} + Should Be Equal ${messages[0]} ${retained_message} msg=MQTT message should be unchanged + + IF "${c8y_fragment}" + # There should not be any c8y related operation transition messages sent: https://cumulocity.com/guides/reference/smartrest-two/#updating-operations + Should Have MQTT Messages c8y/s/ds message_pattern=^(501|502|503),${c8y_fragment}.* minimum=0 maximum=0 + END