Skip to content

Commit 121d108

Browse files
authored
Merge pull request #2730 from rina23q/bugfix/2654/sw-list-and-sw-update-metadata-need-types
tedge-agent publishes "types" for software_list and software_update
2 parents d5f0a43 + 912e295 commit 121d108

File tree

10 files changed

+149
-14
lines changed

10 files changed

+149
-14
lines changed

crates/core/plugin_sm/src/plugin_manager.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub trait Plugins {
4545
}
4646

4747
fn update_default(&mut self, new_default: &Option<SoftwareType>) -> Result<(), SoftwareError>;
48+
49+
fn get_all_software_types(&self) -> Vec<SoftwareType>;
4850
}
4951

5052
#[derive(Debug)]
@@ -90,6 +92,12 @@ impl Plugins for ExternalPlugins {
9092
self.default()
9193
}
9294
}
95+
96+
fn get_all_software_types(&self) -> Vec<SoftwareType> {
97+
let mut software_types: Vec<SoftwareType> = self.plugin_map.keys().cloned().collect();
98+
software_types.sort();
99+
software_types
100+
}
93101
}
94102

95103
impl ExternalPlugins {

crates/core/tedge_agent/src/software_manager/actor.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use tedge_actors::RuntimeRequest;
2525
use tedge_actors::Sender;
2626
use tedge_actors::SimpleMessageBox;
2727
use tedge_api::messages::CommandStatus;
28+
use tedge_api::messages::SoftwareCommandMetadata;
2829
use tedge_api::messages::SoftwareListCommand;
2930
use tedge_api::messages::SoftwareUpdateCommand;
3031
use tedge_api::SoftwareType;
@@ -39,7 +40,7 @@ const SUDO: &str = "sudo";
3940
#[cfg(test)]
4041
const SUDO: &str = "echo";
4142

42-
fan_in_message_type!(SoftwareCommand[SoftwareUpdateCommand, SoftwareListCommand] : Debug, Eq, PartialEq, Deserialize, Serialize);
43+
fan_in_message_type!(SoftwareCommand[SoftwareUpdateCommand, SoftwareListCommand, SoftwareCommandMetadata] : Debug, Eq, PartialEq, Deserialize, Serialize);
4344

4445
/// Actor which performs software operations.
4546
///
@@ -103,6 +104,14 @@ impl Actor for SoftwareManagerActor {
103104
anyhow::anyhow!("actor can't be run more than once").into(),
104105
))?;
105106

