summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRina Fujino <18257209+rina23q@users.noreply.github.com>2022-04-08 15:30:43 +0200
committerRina Fujino <18257209+rina23q@users.noreply.github.com>2022-04-08 15:30:43 +0200
commit5fcf8b57d45b2d98175bf010c8bbd92ef50550f5 (patch)
tree128cea7b6e124bcbda9beccf6b542f6136e6f24a
parent45ea96aca1e148118d2f1726a5c7ec3669582b4c (diff)
Initial skeleton of c8y_configuration_plugin
- The daemon reads the plugin config file, and reports the supported config types to c8y - Issue tracked by #1030, Specs is by #1028 Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
-rw-r--r--Cargo.lock17
-rw-r--r--Cargo.toml1
-rw-r--r--plugins/c8y_configuration_plugin/Cargo.toml23
-rw-r--r--plugins/c8y_configuration_plugin/src/config.rs150
-rw-r--r--plugins/c8y_configuration_plugin/src/main.rs56
-rw-r--r--plugins/c8y_configuration_plugin/src/smartrest.rs51
6 files changed, 298 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ef2e82a6..c4754627 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -388,6 +388,23 @@ dependencies = [
]
[[package]]
+name = "c8y_configuration_plugin"
+version = "0.6.1"
+dependencies = [
+ "anyhow",
+ "c8y_smartrest",
+ "mqtt_channel",
+ "serde",
+ "tedge_config",
+ "tempfile",
+ "test-case",
+ "thiserror",
+ "tokio",
+ "toml",
+ "tracing",
+]
+
+[[package]]
name = "c8y_smartrest"
version = "0.6.1"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index 328fa3de..9f2297fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = [
"crates/common/*",
"crates/core/*",
"crates/tests/*",
+ "plugins/c8y_configuration_plugin",
"plugins/tedge_apt_plugin",
"plugins/tedge_dummy_plugin",
"plugins/tedge_apama_plugin",
diff --git a/plugins/c8y_configuration_plugin/Cargo.toml b/plugins/c8y_configuration_plugin/Cargo.toml
new file mode 100644
index 00000000..81dd3b97
--- /dev/null
+++ b/plugins/c8y_configuration_plugin/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "c8y_configuration_plugin"
+version = "0.6.1"
+authors = ["thin-edge.io team <info@thin-edge.io>"]
+edition = "2021"
+rust-version = "1.58.1"
+license = "Apache-2.0"
+description = "Thin.edge.io operation plugin for Cumulocity configuration management request"
+
+[dependencies]
+anyhow = "1.0"
+c8y_smartrest = { path = "../../crates/core/c8y_smartrest" }
+serde = { version = "1.0", features = ["derive"] }
+thiserror = "1.0"
+tokio = { version = "1.9", default_features = false, features = [ "fs", "io-util", "macros", "rt-multi-thread","signal"] }
+toml = "0.5"
+tracing = { version = "0.1", features = ["attributes", "log"] }
+tedge_config = { path = "../../crates/common/tedge_config" }
+mqtt_channel = { path = "../../crates/common/mqtt_channel" }
+
+[dev-dependencies]
+tempfile = "3.3"
+test-case = "2.0"
diff --git a/plugins/c8y_configuration_plugin/src/config.rs b/plugins/c8y_configuration_plugin/src/config.rs
new file mode 100644
index 00000000..ecd382ab
--- /dev/null
+++ b/plugins/c8y_configuration_plugin/src/config.rs
@@ -0,0 +1,150 @@
+use serde::Deserialize;
+use std::fs;
+use std::path::PathBuf;
+
+pub const PLUGIN_CONFIG_FILE: &str = "c8y_configuration_plugin.toml";
+
+#[derive(Deserialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct PluginConfig {
+ pub files: Vec<String>,
+}
+
+impl Default for PluginConfig {
+ fn default() -> Self {
+ Self { files: vec![] }
+ }
+}
+
+impl PluginConfig {
+ pub fn new(config_root: PathBuf) -> Self {
+ let config_path = config_root.join(PLUGIN_CONFIG_FILE);
+ let config_path_str = config_path.to_str().unwrap_or(PLUGIN_CONFIG_FILE);
+ Self::read_config(config_path.clone()).add_file(config_path_str.into())
+ }
+
+ fn read_config(path: PathBuf) -> Self {
+ match fs::read_to_string(path) {
+ Ok(contents) => match toml::from_str(contents.as_str()) {
+ Ok(config) => config,
+ _ => Self::default(), // The config file is ill-formed
+ },
+ Err(_) => Self::default(), // The config file does not exist or is not readable
+ }
+ }
+
+ fn add_file(&self, file: String) -> Self {
+ let mut files = self.files.clone();
+ let () = files.push(file);
+ Self { files }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::TempDir;
+ use test_case::test_case;
+
+ #[test]
+ fn deserialize_plugin_config() {
+ let config: PluginConfig = toml::from_str(
+ r#"
+ files = [
+ '/etc/tedge/tedge.toml',
+ '/etc/tedge/mosquitto-conf/c8y-bridge.conf',
+ '/etc/tedge/mosquitto-conf/tedge-mosquitto.conf',
+ '/etc/mosquitto/mosquitto.conf'
+ ]"#,
+ )
+ .unwrap();
+
+ assert_eq!(
+ config.files,
+ vec![
+ "/etc/tedge/tedge.toml".to_string(),
+ "/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(),
+ "/etc/tedge/mosquitto-conf/tedge-mosquitto.conf".to_string(),
+ "/etc/mosquitto/mosquitto.conf".to_string(),
+ ]
+ );
+ }
+
+ #[test_case(
+ r#"files = [
+ '/etc/tedge/tedge.toml',
+ '/etc/tedge/mosquitto-conf/c8y-bridge.conf',
+ '/etc/tedge/mosquitto-conf/tedge-mosquitto.conf',
+ '/etc/mosquitto/mosquitto.conf'
+ ]"#,
+ PluginConfig {
+ files: vec![
+ "/etc/tedge/tedge.toml".to_string(),
+ "/etc/tedge/mosquitto-conf/c8y-bridge.conf".to_string(),
+ "/etc/tedge/mosquitto-conf/tedge-mosquitto.conf".to_string(),
+ "/etc/mosquitto/mosquitto.conf".to_string(),
+ ]
+ }
+ )]
+ #[test_case(
+ r#"files = []"#,
+ PluginConfig {
+ files: vec![]
+ }
+ ;"empty case"
+ )]
+ #[test_case(
+ r#"test"#,
+ PluginConfig {
+ files: vec![]
+ }
+ ;"not toml"
+ )]
+ #[test_case(
+ r#"files = [
+ '/etc/tedge/tedge.toml',
+ '/etc/tedge/mosquitto-conf/c8y-bridge.conf',
+ '/etc/tedge/mosquitto-conf/tedge-mosquitto.conf',
+ '/etc/mosquitto/mosquitto.conf'
+ ]
+ test = false
+ "#,
+ PluginConfig {
+ files: vec![]
+ }
+ ;"unexpected field"
+ )]
+ fn read_plugin_config_file(file_content: &str, raw_config: PluginConfig) -> anyhow::Result<()> {
+ let (_dir, config_root_path) = create_temp_plugin_config(file_content)?;
+ let tmp_path_to_plugin_config = config_root_path.join(PLUGIN_CONFIG_FILE);
+ let tmp_path_to_plugin_config_str = tmp_path_to_plugin_config
+ .to_str()
+ .unwrap_or(PLUGIN_CONFIG_FILE);
+
+ let config = PluginConfig::new(config_root_path.clone());
+
+ // The expected output should contain /tmp/<random>/c8y_configuration_plugin.toml
+ let expected_config = raw_config.add_file(tmp_path_to_plugin_config_str.into());
+
+ assert_eq!(config, expected_config);
+
+ Ok(())
+ }
+
+ // Need to return TempDir, otherwise the dir will be deleted when this function ends.
+ fn create_temp_plugin_config(content: &str) -> std::io::Result<(TempDir, PathBuf)> {
+ let temp_dir = TempDir::new()?;
+ let config_root = temp_dir.path().to_path_buf();
+ let config_file_path = config_root.join(PLUGIN_CONFIG_FILE);
+ let mut file = std::fs::File::create(config_file_path.as_path())?;
+ file.write_all(content.as_bytes())?;
+ Ok((temp_dir, config_root))
+ }
+
+ #[test]
+ fn add_file_to_plugin_config() {
+ let config = PluginConfig::default().add_file("/test/path/file".into());
+ assert_eq!(config.files, vec!["/test/path/file".to_string()])
+ }
+}
diff --git a/plugins/c8y_configuration_plugin/src/main.rs b/plugins/c8y_configuration_plugin/src/main.rs
new file mode 100644
index 00000000..c82100fd
--- /dev/null
+++ b/plugins/c8y_configuration_plugin/src/main.rs
@@ -0,0 +1,56 @@
+mod config;
+mod smartrest;
+
+use crate::config::PluginConfig;
+use c8y_smartrest::topic::C8yTopic;
+use mqtt_channel::{SinkExt, StreamExt};
+use std::path::PathBuf;
+use tedge_config::{get_tedge_config, ConfigSettingAccessor, MqttPortSetting};
+use tracing::{debug, error, info, instrument, warn};
+
+const CONFIG_ROOT_PATH: &str = "/etc/tedge/c8y";
+
+async fn create_mqtt_client() -> Result<mqtt_channel::Connection, anyhow::Error> {
+ let tedge_config = get_tedge_config()?;
+ let mqtt_port = tedge_config.query(MqttPortSetting)?.into();
+ let mqtt_config = mqtt_channel::Config::default()
+ .with_port(mqtt_port)
+ .with_subscriptions(mqtt_channel::TopicFilter::new_unchecked(
+ C8yTopic::SmartRestRequest.as_str(),
+ ));
+
+ let mqtt_client = mqtt_channel::Connection::new(&mqtt_config).await?;
+ Ok(mqtt_client)
+}
+
+#[tokio::main]
+async fn main() -> Result<(), anyhow::Error> {
+ // Create required clients
+ let mut mqtt_client = create_mqtt_client().await?;
+
+ let plugin_config = PluginConfig::new(PathBuf::from(CONFIG_ROOT_PATH));
+
+ // Publish supported configuration types
+ let msg = plugin_config.to_message()?;
+ let () = mqtt_client.published.send(msg).await?;
+
+ // mqtt loop
+ while let Some(message) = mqtt_client.received.next().await {
+ debug!("Received {:?}", message);
+ match message.payload_str()?.split_at(3).0 {
+ "524" => {
+ debug!("{}", message.payload_str()?);
+ todo!() // c8y_DownloadConfigFile
+ }
+ "526" => {
+ debug!("{}", message.payload_str()?);
+ todo!() // c8y_UploadConfigFile
+ }
+ _ => {}
+ }
+ }
+
+ mqtt_client.close().await;
+
+ Ok(())
+}
diff --git a/plugins/c8y_configuration_plugin/src/smartrest.rs b/plugins/c8y_configuration_plugin/src/smartrest.rs
new file mode 100644
index 00000000..6822f5a7
--- /dev/null
+++ b/plugins/c8y_configuration_plugin/src/smartrest.rs
@@ -0,0 +1,51 @@
+use crate::config::PluginConfig;
+use c8y_smartrest::{
+ smartrest_serializer::{
+ SmartRestSerializer, SmartRestSetOperationToExecuting, SmartRestSetOperationToSuccessful,
+ },
+ topic::C8yTopic,
+};
+use mqtt_channel::Message;
+
+impl PluginConfig {
+ pub fn to_message(&self) -> Result<Message, anyhow::Error> {
+ let topic = C8yTopic::SmartRestResponse.to_topic()?;
+ Ok(Message::new(&topic, self.to_smartrest_payload()))
+ }
+
+ // 119,typeA,typeB,...
+ fn to_smartrest_payload(&self) -> String {
+ let config_types = self
+ .files
+ .iter()
+ .map(|x| x.to_string())
+ .collect::<Vec<_>>()
+ .join(",");
+ format!("119,{config_types}")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use test_case::test_case;
+
+ #[test_case(
+ PluginConfig {
+ files: vec!["typeA".to_string()]
+ },
+ "119,typeA".to_string()
+ ;"single file"
+ )]
+ #[test_case(
+ PluginConfig {
+ files: vec!["typeA".to_string(), "typeB".to_string(), "typeC".to_string()]
+ },
+ "119,typeA,typeB,typeC".to_string()
+ ;"multiple files"
+ )]
+ fn get_smartrest(input: PluginConfig, expected_output: String) {
+ let output = input.to_smartrest_payload();
+ assert_eq!(output, expected_output);
+ }
+}