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

send measurement to main/child device services #2335

Merged
merged 1 commit into from
Oct 23, 2023

Conversation

PradeepKiruvale
Copy link
Contributor

@PradeepKiruvale PradeepKiruvale commented Oct 11, 2023

Proposed changes

Add a feature to push measurements to the child and main device services using the new te topic.

  • Added tests to verify the M/E/A to child/main device services
  • Added missing event test to the child device
  • Added tests to verify the M/E/A to nested child device/services

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

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

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request October 11, 2023 12:50 — with GitHub Actions Inactive
@codecov
Copy link

codecov bot commented Oct 11, 2023

Codecov Report

Merging #2335 (aee467a) into main (57ae569) will increase coverage by 0.3%.
Report is 1 commits behind head on main.
The diff coverage is 90.7%.

Additional details and impacted files
Files Coverage Δ
crates/extensions/c8y_mapper_ext/src/serializer.rs 81.2% <100.0%> (ø)
crates/extensions/c8y_mapper_ext/src/converter.rs 81.2% <92.6%> (+1.6%) ⬆️
crates/extensions/c8y_mapper_ext/src/tests.rs 92.0% <89.9%> (-0.6%) ⬇️

... and 8 files with indirect coverage changes

let out_second_messages = converter.convert(&in_message).await;
assert_eq!(out_second_messages, vec![expected_c8y_json_message]);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add some tests for events and alarms of services as well? They were working magically already, but we never had test coverage. The existing child device tests for the same could be copied and modified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added in tests.rs

);
let expected_c8y_json_message = Message::new(
&Topic::new_unchecked("c8y/measurement/measurements/create"),
r#"{"externalSource":{"externalId":"test-device:device:child1:service:app1","type":"c8y_Serial"},"temp":{"temp":{"value":1.0}},"time":"2021-11-16T17:45:40.571760714+01:00","type":"m_type"}"#,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd strongly recommend the json! macro to wrap such complex JSONs. I'd use it even for simpler ones with more than a couple of keys. I hope the macro doesn't change the order of keys so that your assertions work seamlessly, without needing assert_json_matches!

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, using json! and to_string used, so don't need to use assert_json_matches.


let expected_c8y_json_message = Message::new(
&Topic::new_unchecked("c8y/measurement/measurements/create"),
r#"{"externalSource":{"externalId":"test-device:device:main:service:appm","type":"c8y_Serial"},"temp":{"temp":{"value":1.0}},"time":"2021-11-16T17:45:40.571760714+01:00","type":"m_type"}"#,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest to use json!.

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

// "externalSource" to tell c8Y identity API to use child-device
if entity.r#type == EntityType::ChildDevice || entity.r#type == EntityType::Service {
let entity_id = &entity.external_id;
// In case the measurement is addressed to a child-device or for the service use fragment
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
// In case the measurement is addressed to a child-device or for the service use fragment
// In case the measurement is addressed to a child-device or a service, use fragment

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

Cumulocity.Device Should Exist ${DEVICE_SN}:device:${CHILD_SN}:service:app6
Execute Command tedge mqtt pub te/device/${CHILD_SN}/service/app6/e/event_002 '{"text": "test event"}'
Device Should Have Event/s expected_text=test event type=event_002 minimum=1 maximum=1

Copy link
Contributor

Choose a reason for hiding this comment

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

You may want to add similar tests for services on nested child devices as well. The "unregistered nested child service" case is not even needed as nested child devices must have explicit registration.

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 will do it as part of another PR as there are unit tests are also missing.

Copy link
Contributor

Choose a reason for hiding this comment

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

No we should include the tests here as well so that we are sure the code supports it before we merge it.

Copy link
Contributor Author

@PradeepKiruvale PradeepKiruvale Oct 13, 2023

Choose a reason for hiding this comment

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

Since that is not in the scope of this task, I do not want to bloat this PR. The nested child devices are a completely different thing, I would do it as part of another PR because this needs Unit tests and integration tests.

One question: can nested child devices also have the services? they need telemetry support as well right?

Copy link
Contributor

Choose a reason for hiding this comment

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

No I would disagree, it is very much in the task as a child device also reference to any child device regardless if it is an immediate child or a nested child.

And generally we should get in the habit of ensuring the code does what it is meant to before it gets merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

One question: can nested child devices also have the services? they need telemetry support as well right?

Yes, services are supported on any type of child device (immediate or nested)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, thanks for the clarification. I will add the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, thanks for the clarification. I will update the tests.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 11, 2023

Robot Results

✅ Passed ❌ Failed ⏭️ Skipped Total Pass % ⏱️ Duration
339 0 3 339 100 55m34.419s

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.

Happy to approve after a little bit of cleanup.

Comment on lines 1819 to 1795
let expected_smart_rest_message_child = Message::new(
&Topic::new_unchecked("c8y/s/us"),
"101,test-device:device:child1,child1,thin-edge.io-child",
);
let expected_smart_rest_message_service = Message::new(
&Topic::new_unchecked("c8y/s/us/test-device:device:child1"),
"102,test-device:device:child1:service:app1,systemd,app1,up",
);
let expected_c8y_json_message = Message::new(
&Topic::new_unchecked("c8y/measurement/measurements/create"),
json!({
"externalSource":{
"externalId":"test-device:device:child1:service:app1",
"type":"c8y_Serial"
},
"temp":{"temp":{"value":1.0}},
"time":"2021-11-16T17:45:40.571760714+01:00",
"type":"m_type"})
.to_string(),
);

// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter
.convert(&in_message)
.await
.into_iter()
.filter(|m| m.topic.name.starts_with("c8y"))
.collect();
assert_eq!(
out_first_messages,
vec![
expected_smart_rest_message_child,
expected_smart_rest_message_service,
expected_c8y_json_message.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
let expected_smart_rest_message_child = Message::new(
&Topic::new_unchecked("c8y/s/us"),
"101,test-device:device:child1,child1,thin-edge.io-child",
);
let expected_smart_rest_message_service = Message::new(
&Topic::new_unchecked("c8y/s/us/test-device:device:child1"),
"102,test-device:device:child1:service:app1,systemd,app1,up",
);
let expected_c8y_json_message = Message::new(
&Topic::new_unchecked("c8y/measurement/measurements/create"),
json!({
"externalSource":{
"externalId":"test-device:device:child1:service:app1",
"type":"c8y_Serial"
},
"temp":{"temp":{"value":1.0}},
"time":"2021-11-16T17:45:40.571760714+01:00",
"type":"m_type"})
.to_string(),
);
// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter
.convert(&in_message)
.await
.into_iter()
.filter(|m| m.topic.name.starts_with("c8y"))
.collect();
assert_eq!(
out_first_messages,
vec![
expected_smart_rest_message_child,
expected_smart_rest_message_service,
expected_c8y_json_message.clone()
]
);
let out_first_messages: Vec<_> = converter
.convert(&in_message)
.await;
assert_messages_matching(
out_first_messages,
[
(
"c8y/s/us",
"101,test-device:device:child1,child1,thin-edge.io-child".into()
),
(
""c8y/s/us/test-device:device:child1"",
"102,test-device:device:child1:service:app1,systemd,app1,up".into()
),
(
""c8y/measurement/measurements/create"",
json!({
"externalSource":{
"externalId":"test-device:device:child1:service:app1",
"type":"c8y_Serial"
},
"temp":{"temp":{"value":1.0}},
"time":"2021-11-16T17:45:40.571760714+01:00",
"type":"m_type"
}).into()
),
]
)

You could reduce a lot a of clutter using the new test helper APIs like that. I know that this API wasn't available while you were writing these tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That can be done in the second phase as a followup task, as there are a lot of tests need to be ported.

.convert(&in_message)
.await
.into_iter()
.filter(|m| m.topic.name.starts_with("c8y"))
Copy link
Contributor

Choose a reason for hiding this comment

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

I've seen this filter in many other tests as well. I wonder why we only care about the messages pushed to c8y and not others. Why not assert the other messages like the auto-registration messages sent to the te/ topics as well, if there is one? That's also part of the convert API contract that we're testing. Skipping them might lead to these tests not catching some unexpected side-effects in future. So, I'll advice against that filter. At least remove them from the new ones that you've added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If not filtered all the messages will get accumulated. And the assert will fail. For this particular test, the purpose of the tests is to assert only the output messages. So, I feel filtering is OK.

Copy link
Contributor

@albinsuresh albinsuresh Oct 19, 2023

Choose a reason for hiding this comment

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

the purpose of the tests is to assert only the output messages

Yes, but all the converted messages. We could skip all messages before or after the conversion, if not relevant for the test, but at least all the converted messages should have been asserted. But here, with this filter we're only asserting the messages sent to c8y, skipping any messges sent to te/ (typically auto-registration messages) or any other topics during the conversion. So, the validation is not complete if we don't assert all the converted messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I have updated only to the tests.

@@ -1686,6 +1686,224 @@ mod tests {
assert_eq!(out_second_messages, vec![expected_c8y_json_message]);
}

#[tokio::test]
async fn convert_measurement_with_nested_child_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 convert_measurement_with_nested_child_id() {
async fn convert_measurement_with_nested_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.

fixed

.await
.unwrap();

mqtt.skip(2).await;
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
mqtt.skip(2).await;
assert_received_contains_str(
&mut mqtt,
[(
"expected/topic",
"",
)],
)

I'd advise against skips like these and do loose asserts here instead, to be really sure that we're skipping what we think we're skipping. Sometimes I've encountered errors with blinds skips like this were messages were sent to tedge/errors indicating an actual issue, but we just skipped it. Since string.contains("") will always pass, we're essentially just validating the topic.

Copy link
Contributor Author

@PradeepKiruvale PradeepKiruvale Oct 19, 2023

Choose a reason for hiding this comment

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

But the order of messages is fixed for any given operation. So, I feel that skipping is OK here instead of just asserting the topics. If we do not skip, the assert happens on the wrong topic and tests will fail. The assert_received_contains_str has to be enhanced to wait for the right message to assert. But this will be kind of wait forever is the worst case right?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we do not skip, the assert happens on the wrong topic and tests will fail

This is exactly what we're trying to prevent. When you're skipping 2 messages here, you're not skipping any two messages. You're specifically skipping 2 messages that are probably not relevant for the test. But, still it is important to be sure that those 2 messages are exactly what you expected. Here, for example, you expect to skip the first 2 registration messages. What if one of them failed and sent an error message to tedge/errors instead? It'd fail in the next assertion but with some really weird errors than what you expected. So, that's why I'm suggesting a loose assertion on ("c8y/s/us", "101") topic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, updated the tests

.await
.unwrap();

mqtt.skip(2).await;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above for all the skips in the rest of the tests.

)
.await;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Most of these newly added tests could have been written at the converter level itself, with far less clutter and overhead of the actors, with the help of the new assert_messages_matching test helper. Not asking you to port them now, as it's already written. More of an FYI, for future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, then there were no assert_messages_matching APIs, it was tough to assert because of the order issues in the payload. Maybe we can do it as a part of a follow-up task. There are a lot of tests that can be ported.

Device Should Have Event/s expected_text=test event type=event_002 minimum=1 maximum=1

# Nested child devices
Nested child devices support sending simple measurements
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
Nested child devices support sending simple measurements
Nested child devices support sending measurement

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

Cumulocity.Device Should Exist ${CHILD_SN}
Cumulocity.Should Have Services min_count=1 max_count=1 name=app1

Cumulocity.Device Should Exist ${DEVICE_SN}:device:${CHILD_SN}:service:app1
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
Cumulocity.Device Should Exist ${DEVICE_SN}:device:${CHILD_SN}:service:app1
Cumulocity.Service Should Exist ${DEVICE_SN}:device:${CHILD_SN}:service:app1

It really should have been a new API like that, or even Cumulocity.Managed Object Should Exist (which is what the API actually does). Not under the scope of this PR though.

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request October 19, 2023 01:55 — with GitHub Actions Inactive
@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request October 20, 2023 02:01 — with GitHub Actions Inactive
);

// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter.convert(&in_message).await.into_iter().collect();
Copy link
Contributor

@albinsuresh albinsuresh Oct 20, 2023

Choose a reason for hiding this comment

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

Suggested change
let out_first_messages: Vec<_> = converter.convert(&in_message).await.into_iter().collect();
let out_first_messages: Vec<_> = converter.convert(&in_message).await;

Why iterate and collect when the output of convert is already a vector? The old code was doing this as they wanted to collect the filtered c8y messages. But, that's not the case anymore.

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, fixed

Comment on lines 1734 to 1736
// Test the second output messages doesn't contain SmartREST child device creation.
let out_second_messages = converter.convert(&in_message).await;
assert_eq!(out_second_messages, vec![expected_c8y_json_message]);
Copy link
Contributor

Choose a reason for hiding this comment

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

The first convert also did not have the SmartREST child creation message, as the nested child device does not support auto-registeration and had to be explicitly registered. So, this assert can be removed.

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

);

// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter.convert(&in_message).await.into_iter().collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary iter and collect.

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 1795 to 1797
// Test the second output messages doesn't contain SmartREST child device creation.
let out_second_messages = converter.convert(&in_message).await;
assert_eq!(out_second_messages, vec![expected_c8y_json_message]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This assertion is also pointless for the same reason mentioned above, unless this test was relying on auto-registration for the service on nested child device. Auto-registration is not supported for nested child devices, but it is supported for services on any child device, if they follow the default topic scheme.

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

);

// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter.convert(&in_message).await.into_iter().collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary iter and collect.

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

);

// Test the first output messages contains SmartREST and C8Y JSON.
let out_first_messages: Vec<_> = converter.convert(&in_message).await.into_iter().collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary iter and collect.

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

@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request October 20, 2023 14:12 — with GitHub Actions Inactive
@PradeepKiruvale PradeepKiruvale temporarily deployed to Test Pull Request October 20, 2023 15:59 — with GitHub Actions Inactive
@didier-wenzek didier-wenzek merged commit d3ed45f into thin-edge:main Oct 23, 2023
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.

4 participants