107+
self.output_sender
108+
.send(SoftwareCommand::SoftwareCommandMetadata(
109+
SoftwareCommandMetadata {
110+
types: plugins.get_all_software_types(),
111+
},
112+
))
113+
.await?;
114+
106115
while let Some(request) = input_receiver.recv().await {
107116
tokio::select! {
108117
_ = self.handle_request(request, &mut plugins, &operation_logs) => {
@@ -174,6 +183,7 @@ impl SoftwareManagerActor {
174183
error!("{:?}", err);
175184
}
176185
}
186+
SoftwareCommand::SoftwareCommandMetadata(_) => {} // Not used as input
177187
}
178188
Ok(())
179189
}
@@ -192,6 +202,7 @@ impl SoftwareManagerActor {
192202
);
193203
self.output_sender.send(response.into()).await?;
194204
}
205+
Ok(Some(SoftwareCommand::SoftwareCommandMetadata(_))) => (), // not used in state repository
195206
Err(StateError::LoadingFromFileFailed { source, .. })
196207
if source.kind() == std::io::ErrorKind::NotFound =>
197208
{

crates/core/tedge_agent/src/software_manager/tests.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use tedge_actors::ServiceConsumer;
1414
use tedge_actors::SimpleMessageBox;
1515
use tedge_actors::SimpleMessageBoxBuilder;
1616
use tedge_api::messages::CommandStatus;
17+
use tedge_api::messages::SoftwareCommandMetadata;
1718
use tedge_api::messages::SoftwareListCommand;
1819
use tedge_api::messages::SoftwareModuleAction;
1920
use tedge_api::messages::SoftwareModuleItem;
@@ -61,9 +62,18 @@ async fn test_pending_software_update_operation() -> Result<(), DynError> {
6162
async fn test_new_software_update_operation() -> Result<(), DynError> {
6263
let temp_dir = TempTedgeDir::new();
6364
temp_dir.dir(".agent");
65+
temp_dir.file("apt");
66+
temp_dir.file("docker");
6467

6568
let mut converter_box = spawn_software_manager(&temp_dir).await?;
6669

70+
// software cmd metadata internal message
71+
converter_box
72+
.assert_received([SoftwareCommandMetadata {
73+
types: vec!["apt".into(), "docker".into()],
74+
}])
75+
.await;
76+
6777
let debian_module1 = SoftwareModuleItem {
6878
name: "debian1".into(),
6979
version: Some("0.0.1".into()),
@@ -95,6 +105,9 @@ async fn test_new_software_update_operation() -> Result<(), DynError> {
95105
SoftwareCommand::SoftwareListCommand(_) => {
96106
panic!("Received SoftwareListCommand")
97107
}
108+
SoftwareCommand::SoftwareCommandMetadata(_) => {
109+
panic!("Received SoftwareCommandMetadata")
110+
}
98111
}
99112

100113
Ok(())
@@ -143,10 +156,12 @@ async fn test_new_software_list_operation() -> Result<(), DynError> {
143156
.with_status(CommandStatus::Scheduled);
144157
converter_box.send(command.clone().into()).await?;
145158

159+
let software_metadata = SoftwareCommandMetadata { types: vec![] };
146160
let executing_response = command.clone().with_status(CommandStatus::Executing);
147161
let mut successful_response = command.clone().with_status(CommandStatus::Successful);
148162
successful_response.add_modules("".to_string(), vec![]);
149163

164+
converter_box.assert_received([software_metadata]).await;
150165
converter_box
151166
.assert_received([executing_response, successful_response])
152167
.await;

crates/core/tedge_agent/src/tedge_operation_converter/actor.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use tedge_actors::RuntimeError;
1616
use tedge_actors::Sender;
1717
use tedge_actors::UnboundedLoggingReceiver;
1818
use tedge_api::messages::RestartCommand;
19+
use tedge_api::messages::SoftwareCommandMetadata;
1920
use tedge_api::messages::SoftwareListCommand;
2021
use tedge_api::messages::SoftwareUpdateCommand;
2122
use tedge_api::mqtt_topics::Channel;
@@ -27,7 +28,10 @@ use tedge_api::workflow::GenericCommandState;
2728
use tedge_api::workflow::OperationAction;
2829
use tedge_api::workflow::WorkflowExecutionError;
2930
use tedge_api::workflow::WorkflowSupervisor;
31+
use tedge_api::Jsonify;
32+
use tedge_mqtt_ext::Message;
3033
use tedge_mqtt_ext::MqttMessage;
34+
use tedge_mqtt_ext::QoS;
3135
use tedge_script_ext::Execute;
3236
use time::format_description;
3337
use time::OffsetDateTime;
@@ -75,6 +79,10 @@ impl Actor for TedgeOperationConverterActor {
7579
AgentInput::SoftwareCommand(SoftwareCommand::SoftwareUpdateCommand(res)) => {
7680
self.process_software_update_response(res).await?;
7781
}
82+
AgentInput::SoftwareCommand(SoftwareCommand::SoftwareCommandMetadata(payload)) => {
83+
self.publish_software_operation_capabilities(payload)
84+
.await?;
85+
}
7886
AgentInput::RestartCommand(cmd) => {
7987
self.process_restart_response(cmd).await?;
8088
}
@@ -95,6 +103,22 @@ impl TedgeOperationConverterActor {
95103
Ok(())
96104
}
97105

106+
async fn publish_software_operation_capabilities(
107+
&mut self,
108+
payload: SoftwareCommandMetadata,
109+
) -> Result<(), RuntimeError> {
110+
for operation in [OperationType::SoftwareList, OperationType::SoftwareUpdate] {
111+
let meta_topic = self
112+
.mqtt_schema
113+
.capability_topic_for(&self.device_topic_id, operation);
114+
let message = Message::new(&meta_topic, payload.to_json())
115+
.with_retain()
116+
.with_qos(QoS::AtLeastOnce);
117+
self.mqtt_publisher.send(message).await?;
118+
}
119+
Ok(())
120+
}
121+
98122
async fn process_mqtt_message(&mut self, message: MqttMessage) -> Result<(), RuntimeError> {
99123
let (operation, cmd_id) = match self.mqtt_schema.entity_channel_of(&message.topic) {
100124
Ok((_, Channel::Command { operation, cmd_id })) => (operation, cmd_id),

crates/core/tedge_agent/src/tedge_operation_converter/tests.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::software_manager::actor::SoftwareCommand;
22
use crate::tedge_operation_converter::builder::TedgeOperationConverterBuilder;
33
use crate::tedge_operation_converter::config::OperationConfig;
44
use camino::Utf8Path;
5+
use serde_json::json;
56
use std::process::Output;
67
use std::time::Duration;
78
use tedge_actors::test_helpers::MessageReceiverExt;
@@ -15,6 +16,7 @@ use tedge_actors::SimpleMessageBox;
1516
use tedge_actors::SimpleMessageBoxBuilder;
1617
use tedge_api::messages::CommandStatus;
1718
use tedge_api::messages::RestartCommandPayload;
19+
use tedge_api::messages::SoftwareCommandMetadata;
1820
use tedge_api::messages::SoftwareListCommand;
1921
use tedge_api::messages::SoftwareModuleAction;
2022
use tedge_api::messages::SoftwareModuleItem;
@@ -131,6 +133,15 @@ async fn convert_outgoing_software_list_response() -> Result<(), DynError> {
131133
let (mut software_box, _restart_box, mut mqtt_box) =
132134
spawn_mqtt_operation_converter("device/main//").await?;
133135

136+
// Declare supported software types from software actor
137+
software_box
138+
.send(SoftwareCommand::SoftwareCommandMetadata(
139+
SoftwareCommandMetadata {
140+
types: vec!["apt".into(), "docker".into()],
141+
},
142+
))
143+
.await?;
144+
134145
skip_capability_messages(&mut mqtt_box, "device/main//").await;
135146

136147
// Simulate SoftwareList response message received.
@@ -155,7 +166,7 @@ async fn convert_outgoing_software_list_response() -> Result<(), DynError> {
155166
#[tokio::test]
156167
async fn publish_capabilities_on_start() -> Result<(), DynError> {
157168
// Spawn outgoing mqtt message converter
158-
let (_software_box, _restart_box, mut mqtt_box) =
169+
let (mut software_box, _restart_box, mut mqtt_box) =
159170
spawn_mqtt_operation_converter("device/child//").await?;
160171

161172
mqtt_box
@@ -166,18 +177,27 @@ async fn publish_capabilities_on_start() -> Result<(), DynError> {
166177
.with_retain()])
167178
.await;
168179

180+
// Declare supported software types from software actor
181+
software_box
182+
.send(SoftwareCommand::SoftwareCommandMetadata(
183+
SoftwareCommandMetadata {
184+
types: vec!["apt".into(), "docker".into()],
185+
},
186+
))
187+
.await?;
188+
169189
mqtt_box
170190
.assert_received([MqttMessage::new(
171191
&Topic::new_unchecked("te/device/child///cmd/software_list"),
172-
"{}",
192+
json!({"types": ["apt", "docker"]}).to_string(),
173193
)
174194
.with_retain()])
175195
.await;
176196

177197
mqtt_box
178198
.assert_received([MqttMessage::new(
179199
&Topic::new_unchecked("te/device/child///cmd/software_update"),
180-
"{}",
200+
json!({"types": ["apt", "docker"]}).to_string(),
181201
)
182202
.with_retain()])
183203
.await;
@@ -191,6 +211,15 @@ async fn convert_outgoing_software_update_response() -> Result<(), DynError> {
191211
let (mut software_box, _restart_box, mut mqtt_box) =
192212
spawn_mqtt_operation_converter("device/main//").await?;
193213

214+
// Declare supported software types from software actor
215+
software_box
216+
.send(SoftwareCommand::SoftwareCommandMetadata(
217+
SoftwareCommandMetadata {
218+
types: vec!["apt".into(), "docker".into()],
219+
},
220+
))
221+
.await?;
222+
194223
skip_capability_messages(&mut mqtt_box, "device/main//").await;
195224

196225
// Simulate SoftwareUpdate response message received.
@@ -213,9 +242,18 @@ async fn convert_outgoing_software_update_response() -> Result<(), DynError> {
213242
#[tokio::test]
214243
async fn convert_outgoing_restart_response() -> Result<(), DynError> {
215244
// Spawn outgoing mqtt message converter
216-
let (_software_box, mut restart_box, mut mqtt_box) =
245+
let (mut software_box, mut restart_box, mut mqtt_box) =
217246
spawn_mqtt_operation_converter("device/main//").await?;
218247

248+
// Declare supported software types from software actor
249+
software_box
250+
.send(SoftwareCommand::SoftwareCommandMetadata(
251+
SoftwareCommandMetadata {
252+
types: vec!["apt".into(), "docker".into()],
253+
},
254+
))
255+
.await?;
256+
219257
skip_capability_messages(&mut mqtt_box, "device/main//").await;
220258

221259
// Simulate Restart response message received.
@@ -292,8 +330,14 @@ async fn skip_capability_messages(mqtt: &mut impl MessageReceiver<MqttMessage>,
292330
mqtt,
293331
[
294332
(format!("te/{}/cmd/restart", device).as_ref(), "{}"),
295-
(format!("te/{}/cmd/software_list", device).as_ref(), "{}"),
296-
(format!("te/{}/cmd/software_update", device).as_ref(), "{}"),
333+
(
334+
format!("te/{}/cmd/software_list", device).as_ref(),
335+
&json!({"types": ["apt", "docker"]}).to_string(),
336+
),
337+
(
338+
format!("te/{}/cmd/software_update", device).as_ref(),
339+
&json!({"types": ["apt", "docker"]}).to_string(),
340+
),
297341
],
298342
)
299343
.await;

crates/core/tedge_api/src/messages.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,16 @@ impl From<SoftwareError> for Option<SoftwareModuleItem> {
498498
}
499499
}
500500

501+
/// Payload of SoftwareList and SoftwareUpdate commands metadata
502+
#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
503+
#[serde(rename_all = "camelCase")]
504+
pub struct SoftwareCommandMetadata {
505+
#[serde(default)]
506+
pub types: Vec<SoftwareType>,
507+
}
508+
509+
impl<'a> Jsonify<'a> for SoftwareCommandMetadata {}
510+
501511
/// Command to restart a device
502512
pub type RestartCommand = Command<RestartCommandPayload>;
503513

crates/core/tedge_api/src/workflow/mod.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,24 @@ impl OperationWorkflow {
188188
}
189189

190190
/// Return the MQTT message to register support for the operation described by this workflow
191-
pub fn capability_message(&self, schema: &MqttSchema, target: &EntityTopicId) -> Message {
192-
let meta_topic = schema.capability_topic_for(target, self.operation.clone());
193-
let payload = "{}";
194-
Message::new(&meta_topic, payload)
195-
.with_retain()
196-
.with_qos(QoS::AtLeastOnce)
191+
pub fn capability_message(
192+
&self,
193+
schema: &MqttSchema,
194+
target: &EntityTopicId,
195+
) -> Option<Message> {
196+
match self.operation {
197+
// Need to treat SoftwareList and SoftwareUpdate as exceptions as they require software types in the payload
198+
OperationType::SoftwareList | OperationType::SoftwareUpdate => None,
199+
_ => {
200+
let meta_topic = schema.capability_topic_for(target, self.operation.clone());
201+
let payload = "{}".to_string();
202+
Some(
203+
Message::new(&meta_topic, payload)
204+
.with_retain()
205+
.with_qos(QoS::AtLeastOnce),
206+
)
207+
}
208+
}
197209
}
198210

199211
/// Extract the current action to be performed on a command request

crates/core/tedge_api/src/workflow/supervisor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl WorkflowSupervisor {
6363
operations.sort_by(|&a, &b| a.operation.to_string().cmp(&b.operation.to_string()));
6464
operations
6565
.iter()
66-
.map(|workflow| workflow.capability_message(schema, target))
66+
.filter_map(|workflow| workflow.capability_message(schema, target))
6767
.collect()
6868
}
6969

tests/RobotFramework/tests/cumulocity/software_management/software-update-child.robot

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Test Setup Custom Setup
88
Test Teardown Custom Teardown
99

1010
*** Test Cases ***
11+
Supported software types should be declared during startup
12+
[Documentation] #2654 This test will be updated once advanced software management support is implemented
13+
ThinEdgeIO.Set Device Context ${PARENT_SN}
14+
Should Have MQTT Messages topic=te/device/${CHILD_SN}///cmd/software_list minimum=1 maximum=1 message_contains="types":["apt"]
15+
Should Have MQTT Messages topic=te/device/${CHILD_SN}///cmd/software_update minimum=1 maximum=1 message_contains="types":["apt"]
16+
1117
Software list should be populated during startup
1218
Cumulocity.Should Contain Supported Operations c8y_SoftwareUpdate
1319
Device Should Have Installed Software tedge timeout=120

tests/RobotFramework/tests/cumulocity/software_management/software.robot

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ Test Setup Custom Setup
88
Test Teardown Custom Teardown
99

1010
*** Test Cases ***
11+
Supported software types should be declared during startup
12+
[Documentation] #2654 This test will be updated once advanced software management support is implemented
13+
Should Have MQTT Messages topic=te/device/main///cmd/software_list minimum=1 maximum=1 message_contains="types":["apt"]
14+
Should Have MQTT Messages topic=te/device/main///cmd/software_update minimum=1 maximum=1 message_contains="types":["apt"]
15+
1116
Software list should be populated during startup
1217
Device Should Have Installed Software tedge timeout=120
1318

0 commit comments

Comments
 (0)