Skip to content
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

Retry http requests to c8y fails with the response status code 401,403 and 404 #2065

Merged
merged 1 commit into from
Jul 26, 2023

Conversation

PradeepKiruvale
Copy link
Contributor

@PradeepKiruvale PradeepKiruvale commented Jul 10, 2023

Proposed changes

When an HTTP interaction with Cumulocity fails with

  • status code 401 or 403, then get the renewed JWT token and retry the HTTP request.
  • status code 404 then get the new internal-id and retry the HTTP request.
  • use map inside the C8yEndPoint to cache child_internal_id and main_device_internal id. Also, cache the token in C8yEndPoint

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Improvement (general improvements like code refactoring that doesn't explicitly fix a bug or add any new functionality)
  • Documentation Update (if none of the other choices apply)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Paste Link to the issue

#1201

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA (in all commits with git commit -s)
  • I ran cargo fmt as mentioned in CODING_GUIDELINES
  • I used cargo clippy as mentioned in CODING_GUIDELINES
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Further comments

@github-actions
Copy link
Contributor

github-actions bot commented Jul 10, 2023

Robot Results

✅ Passed ❌ Failed ⏭️ Skipped Total Pass %
239 0 5 239 100

Passed Tests

Name ⏱️ Duration Suite
Define Child device 1 ID 0.032 s C8Y Child Alarms Rpi
Normal case when the child device does not exist on c8y cloud 3.121 s C8Y Child Alarms Rpi
Normal case when the child device already exists 0.951 s C8Y Child Alarms Rpi
Reconciliation when the new alarm message arrives, restart the mapper 1.15 s C8Y Child Alarms Rpi
Reconciliation when the alarm that is cleared 65.64 s C8Y Child Alarms Rpi
Prerequisite Parent 13.045 s Child Conf Mgmt Plugin
Prerequisite Child 0.264 s Child Conf Mgmt Plugin
Child device bootstrapping 12.141 s Child Conf Mgmt Plugin
Snapshot from device 63.279 s Child Conf Mgmt Plugin
Child device config update 63.05 s Child Conf Mgmt Plugin
Configuration types should be detected on file change (without restarting service) 44.232 s Inotify Crate
Check lock file existence in default folder 1.467 s Lock File
Check PID number in lock file 1.838 s Lock File
Check PID number in lock file after restarting the services 62.064 s Lock File
Check starting same service twice 1.063 s Lock File
Switch off lock file creation 2.219 s Lock File
Set configuration when file exists 12.427 s Configuration Operation
Set configuration when file does not exist 5.151 s Configuration Operation
Set configuration with broken url 4.464 s Configuration Operation
Get configuration 5.115 s Configuration Operation
Get non existent configuration file 4.87 s Configuration Operation
Get non existent configuration type 4.377 s Configuration Operation
Update configuration plugin config via cloud 5.366 s Configuration Operation
Modify configuration plugin config via local filesystem modify inplace 4.862 s Configuration Operation
Modify configuration plugin config via local filesystem overwrite 4.75 s Configuration Operation
Update configuration plugin config via local filesystem copy 4.432 s Configuration Operation
Update configuration plugin config via local filesystem move (different directory) 4.644 s Configuration Operation
Update configuration plugin config via local filesystem move (same directory) 3.497 s Configuration Operation
Update the custom operation dynamically 51.007 s Dynamically Reload Operation
Custom operation successful 123.327 s Custom Operation
Custom operation fails 72.155 s Custom Operation
Successful firmware operation 120.857 s Firmware Operation
Install with empty firmware name 112.58 s Firmware Operation
Prerequisite Parent 14.931000000000001 s Firmware Operation Child Device
Prerequisite Child 8.104 s Firmware Operation Child Device
Child device firmware update 4.432 s Firmware Operation Child Device
Child device firmware update with cache 6.048 s Firmware Operation Child Device
Firmware plugin supports restart via service manager #1932 4.693 s Firmware Operation Child Device Retry
Update Inventory data via inventory.json 1.28 s Inventory Update
Inventory includes the agent fragment with version information 0.908 s Inventory Update
Retrieve a JWT tokens 26.807 s Jwt Request
Mapper recovers and processes output of ongoing software update request 15.521 s Recover And Publish Software Update Message
Check running collectd 1.4729999999999999 s Monitor Device Collectd
Is collectd publishing MQTT messages? 3.016 s Monitor Device Collectd
Check thin-edge monitoring 4.127 s Monitor Device Collectd
Check grouping of measurements 8.958 s Monitor Device Collectd
Main device registration 2.425 s Device Registration
Child device registration 2.415 s Device Registration
Supports restarting the device 41.26 s Restart Device
Update tedge version from previous using Cumulocity 76.458 s Tedge Self Update
Test if all c8y services are up 52.609 s Service Monitoring
Test if all c8y services are down 97.849 s Service Monitoring
Test if all c8y services are using configured service type 214.696 s Service Monitoring
Test if all c8y services using default service type when service type configured as empty 152.022 s Service Monitoring
Check health status of tedge-mapper-c8y service on broker stop start 22.503 s Service Monitoring
Check health status of tedge-mapper-c8y service on broker restart 23.764 s Service Monitoring
Check health status of child device service 18.745 s Service Monitoring
Successful shell command with output 4.727 s Shell Operation
Check Successful shell command with literal double quotes output 3.496 s Shell Operation
Execute multiline shell command 3.873 s Shell Operation
Failed shell command 3.838 s Shell Operation
Software list should be populated during startup 42.871 s Software
Install software via Cumulocity 39.595 s Software
tedge-agent should terminate on SIGINT while downloading file 34.486 s Software
Software list should only show currently installed software and not candidates 40.425 s Software
Create and publish the tedge agent supported operations on mapper restart 42.161 s Mapper-Publishing-Agent-Supported-Ops
Agent gets the software list request once it comes up 27.733 s Mapper-Publishing-Agent-Supported-Ops
Child devices support sending simple measurements 4.122 s Child Device Telemetry
Child devices support sending custom measurements 1.325 s Child Device Telemetry
Child devices support sending custom events 1.26 s Child Device Telemetry
Child devices support sending custom events overriding the type 3.516 s Child Device Telemetry
Child devices support sending custom alarms #1699 1.2810000000000001 s Child Device Telemetry
Child devices support sending inventory data via c8y topic 3.24 s Child Device Telemetry
Child device supports sending custom child device measurements directly to c8y 1.764 s Child Device Telemetry
Check retained alarms 209.976 s Raise Alarms
Thin-edge devices support sending simple measurements 1.6720000000000002 s Thin-Edge Device Telemetry
Thin-edge devices support sending simple measurements with custom type 1.121 s Thin-Edge Device Telemetry
Thin-edge devices support sending custom measurements 1.065 s Thin-Edge Device Telemetry
Thin-edge devices support sending custom events 1.1400000000000001 s Thin-Edge Device Telemetry
Thin-edge devices support sending custom events overriding the type 1.109 s Thin-Edge Device Telemetry
Thin-edge devices support sending custom alarms #1699 3.33 s Thin-Edge Device Telemetry
Thin-edge device supports sending custom Thin-edge device measurements directly to c8y 1.6059999999999999 s Thin-Edge Device Telemetry
Thin-edge device support sending inventory data via c8y topic 1.681 s Thin-Edge Device Telemetry
thin-edge components support a custom config-dir location via flags 26.526 s Config Dir
Validate updated data path used by tedge-agent 0.145 s Data Path Config
Validate updated data path used by c8y-firmware-plugin 10.417 s Data Path Config
Validate updated data path used by tedge-agent 0.278 s Log Path Config
Check existence of init directories 0.711 s Tedge Init
Tedge init and check creation of folders 0.771 s Tedge Init
Check ownership of the folders 1.1179999999999999 s Tedge Init
Change user/group and check the change 1.025 s Tedge Init
Tedge init and check if default values are restored 0.943 s Tedge Init
Install thin-edge via apt 45.493 s Install Apt
Install latest via script (from current branch) 29.159 s Install Tedge
Install specific version via script (from current branch) 20.61 s Install Tedge
Install latest tedge via script (from main branch) 25.468 s Install Tedge
Install then uninstall latest tedge via script (from main branch) 42.043 s Install Tedge
Support starting and stopping services 39.281 s Service-Control
Supports a reconnect 54.104 s Test-Commands
Supports disconnect then connect 37.659 s Test-Commands
Update unknown setting 37.671 s Test-Commands
Update known setting 30.143 s Test-Commands
It checks MQTT messages using a pattern 69.169 s Test-Mqtt
Stop c8y-configuration-plugin 0.188 s Health C8Y-Configuration-Plugin
Update the service file 0.151 s Health C8Y-Configuration-Plugin
Reload systemd files 0.535 s Health C8Y-Configuration-Plugin
Start c8y-configuration-plugin 0.204 s Health C8Y-Configuration-Plugin
Start watchdog service 10.15 s Health C8Y-Configuration-Plugin
Check PID of c8y-configuration-plugin 0.1 s Health C8Y-Configuration-Plugin
Kill the PID 0.351 s Health C8Y-Configuration-Plugin
Recheck PID of c8y-configuration-plugin 6.31 s Health C8Y-Configuration-Plugin
Compare PID change 0.001 s Health C8Y-Configuration-Plugin
Stop watchdog service 0.147 s Health C8Y-Configuration-Plugin
Remove entry from service file 0.098 s Health C8Y-Configuration-Plugin
Stop c8y-log-plugin 0.186 s Health C8Y-Log-Plugin
Update the service file 0.137 s Health C8Y-Log-Plugin
Reload systemd files 0.351 s Health C8Y-Log-Plugin
Start c8y-log-plugin 0.094 s Health C8Y-Log-Plugin
Start watchdog service 10.156 s Health C8Y-Log-Plugin
Check PID of c8y-log-plugin 0.098 s Health C8Y-Log-Plugin
Kill the PID 0.32 s Health C8Y-Log-Plugin
Recheck PID of c8y-log-plugin 6.378 s Health C8Y-Log-Plugin
Compare PID change 0.001 s Health C8Y-Log-Plugin
Stop watchdog service 0.13 s Health C8Y-Log-Plugin
Remove entry from service file 0.106 s Health C8Y-Log-Plugin
Stop tedge-mapper 0.189 s Health Tedge Mapper C8Y
Update the service file 0.276 s Health Tedge Mapper C8Y
Reload systemd files 0.582 s Health Tedge Mapper C8Y
Start tedge-mapper 0.221 s Health Tedge Mapper C8Y
Start watchdog service 10.299 s Health Tedge Mapper C8Y
Check PID of tedge-mapper 0.081 s Health Tedge Mapper C8Y
Kill the PID 0.128 s Health Tedge Mapper C8Y
Recheck PID of tedge-mapper 6.679 s Health Tedge Mapper C8Y
Compare PID change 0.006 s Health Tedge Mapper C8Y
Stop watchdog service 0.176 s Health Tedge Mapper C8Y
Remove entry from service file 0.164 s Health Tedge Mapper C8Y
Stop tedge-agent 0.183 s Health Tedge-Agent
Update the service file 0.12 s Health Tedge-Agent
Reload systemd files 0.617 s Health Tedge-Agent
Start tedge-agent 0.151 s Health Tedge-Agent
Start watchdog service 10.32 s Health Tedge-Agent
Check PID of tedge-mapper 0.066 s Health Tedge-Agent
Kill the PID 0.119 s Health Tedge-Agent
Recheck PID of tedge-agent 6.523 s Health Tedge-Agent
Compare PID change 0.006 s Health Tedge-Agent
Stop watchdog service 0.147 s Health Tedge-Agent
Remove entry from service file 0.398 s Health Tedge-Agent
Stop tedge-mapper-az 0.098 s Health Tedge-Mapper-Az
Update the service file 0.098 s Health Tedge-Mapper-Az
Reload systemd files 0.427 s Health Tedge-Mapper-Az
Start tedge-mapper-az 0.111 s Health Tedge-Mapper-Az
Start watchdog service 10.181 s Health Tedge-Mapper-Az
Check PID of tedge-mapper-az 0.124 s Health Tedge-Mapper-Az
Kill the PID 0.245 s Health Tedge-Mapper-Az
Recheck PID of tedge-agent 6.679 s Health Tedge-Mapper-Az
Compare PID change 0.001 s Health Tedge-Mapper-Az
Stop watchdog service 0.147 s Health Tedge-Mapper-Az
Remove entry from service file 0.162 s Health Tedge-Mapper-Az
Stop tedge-mapper-collectd 0.169 s Health Tedge-Mapper-Collectd
Update the service file 0.15 s Health Tedge-Mapper-Collectd
Reload systemd files 0.49 s Health Tedge-Mapper-Collectd
Start tedge-mapper-collectd 0.141 s Health Tedge-Mapper-Collectd
Start watchdog service 10.157 s Health Tedge-Mapper-Collectd
Check PID of tedge-mapper-collectd 0.178 s Health Tedge-Mapper-Collectd
Kill the PID 0.183 s Health Tedge-Mapper-Collectd
Recheck PID of tedge-mapper-collectd 6.462 s Health Tedge-Mapper-Collectd
Compare PID change 0.001 s Health Tedge-Mapper-Collectd
Stop watchdog service 0.164 s Health Tedge-Mapper-Collectd
Remove entry from service file 0.119 s Health Tedge-Mapper-Collectd
tedge-collectd-mapper health status 5.591 s Health Tedge-Mapper-Collectd
c8y-log-plugin health status 5.726 s MQTT health endpoints
c8y-configuration-plugin health status 5.531 s MQTT health endpoints
Publish on a local insecure broker 0.169 s Basic Pub Sub
Publish on a local secure broker 1.9849999999999999 s Basic Pub Sub
Publish on a local secure broker with client authentication 2.88 s Basic Pub Sub
Publish events to subscribed topic 0.435 s Custom Sub Topics Tedge-Mapper-Aws
Publish measurements to unsubscribed topic 5.296 s Custom Sub Topics Tedge-Mapper-Aws
Publish measurements to subscribed topic 0.24 s Custom Sub Topics Tedge-Mapper-Az
Publish measurements to unsubscribed topic 5.225 s Custom Sub Topics Tedge-Mapper-Az
Publish events to subscribed topic 0.27 s Custom Sub Topics Tedge-Mapper-C8Y
Publish measurements to unsubscribed topic 5.212 s Custom Sub Topics Tedge-Mapper-C8Y
Check remote mqtt broker #1773 3.545 s Remote Mqtt Broker
Apply name filter 0.191 s Filter Packages List Output
Apply maintainer filter 0.196 s Filter Packages List Output
Apply both filters 0.183 s Filter Packages List Output
No filters 0.211 s Filter Packages List Output
Both filters but name filter as empty string 0.164 s Filter Packages List Output
Both filters but maintainer filter as empty string 0.163 s Filter Packages List Output
Both filters as empty string 0.194 s Filter Packages List Output
Wrong package name 0.143 s Improve Tedge Apt Plugin Error Messages
Wrong version 0.171 s Improve Tedge Apt Plugin Error Messages
Wrong type 0.388 s Improve Tedge Apt Plugin Error Messages
tedge_connect_test_positive 0.305 s Tedge Connect Test
tedge_connect_test_negative 1.038 s Tedge Connect Test
tedge_connect_test_sm_services 8.621 s Tedge Connect Test
tedge_disconnect_test_sm_services 60.401 s Tedge Connect Test
Install thin-edge.io 15.984 s Call Tedge
call tedge -V 0.091 s Call Tedge
call tedge -h 0.077 s Call Tedge
call tedge -h -V 0.13 s Call Tedge
call tedge help 0.138 s Call Tedge
tedge config list 0.081 s Call Tedge Config List
tedge config list --all 0.063 s Call Tedge Config List
set/unset device.type 0.223 s Call Tedge Config List
set/unset device.key_path 0.291 s Call Tedge Config List
set/unset device.cert_path 0.243 s Call Tedge Config List
set/unset c8y.root_cert_path 0.339 s Call Tedge Config List
set/unset c8y.smartrest.templates 0.478 s Call Tedge Config List
set/unset c8y.topics 0.411 s Call Tedge Config List
set/unset az.root_cert_path 0.338 s Call Tedge Config List
set/unset az.topics 0.466 s Call Tedge Config List
set/unset aws.topics 0.465 s Call Tedge Config List
set/unset aws.url 0.71 s Call Tedge Config List
set/unset aws.root_cert_path 0.429 s Call Tedge Config List
set/unset aws.mapper.timestamp 0.304 s Call Tedge Config List
set/unset az.mapper.timestamp 0.323 s Call Tedge Config List
set/unset mqtt.bind.address 0.299 s Call Tedge Config List
set/unset mqtt.bind.port 0.244 s Call Tedge Config List
set/unset http.bind.port 0.225 s Call Tedge Config List
set/unset tmp.path 0.222 s Call Tedge Config List
set/unset logs.path 0.282 s Call Tedge Config List
set/unset run.path 0.244 s Call Tedge Config List
set/unset firmware.child.update.timeout 0.333 s Call Tedge Config List
set/unset c8y.url 0.315 s Call Tedge Config List
set/unset az.url 0.345 s Call Tedge Config List
set/unset mqtt.external.bind.port 0.307 s Call Tedge Config List
mqtt.external.bind.address 0.235 s Call Tedge Config List
mqtt.external.bind.interface 0.29 s Call Tedge Config List
set/unset mqtt.external.ca_path 0.21 s Call Tedge Config List
set/unset mqtt.external.cert_file 0.24 s Call Tedge Config List
set/unset mqtt.external.key_file 0.228 s Call Tedge Config List
set/unset software.plugin.default 0.258 s Call Tedge Config List
Get Put Delete 1.6440000000000001 s Http File Transfer Api
Set keys should return value on stdout 0.2 s Tedge Config Get
Unset keys should not return anything on stdout and warnings on stderr 0.352 s Tedge Config Get
Invalid keys should not return anything on stdout and warnings on stderr 0.271 s Tedge Config Get
Set configuration via environment variables 0.631 s Tedge Config Get
Set configuration via environment variables for topics 0.241 s Tedge Config Get
Set unknown configuration via environment variables 0.062 s Tedge Config Get

crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
Comment on lines 255 to 257
// get new internal id not the cached one
self.end_point.c8y_internal_id = "".into();
let internal_id = match child_device_id {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would introduce a get_fresh_internal_id() or renew_internal_id() method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored

crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jul 11, 2023

Codecov Report

Merging #2065 (5d5c541) into main (293c7ea) will increase coverage by 0.1%.
The diff coverage is 72.3%.

❗ Current head 5d5c541 differs from pull request most recent head ac3ce07. Consider uploading reports for the commit ac3ce07 to get more accurate results

Additional details and impacted files
Files Changed Coverage Δ
crates/core/c8y_api/src/json_c8y.rs 83.8% <0.0%> (-3.9%) ⬇️
crates/extensions/c8y_http_proxy/src/messages.rs 33.3% <0.0%> (-3.1%) ⬇️
crates/extensions/c8y_mapper_ext/src/tests.rs 93.4% <ø> (ø)
crates/extensions/c8y_http_proxy/src/actor.rs 40.0% <45.1%> (+6.8%) ⬆️
crates/extensions/c8y_mapper_ext/src/converter.rs 78.8% <54.5%> (+<0.1%) ⬆️
crates/extensions/c8y_http_proxy/src/tests.rs 90.7% <90.7%> (-0.1%) ⬇️
crates/core/c8y_api/src/http_proxy.rs 70.2% <97.1%> (+3.3%) ⬆️
crates/extensions/c8y_config_manager/src/tests.rs 88.9% <100.0%> (ø)
crates/extensions/c8y_config_manager/src/upload.rs 67.1% <100.0%> (ø)
...ates/extensions/c8y_firmware_manager/src/config.rs 68.0% <100.0%> (ø)
... and 2 more

... and 4 files with indirect coverage changes

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request July 11, 2023 12:04 — with GitHub Actions Inactive
Copy link
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current design is too fragile.

crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
crates/extensions/c8y_http_proxy/src/actor.rs Outdated Show resolved Hide resolved
Comment on lines 202 to 207
let req_builder =
|token: String| HttpRequestBuilder::get(&url_get_id).bearer_auth(token);

self.get_and_set_jwt_token().await?;
let request_builder =
req_builder(self.cached_identifiers.token.clone().unwrap_or_default());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the closure is used along its definition, this seems to be useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored, not needed any more.

crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
let mut url_update_swlist = self.get_base_url();
url_update_swlist.push_str("/inventory/managedObjects/");
url_update_swlist.push_str(&self.c8y_internal_id);
url_update_swlist.push_str(&self.get_internal_id(device_id).unwrap_or_default());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the meaning of an empty string for an internal id? This unwrap is suspicious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to return an error if the internal-id is an empty string.

Comment on lines 132 to 138
while self.end_point.get_internal_id(None).is_none() {
if let Err(error) = self
.try_get_and_set_internal_id(self.end_point.device_id.clone())
.await
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this initialization loop still required, now that the code is ready to retry on obsolete internal ids?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later we retry only once if there is any failure. I feel this is important because during the init phase the software list will be sent to c8y. If the call fails because of internal ID then the list will be missing on the c8y. So, it's better to retry for the first time and move on only if there is an internal ID.

Comment on lines 186 to 193
let build_request = |end_point: &C8yEndPoint| -> Result<HttpRequestBuilder, C8YRestError> {
Ok(HttpRequestBuilder::get(&url_get_id)
.bearer_auth(end_point.token.clone().unwrap_or_default()))
};

async fn try_get_internal_id(
&mut self,
device_id: Option<&str>,
) -> Result<String, C8YRestError> {
let url_get_id = self.end_point.get_url_for_get_id(device_id);
let request_builder = build_request(&self.end_point)?;

let request = request_builder.build()?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point to create and use the build_request closure in the same scope. Why not a simple method to build the request?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This closure is not just used inside this function. When the first attempt fails because of 401/403 then the retries with the new token. Then it calls the try_with_fresh_token, this function takes the `closure. So, I created a closure here.

Copy link
Contributor

@albinsuresh albinsuresh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some cosmetic comments.

}
}

pub fn get_c8y_internal_id(&self) -> &str {
&self.c8y_internal_id
pub fn get_internal_id(&self, id: Option<&str>) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn get_internal_id(&self, id: Option<&str>) -> Option<String> {
pub fn get_internal_id(&self, child_id: Option<&str>) -> Option<String> {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep it as device_id, which is more generic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested child_id specifically since this API is expecting only child devices IDs as that argument and None for the main device. Keeping it generic would have been fine if this API accepted the main device's id as well. But this suggestion Didier seems like the best way to avoid all such confusion.


url_update_swlist
}

pub fn get_url_for_get_id(&self, device_id: Option<&str>) -> String {
pub fn get_url_for_get_id(&self, device_id: String) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn get_url_for_get_id(&self, device_id: String) -> String {
pub fn get_url_for_internal_id(&self, device_id: &str) -> String {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -47,18 +61,18 @@ impl C8yEndPoint {
url_get_id
}

pub fn get_url_for_sw_list(&self) -> String {
pub fn get_url_for_sw_list(&self, device_id: Option<&str>) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn get_url_for_sw_list(&self, device_id: Option<&str>) -> String {
pub fn get_url_for_sw_list(&self, child_id: Option<&str>) -> String {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this generic name.

let mut url_update_swlist = self.get_base_url();
url_update_swlist.push_str("/inventory/managedObjects/");
url_update_swlist.push_str(&self.c8y_internal_id);
url_update_swlist.push_str(&self.get_internal_id(device_id).unwrap_or_default());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that unwrap_or_default there's the risk of broken URLs for unknown child devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, it returns error if the internal id is empty.

Comment on lines 186 to 189
let build_request = |end_point: &C8yEndPoint| -> Result<HttpRequestBuilder, C8YRestError> {
Ok(HttpRequestBuilder::get(&url_get_id)
.bearer_auth(end_point.token.clone().unwrap_or_default()))
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let build_request = |end_point: &C8yEndPoint| -> Result<HttpRequestBuilder, C8YRestError> {
Ok(HttpRequestBuilder::get(&url_get_id)
.bearer_auth(end_point.token.clone().unwrap_or_default()))
};
let build_request = |end_point: &C8yEndPoint| -> HttpRequestBuilder {
HttpRequestBuilder::get(&url_get_id)
.bearer_auth(end_point.token.clone().unwrap_or_default())
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it like this just to reuse the retry code when it fails. So, the try_with_fresh_jwt_token function takes a closure that returns the Result. So, this has to be consistent with that.

self.get_and_set_jwt_token().await
}

async fn retry_request_with_fresh_token(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async fn retry_request_with_fresh_token(
async fn try_request_with_fresh_token(

As the API is doing just that. It's just that we are using it on retries only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Ok(self.peers.http.await_response(request).await?)
}

async fn create_event(
async fn retry_request_with_fresh_internal_id(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async fn retry_request_with_fresh_internal_id(
async fn try_request_with_fresh_internal_id(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines 270 to 273
.clear_the_cached_internal_id(device_id.clone());
// get new internal id not the cached one
self.try_get_and_set_internal_id(device_id.unwrap_or(self.end_point.device_id.clone()))
.await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically there's nothing wrong with this impl. But using a clear API followed by get_and_set feels a bit like exposing internal impl details.

But, i'd have introduced a new set_and_get_internal_id API that forcefully updates the internal ID, whether one is cached or not, and used that here. I'd also use that same API from inside this try_get_and_set_internal_id when the cached internal ID is empty/None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, but named it as get_and_set_internal_id

Ok(internal_id)
}

async fn execute(
&mut self,
request_builder: HttpRequestBuilder,
device_id: Option<String>,
build_request: impl Fn(&C8yEndPoint) -> Result<HttpRequestBuilder, C8YRestError>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
build_request: impl Fn(&C8yEndPoint) -> Result<HttpRequestBuilder, C8YRestError>,
build_request: impl Fn(&C8yEndPoint) -> HttpRequestBuilder,

Wondering why that closure must return a Result and not just the HttpRequestBuilder directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah,its needed because get_software_list is called inside the closure and if it fails it will return an error.

Copy link
Contributor

@didier-wenzek didier-wenzek Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah,its needed because get_software_list is called inside the closure and if it fails it will return an error.

There is no get_software_list method nor function.

A typo here, I meant get_url_for_sw_list

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you introduced methods that build urls from an internal id and never fail, I would try what is suggested by @albinsuresh . I.e. to remove the error case by passing to the closure the internal id.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just passing the internal-id to the closure is not enough. Still the end_point is needed to get the URL for any specific HTTP call, because these URL creation functions are part of the end_point.

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request July 13, 2023 01:55 — with GitHub Actions Inactive
crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
crates/core/c8y_api/src/http_proxy.rs Outdated Show resolved Hide resolved
Ok(internal_id)
}

async fn execute(
&mut self,
request_builder: HttpRequestBuilder,
device_id: Option<String>,
build_request: impl Fn(&C8yEndPoint) -> Result<HttpRequestBuilder, C8YRestError>,
Copy link
Contributor

@didier-wenzek didier-wenzek Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah,its needed because get_software_list is called inside the closure and if it fails it will return an error.

There is no get_software_list method nor function.

A typo here, I meant get_url_for_sw_list

Copy link
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API for C8yEndPoint has now a nice shape. This should lead to further simplifications on C8YHttpProxyActor.

device_id: Option<&str>,
) -> Result<String, C8YRestError> {
let url_get_id = self.end_point.get_url_for_get_id(device_id);
let request_builder = build_request(&self.end_point)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As already said, I would not use a closure to build the request to get the internal id of a device. I would simply use the url returned by get_url_for_internal_id() and not call try_request_with_fresh_token(). Sharing the later for regular requests and for the very specific request to get an internal id makes the code more complex than necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see what has been updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the closure and used the URL returned by the get_url_for_internal_id().

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request July 19, 2023 06:58 — with GitHub Actions Inactive
Comment on lines 128 to 133
pub fn get_device_type(&self, device_id: Option<String>) -> DeviceType {
match device_id {
Some(device_id) => DeviceType::ChildDevice(device_id),
None => DeviceType::MainDevice,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function just highlights a problem that we were trying to avoid: using magics like None to represent main device. I'd have just created those enum variants directly from the callers, which would have improved the code readability from there as well, as it would be clear if a certain function is called for the MainDevice or a ChildDevice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not do that because the APIs have to be changed. I wanted to keep the http_proxy APIs as it is and do the mapping here. From the API pov, it's calling either for the main device or for the child device.

Other way would be we must change the APIs to get the device-id always, irrespective of its for a child or the main device. For example for the send_software_list_http()`` API, this must always take a device-id`. That way things will be clearer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point we need to change those APIs to complete the cleanup what we've started here. The problem is that some of those APIs still accept request structs like UploadConfigFile, UploadLogBinary etc that has an Option field for the device-id which can be None for the main device. We should use the new DeviceType enum to those structs as well, so that we use it across the entire call stack and avoid this None magic completely.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PradeepKiruvale I encourage you to follow the direction given by @albinsuresh

extras: c8y_event.extras.clone(),
}
};
let device_type = self.end_point.get_device_type(None);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The device_type will always be the main device? As you're passing None here. Why? What if an event has to be created for a child device?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this function create_event is only used by the main device. If a child device wants to use this function then it has to pass the device id, then the DeviceType can be derived out of this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't sound right. Even if a child device sends an event with extra fragments, it has to go through this same API call. So, we're missing something here.

.json(&software_list))
};

