-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add c8y-firmware-plugin #1719
Add c8y-firmware-plugin #1719
Conversation
6388c57
to
e953604
Compare
The first skeleton to receive SmartREST messages from c8y Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
bf9a920
to
aea4839
Compare
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
6bdcabe
to
9c64424
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One aspect that I didn't fully understand from the impl is how the child devices would send their current firmware details to C8Y using the set firmware message? There was some contract for the c8y-config-plugin
to send the initial config list to tedge/c8y. Something similar is needed here as well, right?
crates/common/tedge_config/src/tedge_config_cli/models/seconds.rs
Outdated
Show resolved
Hide resolved
crates/common/tedge_config/src/tedge_config_cli/models/seconds.rs
Outdated
Show resolved
Hide resolved
if smartrest_request.device == self.tedge_device_id { | ||
warn!("c8y-firmware-plugin does not support firmware operation for the main tedge device. \ | ||
Please define a custom operation handler for the c8y_Firmware operation."); | ||
Ok(()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing that Ok(())
here surprised me a bit as I thought that it'd make the operation succeed in the cloud. But since that's not the case, it's fine. May be worth adding a comment that we're ignoring this request so that some other plugin on the machine can handle it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be worth adding a comment that we're ignoring this request so that some other plugin on the machine can handle it.
I think it's our basic principle, if plugin doesn't address either parent device or child device, ignore the operations coming from c8y, so that user's custom plugin can consume them.
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
- Improve log messages - Add validation of response from child device - Add `--init` option as a placeholder for the future enhancements Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
9c64424
to
4402a17
Compare
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's still scope for some more refactoring to simplify the error propagation. Failing operations in the cloud is done from multiple places which can be avoided with some error propagation refactoring.
pub default_http_bind_address: IpAddress, | ||
|
||
/// Default firmware operation timeout in seconds | ||
pub default_firmware_timeout: Seconds, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub default_firmware_timeout: Seconds, | |
pub default_firmware_update_timeout: Seconds, |
use tokio::sync::Mutex; | ||
|
||
// FIXME!: Think of good text | ||
const AFTER_HELP_TEXT: &str = r#"We will write later!"#; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget ;)
I think we can split the initial setting of the firmware when the child device starts in another PR, as we still need to think about the interface here. In the meantime the child device adapter/connector can update the firmware in the |
@@ -58,6 +58,15 @@ pub fn create_directory_with_mode(dir: impl AsRef<Path>, mode: u32) -> Result<() | |||
perm_entry.create_directory(dir.as_ref()) | |||
} | |||
|
|||
pub fn create_file_with_mode( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather create a single create_file
function that accepts the user
, group
and mode
as Option
s. This single function would be used everywhere and would help us avoid having to come up with so many variants like create_file_with_mode
and create_file_with_user_group
for each combination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done independently as this PR is already large.
That said I would suggest yet another direction (for later!). I agree with Albin that we cannot have a method per combination, but I also understand the need for something more explicit than a function with 3 optional arguments. So I would add with_user
, with_group
and with_mode
method to PermissionEntry
. So we could write: PermissionEntry::new().with_mode(644).create_file(path, content)
.
id: op_id.to_string(), | ||
}) | ||
} | ||
fn get_file_path(&self, op_id: &str) -> PathBuf { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn get_file_path(&self, op_id: &str) -> PathBuf { | |
fn get_operation_file_path(&self, op_id: &str) -> PathBuf { |
To be more contextual.
@@ -598,16 +607,22 @@ pub struct FirmwareOperationEntry { | |||
} | |||
|
|||
impl FirmwareOperationEntry { | |||
fn create_file(&self) -> Result<(), FirmwareManagementError> { | |||
let path = PersistentStore::get_file_path(&self.operation_id); | |||
fn create_file( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels more like this create_file
or rather create_operation_file
and overwrite_file
functions should have been part of the PresistentStore
as creating a file in the store should have been a function of that store.
// TODO! We should make it configurable by tedge config later. | ||
const PERSISTENT_DIR_PATH: &str = "/var/tedge"; | ||
|
||
pub const CACHE_DIR_NAME: &str = "cache"; | ||
pub const FILE_TRANSFER_DIR_NAME: &str = "file-transfer"; | ||
pub const PERSISTENT_STORE_DIR_NAME: &str = "firmware"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These constants better belong on the firmware_manager
module itself so that they are portable when we migrate this plugin to the actor model with a different main.
const PERSISTENT_DIR_PATH: &str = "/var/tedge"; | ||
|
||
pub const CACHE_DIR_NAME: &str = "cache"; | ||
pub const FILE_TRANSFER_DIR_NAME: &str = "file-transfer"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO for later: This constant ideally should have been defined in some tedge-api
crate so that all plugins using the file-transfer functionality could reuse it.
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
1. Move the creation of 3 directories (cache, file-transfer, firmware) to `--init` option. 2. The path of `/var/tedge` is given as argument of FirmwareManager, so that test can use /tmp directory instead. 3. Remove permission check of persistent store files. 4. Stop using anyhow::Error, instead, FirmwareManagement error is used. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Robot Results
Passed Tests
|
- Improve error handling - Add more integration tests Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
I believe now this PR needs only minor fixes, therefore, change the state from Draft to Open. |
.await?; | ||
|
||
// Assert the c8y_Firmware operation status mapping to EXECUTING(501) and FAILED(502) | ||
mqtt_tests::assert_received_all_expected( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you need to make sure that the timeout period has elapsed before doing this assertion? With a sleep
may be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The firmware response timeout is 1 sec. We wait 5 seconds to check if all expected message are received inside assert. I don't think we need sleep.
fa125e9
to
f98d246
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds good and ready to be merged.
Just need to provide some proper help text.
@@ -58,6 +58,15 @@ pub fn create_directory_with_mode(dir: impl AsRef<Path>, mode: u32) -> Result<() | |||
perm_entry.create_directory(dir.as_ref()) | |||
} | |||
|
|||
pub fn create_file_with_mode( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done independently as this PR is already large.
That said I would suggest yet another direction (for later!). I agree with Albin that we cannot have a method per combination, but I also understand the need for something more explicit than a function with 3 optional arguments. So I would add with_user
, with_group
and with_mode
method to PermissionEntry
. So we could write: PermissionEntry::new().with_mode(644).create_file(path, content)
.
Ok(()) | ||
} | ||
|
||
// TODO: Move some to functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this now with 813e745!
It sounds good. I'd like to address in next PR! |
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
f4e570e
to
150fc0b
Compare
- Revert the ResponsePayload.status to non Option type. - Change the type of http_proxy as only one http_proxy is used. - handle_child_device_firmware_update_response() publishes mqtt messages directly inside the function. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
2b54971
to
c45e82c
Compare
Proposed changes
This PR should implement as described in the ticket #1696.
The following memo might help you to understand the code better.
c8y-firmware-plugin --init
creates 3 required directories:/var/tedge/cache
,/var/tedge/firmware
, and/var/tedge/file-transfer
. Make sure that/var/tedge/file-transfer
can be also created bytedge-agent
upon the first POST request./tmp
first, then move to/var/tedge/cache
. If/var/tedge/cache
doesn't exist,this plugin creates the directory in runtimereturns error./var/tedge/file-transfer/<child-id>/firmware_update
will keep a symlink of the file.If this directory doesn't exist, this plugin creates the directory in runtime.If/var/tedge/file-transfer
directory doesn't exist, returns error./<child-id>/firmware_update
directory will be created in runtime.sha256
field of request and response JSON is the sha256 of file itself. Checksum if they downloaded correct file.tedge config set firmware.timeout 200
tedge config set firmware.child.update.timeout 200
.status
field in the response from child device is invalid but operation ID is correct, the operation status is treatedNone
internally. From user, it will be an error.NOT in this PR's scope
c8y_Firmware
. This will be created by user.TODO before merge
c8y-firmware-plugin --help
.--init
creates directories.firmware.timeout
tofirmware.child.update.timeout
.Types of changes
Paste Link to the issue
#1696
Checklist
cargo fmt
as mentioned in CODING_GUIDELINEScargo clippy
as mentioned in CODING_GUIDELINESFurther comments
Directory/file permissions under
/var/tedge
and its security./var/tedge
is created bytedge-agent --init
owned bytedge:tedge
./var/tedge/file-transfer
is created bytedge-agent
owned bytedge:tedge upon
PUT` request. (timing of child device bootstrapping of config plugin)/var/tedge/file-transfer/<child-id>
is created bytedge-agent
owned bytedge:tedge
uponPUT
request. (timing of child device bootstrapping of config plugin)/var/tedge/file-transfer/<child-id>/config_update
is created byc8y-configuration-plugin
owned byroot:root
.This plugin needs them. Now c8y-firmware-plugin is supposed to be run by
tedge
user, therefore, they will be owned bytedge:tedge
. But it is a security risk as any one can modify via tedge-agent http server./var/tedge/cache/<file>
/var/tedge/file-transfer/<child-id>/firmware_update/<symlink>