let http_result = self.execute(DeviceType::MainDevice, build_request).await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also, hardcoded to MainDevice. Something looks wrong here. I understand that we don't support software list upload for child devices ATM. But this generic API assuming the same seems wrong. This API should be able to send software list for any device.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats right. Since the Software list is supported only by the main device, I have hardcoded here. When the software list feature is supported by the child device, then one has to pass the device ID to send_software_list_http(), and based on that URL can be created.

Comment on lines 435 to 434
// Get and set child device internal id
if !device_type.eq(&DeviceType::MainDevice) {
self.get_and_set_internal_id(device_type.clone()).await?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a first look, it wasn't obvious to me as to why we're doing this here. But later understood.

@@ -102,6 +114,14 @@ impl C8yEndPoint {
}
false
}

pub fn get_device_id(&self, device_id: String) -> String {
if device_id.eq("main") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about that special treatment for the String literal main here. Was this really necessary? With the APIs explicitly requiring the device id to be passed, I would have expected all users to pass the actual device id itself, and not the magic key "main".

If we needed to retrieve the internal id of the main device somewhere, I'd have added a get_main_device_id function instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored

@@ -90,7 +90,7 @@ impl ConfigUploadManager {
self.upload_config_file(
Path::new(config_file_path.as_str()),
&config_upload_request.config_type,
None,
"main".into(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be passing the actual device-id here rather than this special key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +131 to +132
.end_point
.get_internal_id(self.end_point.device_id.clone())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.end_point
.get_internal_id(self.end_point.device_id.clone())
.end_point
.get_main_device_internal_id()

Such a function would be better.

.is_err()
{
if let Err(error) = self
.get_and_set_internal_id(self.end_point.device_id.clone())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.get_and_set_internal_id(self.end_point.device_id.clone())
.get_and_set_main_device_internal_id()

} else {
return Err(C8YRestError::CustomError("JWT token not available".into()));
};
let device_id = self.end_point.get_device_id(device_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks really weird. Deriving the device_id from the device_id. I'm assuming that this is happening because of that special handling of "main" literal. If possible, I'd avoid passing that special char around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored

@@ -61,12 +61,12 @@ impl C8YHttpProxy {
&mut self,
log_type: &str,
log_content: &str,
child_device_id: Option<String>,
source: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
source: String,
device_id: String,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -79,12 +79,12 @@ impl C8YHttpProxy {
&mut self,
config_path: &Path,
config_type: &str,
child_device_id: Option<String>,
source: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
source: String,
device_id: String,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -47,14 +47,14 @@ pub struct GetJwtToken;
pub struct UploadLogBinary {
pub log_type: String,
pub log_content: String,
pub child_device_id: Option<String>,
pub source: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub source: String,
pub device_id: String,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

#[derive(Debug, PartialEq, Eq)]
pub struct UploadConfigFile {
pub config_path: PathBuf,
pub config_type: String,
pub child_device_id: Option<String>,
pub source: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub source: String,
pub device_id: String,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -117,6 +118,8 @@ impl From<&SoftwareListResponse> for C8yUpdateSoftwareListResponse {

Self {
c8y_software_list: Some(new_list),
// Must derive the source from the topic
source: "main".into(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This source field should not have been implicit like this. It should have been injected/derived from outside. When we want to extend software list support for child devices, this will have to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored, now it gets the device id from the caller.

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request July 25, 2023 16:52 — with GitHub Actions Inactive
C8YRestRequest::C8yUpdateSoftwareListResponse(request) => self
.send_software_list_http(request.source.clone(), request)
C8YRestRequest::SoftwareListResponse(request) => self
.send_software_list_http(request.device_id.clone(), request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.send_software_list_http(request.device_id.clone(), request)
.send_software_list_http(request)

When the request instance already has the device_id in it, why extract it and pass it separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

.await
.map(|response| response.into()),

C8YRestRequest::UploadLogBinary(request) => self
.upload_log_binary(request.source.clone(), request)
.upload_log_binary(request.device_id.clone(), request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.upload_log_binary(request.device_id.clone(), request)
.upload_log_binary(request)

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

.await
.map(|response| response.into()),
C8YRestRequest::UploadConfigFile(request) => self
.upload_config_file(request.source.clone(), request)
.upload_config_file(request.device_id.clone(), request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.upload_config_file(request.device_id.clone(), request)
.upload_config_file(request)

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

…e to expired JWT token or internal id.

Signed-off-by: Pradeep Kumar K J <pkj@softwareag.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants