diff --git a/src/cpp/rtps/builtin/liveliness/WLP.cpp b/src/cpp/rtps/builtin/liveliness/WLP.cpp index 987702046a4..d10a508010e 100644 --- a/src/cpp/rtps/builtin/liveliness/WLP.cpp +++ b/src/cpp/rtps/builtin/liveliness/WLP.cpp @@ -860,10 +860,11 @@ bool WLP::remove_local_reader( bool WLP::automatic_liveliness_assertion() { - std::lock_guard guard(*mp_builtinProtocols->mp_PDP->getMutex()); + std::unique_lock lock(*mp_builtinProtocols->mp_PDP->getMutex()); if (0 < automatic_writers_.size()) { + lock.unlock(); return send_liveliness_message(automatic_instance_handle_); } diff --git a/src/cpp/rtps/reader/StatefulReader.cpp b/src/cpp/rtps/reader/StatefulReader.cpp index ec3ea29c6d3..03ca9f511df 100644 --- a/src/cpp/rtps/reader/StatefulReader.cpp +++ b/src/cpp/rtps/reader/StatefulReader.cpp @@ -354,40 +354,11 @@ bool StatefulReader::matched_writer_remove( const GUID_t& writer_guid, bool removed_by_lease) { - - if (is_alive_ && liveliness_lease_duration_ < c_TimeInfinite) - { - auto wlp = this->mp_RTPSParticipant->wlp(); - if ( wlp != nullptr) - { - LivelinessData::WriterStatus writer_liveliness_status; - wlp->sub_liveliness_manager_->remove_writer( - writer_guid, - liveliness_kind_, - liveliness_lease_duration_, - writer_liveliness_status); - - if (writer_liveliness_status == LivelinessData::WriterStatus::ALIVE) - { - wlp->update_liveliness_changed_status(writer_guid, this, -1, 0); - } - else if (writer_liveliness_status == LivelinessData::WriterStatus::NOT_ALIVE) - { - wlp->update_liveliness_changed_status(writer_guid, this, 0, -1); - } - - } - else - { - EPROSIMA_LOG_ERROR(RTPS_LIVELINESS, - "Finite liveliness lease duration but WLP not enabled, cannot remove writer"); - } - } - - std::unique_lock lock(mp_mutex); WriterProxy* wproxy = nullptr; if (is_alive_) { + std::unique_lock lock(mp_mutex); + //Remove cachechanges belonging to the unmatched writer mp_history->writer_unmatched(writer_guid, get_last_notified(writer_guid)); @@ -439,7 +410,36 @@ bool StatefulReader::matched_writer_remove( } } - return (wproxy != nullptr); + bool ret_val = (wproxy != nullptr); + if (ret_val && liveliness_lease_duration_ < c_TimeInfinite) + { + auto wlp = this->mp_RTPSParticipant->wlp(); + if ( wlp != nullptr) + { + LivelinessData::WriterStatus writer_liveliness_status; + wlp->sub_liveliness_manager_->remove_writer( + writer_guid, + liveliness_kind_, + liveliness_lease_duration_, + writer_liveliness_status); + + if (writer_liveliness_status == LivelinessData::WriterStatus::ALIVE) + { + wlp->update_liveliness_changed_status(writer_guid, this, -1, 0); + } + else if (writer_liveliness_status == LivelinessData::WriterStatus::NOT_ALIVE) + { + wlp->update_liveliness_changed_status(writer_guid, this, 0, -1); + } + } + else + { + EPROSIMA_LOG_ERROR(RTPS_LIVELINESS, + "Finite liveliness lease duration but WLP not enabled, cannot remove writer"); + } + } + + return ret_val; } bool StatefulReader::matched_writer_is_matched( diff --git a/src/cpp/rtps/reader/StatelessReader.cpp b/src/cpp/rtps/reader/StatelessReader.cpp index 440b8b78b0e..a6f798c7fcc 100644 --- a/src/cpp/rtps/reader/StatelessReader.cpp +++ b/src/cpp/rtps/reader/StatelessReader.cpp @@ -204,33 +204,8 @@ bool StatelessReader::matched_writer_remove( const GUID_t& writer_guid, bool removed_by_lease) { - if (liveliness_lease_duration_ < c_TimeInfinite) - { - auto wlp = mp_RTPSParticipant->wlp(); - if ( wlp != nullptr) - { - LivelinessData::WriterStatus writer_liveliness_status; - wlp->sub_liveliness_manager_->remove_writer( - writer_guid, - liveliness_kind_, - liveliness_lease_duration_, - writer_liveliness_status); + bool ret_val = false; - if (writer_liveliness_status == LivelinessData::WriterStatus::ALIVE) - { - wlp->update_liveliness_changed_status(writer_guid, this, -1, 0); - } - else if (writer_liveliness_status == LivelinessData::WriterStatus::NOT_ALIVE) - { - wlp->update_liveliness_changed_status(writer_guid, this, 0, -1); - } - } - else - { - EPROSIMA_LOG_ERROR(RTPS_LIVELINESS, - "Finite liveliness lease duration but WLP not enabled, cannot remove writer"); - } - } { std::unique_lock guard(mp_mutex); @@ -260,11 +235,41 @@ bool StatelessReader::matched_writer_remove( guard.unlock(); listener->on_writer_discovery(this, WriterDiscoveryInfo::REMOVED_WRITER, writer_guid, nullptr); } - return true; + ret_val = true; + break; } } } - return false; + + if (ret_val && liveliness_lease_duration_ < c_TimeInfinite) + { + auto wlp = mp_RTPSParticipant->wlp(); + if ( wlp != nullptr) + { + LivelinessData::WriterStatus writer_liveliness_status; + wlp->sub_liveliness_manager_->remove_writer( + writer_guid, + liveliness_kind_, + liveliness_lease_duration_, + writer_liveliness_status); + + if (writer_liveliness_status == LivelinessData::WriterStatus::ALIVE) + { + wlp->update_liveliness_changed_status(writer_guid, this, -1, 0); + } + else if (writer_liveliness_status == LivelinessData::WriterStatus::NOT_ALIVE) + { + wlp->update_liveliness_changed_status(writer_guid, this, 0, -1); + } + } + else + { + EPROSIMA_LOG_ERROR(RTPS_LIVELINESS, + "Finite liveliness lease duration but WLP not enabled, cannot remove writer"); + } + } + + return ret_val; } bool StatelessReader::matched_writer_is_matched( diff --git a/test/blackbox/common/BlackboxTestsLivelinessQos.cpp b/test/blackbox/common/BlackboxTestsLivelinessQos.cpp index 14aab586e0b..4d17d8b5f7a 100644 --- a/test/blackbox/common/BlackboxTestsLivelinessQos.cpp +++ b/test/blackbox/common/BlackboxTestsLivelinessQos.cpp @@ -14,6 +14,7 @@ #include "BlackboxTests.hpp" +#include #include #include "PubSubReader.hpp" @@ -2039,6 +2040,175 @@ TEST(LivelinessTests, correct_liveliness_state_one_writer_multiple_readers) ASSERT_EQ(reader.sub_wait_liveliness_lost_for(2, std::chrono::seconds(4)), 2u); } +/** + * This is a regression test for redmine issue #21189. + * + * The test ensures that liveliness changed status is not affected by writers on a topic different from + * the one of the reader. + * + * The test creates two readers and two writers, each reader and writer pair on a different topic. + * Writing a sample on one writer should not affect the liveliness changed status of the other reader. + * Destroying the writer should not affect the liveliness changed status of the other reader. + */ +static void test_liveliness_qos_independent_topics( + const std::string& topic_name, + eprosima::fastdds::dds::ReliabilityQosPolicyKind reliability_kind) +{ + const auto lease_dutation_time = std::chrono::seconds(1); + const eprosima::fastrtps::Duration_t lease_duration(1, 0); + const eprosima::fastrtps::Duration_t announcement_period(0, 250000000); + + PubSubReader reader1(topic_name + "1"); + PubSubReader reader2(topic_name + "2"); + + PubSubWriter writer1(topic_name + "1"); + PubSubWriter writer2(topic_name + "2"); + + // Configure and start the readers + reader1.liveliness_kind(eprosima::fastdds::dds::AUTOMATIC_LIVELINESS_QOS) + .liveliness_lease_duration(lease_duration) + .reliability(reliability_kind) + .init(); + reader2.liveliness_kind(eprosima::fastdds::dds::AUTOMATIC_LIVELINESS_QOS) + .liveliness_lease_duration(lease_duration) + .reliability(reliability_kind) + .init(); + + // Configure and start the writers + writer1.liveliness_kind(eprosima::fastdds::dds::AUTOMATIC_LIVELINESS_QOS) + .liveliness_lease_duration(lease_duration) + .liveliness_announcement_period(announcement_period) + .reliability(reliability_kind) + .init(); + writer2.liveliness_kind(eprosima::fastdds::dds::AUTOMATIC_LIVELINESS_QOS) + .liveliness_lease_duration(lease_duration) + .liveliness_announcement_period(announcement_period) + .reliability(reliability_kind) + .init(); + + // Wait for discovery + reader1.wait_discovery(); + reader2.wait_discovery(); + writer1.wait_discovery(); + writer2.wait_discovery(); + + HelloWorldPubSubType::type data; + + // Write a sample on writer1 and wait for reader1 to assert writer1's liveliness + writer1.send_sample(data); + reader1.wait_liveliness_recovered(); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 0); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + // Write a sample on writer2 and wait for reader2 to assert writer2's liveliness + writer2.send_sample(data); + reader2.wait_liveliness_recovered(); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + // Destroy writer2 and wait twice the lease duration time + writer2.destroy(); + std::this_thread::sleep_for(lease_dutation_time * 2); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 0); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + // Start writer2 again and wait for reader2 to assert writer2's liveliness + writer2.init(); + reader2.wait_discovery(); + writer2.send_sample(data); + reader2.wait_liveliness_recovered(2); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + // Destroy writer1 and wait twice the lease duration time + writer1.destroy(); + std::this_thread::sleep_for(lease_dutation_time * 2); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 0); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 1); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + // Destroy writer2 and wait twice the lease duration time + writer2.destroy(); + std::this_thread::sleep_for(lease_dutation_time * 2); + + // Check liveliness changed status on both readers + { + auto liveliness = reader1.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 0); + EXPECT_EQ(liveliness.not_alive_count, 0); + } + + { + auto liveliness = reader2.liveliness_changed_status(); + EXPECT_EQ(liveliness.alive_count, 0); + EXPECT_EQ(liveliness.not_alive_count, 0); + } +} + +TEST_P(LivelinessQos, IndependentTopics_reliable) +{ + test_liveliness_qos_independent_topics(TEST_TOPIC_NAME, eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS); +} + +TEST_P(LivelinessQos, IndependentTopics_besteffort) +{ + test_liveliness_qos_independent_topics(TEST_TOPIC_NAME, eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS); +} + #ifdef INSTANTIATE_TEST_SUITE_P #define GTEST_INSTANTIATE_TEST_MACRO(x, y, z, w) INSTANTIATE_TEST_SUITE_P(x, y, z, w) #else diff --git a/test/blackbox/common/DDSBlackboxTestsROS2.cpp b/test/blackbox/common/DDSBlackboxTestsROS2.cpp new file mode 100644 index 00000000000..43ea22f5032 --- /dev/null +++ b/test/blackbox/common/DDSBlackboxTestsROS2.cpp @@ -0,0 +1,171 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./ros2/Context.hpp" +#include "./ros2/DataReaderHolder.hpp" +#include "./ros2/DataWriterHolder.hpp" +#include "./ros2/Node.hpp" +#include "./ros2/PublicationNode.hpp" +#include "./ros2/SubscriptionNode.hpp" +#include "./ros2/TopicHolder.hpp" + +#include "BlackboxTests.hpp" +#include "../types/HelloWorldPubSubTypes.h" + +namespace eprosima { +namespace fastdds { +namespace dds { + +namespace ros2 = eprosima::testing::ros2; + +/** + * This is a regression test for redmine issue #21189. + * + * It mimicks the behavior of the following ROS2 system test: + * https://github.com/ros2/system_tests/blob/rolling/test_quality_of_service/test/test_liveliness.cpp + */ +TEST(DDS_ROS2, test_automatic_liveliness_changed) +{ + using namespace eprosima::fastrtps; + + // Force intraprocess + LibrarySettingsAttributes old_library_settings; + old_library_settings = xmlparser::XMLProfileManager::library_settings(); + + { + LibrarySettingsAttributes library_settings; + library_settings.intraprocess_delivery = IntraprocessDeliveryType::INTRAPROCESS_FULL; + xmlparser::XMLProfileManager::library_settings(library_settings); + } + + { + auto context = std::make_shared(); + + auto topic_name = TEST_TOPIC_NAME; + const Duration_t liveliness_duration = { 1, 0 }; + const Duration_t liveliness_announcement_period = { 0, 333333333 }; + LivelinessQosPolicy liveliness; + liveliness.kind = LivelinessQosPolicyKind::AUTOMATIC_LIVELINESS_QOS; + liveliness.lease_duration = liveliness_duration; + liveliness.announcement_period = liveliness_announcement_period; + ReliabilityQosPolicy reliability; + reliability.kind = RELIABLE_RELIABILITY_QOS; + + TypeSupport type_support(new HelloWorldPubSubType()); + auto pub = std::make_shared(context, topic_name + "/pub", topic_name, type_support); + auto sub = std::make_shared(context, topic_name + "/sub", topic_name, type_support); + + // Configure the subscription node with a listener that will check the liveliness changed status. + int total_number_of_liveliness_events = 0; + auto sub_listener = std::make_shared(); + sub_listener->liveliness_callback = [&total_number_of_liveliness_events]( + const LivelinessChangedStatus& event) -> void + { + total_number_of_liveliness_events++; + + // strict checking for expected events + if (total_number_of_liveliness_events == 1) + { + // publisher came alive + ASSERT_EQ(1, event.alive_count); + ASSERT_EQ(0, event.not_alive_count); + ASSERT_EQ(1, event.alive_count_change); + ASSERT_EQ(0, event.not_alive_count_change); + } + else if (total_number_of_liveliness_events == 2) + { + // publisher died + ASSERT_EQ(0, event.alive_count); + ASSERT_EQ(0, event.not_alive_count); + ASSERT_EQ(-1, event.alive_count_change); + ASSERT_EQ(0, event.not_alive_count_change); + } + }; + sub->set_listener(sub_listener); + + DataReaderQos reader_qos = context->subscriber()->get_default_datareader_qos(); + reader_qos.liveliness(liveliness); + reader_qos.reliability(reliability); + sub->set_qos(reader_qos); + + // Start the subscription node. + sub->start(); + + DataWriterQos writer_qos = context->publisher()->get_default_datawriter_qos(); + writer_qos.liveliness(liveliness); + writer_qos.reliability(reliability); + pub->set_qos(writer_qos); + + // Start the publication node. + pub->start(); + + // Wait some time and kill the publication node. + HelloWorld hello; + hello.message("Hello, World!"); + for (uint16_t i = 0; i < 10; ++i) + { + hello.index(i); + pub->publish(&hello); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + pub->stop(); + + // Wait some time and check that the liveliness changed status was triggered. + std::this_thread::sleep_for(std::chrono::seconds(6)); + EXPECT_EQ(2, total_number_of_liveliness_events); // check expected number of liveliness events + + sub->stop(); + } + + xmlparser::XMLProfileManager::library_settings(old_library_settings); +} + +} // namespace dds +} // namespace fastdds +} // namespace eprosima diff --git a/test/blackbox/common/ros2/Context.hpp b/test/blackbox/common/ros2/Context.hpp new file mode 100644 index 00000000000..3a467efa839 --- /dev/null +++ b/test/blackbox/common/ros2/Context.hpp @@ -0,0 +1,295 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__CONTEXT_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__CONTEXT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./DataReaderHolder.hpp" +#include "./DataWriterHolder.hpp" +#include "./TopicHolder.hpp" +#include "../BlackboxTests.hpp" +#include "../../types/HelloWorldPubSubTypes.h" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +class Context +{ + using RosDiscoveryInfoPubSubType = HelloWorldPubSubType; + +public: + + Context() + { + factory_ = DomainParticipantFactory::get_shared_instance(); + + uint32_t domain_id = static_cast(GET_PID() % 230); + participant_ = factory_->create_participant(domain_id, PARTICIPANT_QOS_DEFAULT); + EXPECT_NE(nullptr, participant_); + + if (nullptr != participant_) + { + publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT); + EXPECT_NE(nullptr, publisher_); + subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT); + EXPECT_NE(nullptr, subscriber_); + } + + // Create DataWriter for ros_discovery_info topic + if (nullptr != publisher_ && nullptr != subscriber_) + { + TypeSupport type_support(new RosDiscoveryInfoPubSubType()); + std::shared_ptr topic_holder; + create_topic(topic_holder, "ros_discovery_info", "rmw_dds_common::msg::dds_::ParticipantEntitiesInfo_", + type_support); + { + DataWriterQos qos = publisher_->get_default_datawriter_qos(); + qos.reliability().kind = RELIABLE_RELIABILITY_QOS; + qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS; + qos.history().kind = KEEP_LAST_HISTORY_QOS; + qos.history().depth = 1; + create_publication(discovery_info_pub_, topic_holder, DATAWRITER_QOS_DEFAULT, nullptr, false); + } + { + DataReaderQos qos = subscriber_->get_default_datareader_qos(); + qos.reliability().kind = RELIABLE_RELIABILITY_QOS; + qos.durability().kind = TRANSIENT_LOCAL_DURABILITY_QOS; + qos.history().kind = KEEP_LAST_HISTORY_QOS; + qos.history().depth = 100; + create_subscription(discovery_info_sub_, topic_holder, DATAREADER_QOS_DEFAULT, nullptr, false); + } + } + } + + ~Context() + { + discovery_info_sub_.reset(); + discovery_info_pub_.reset(); + topics_.clear(); + + if (participant_ != nullptr) + { + participant_->delete_contained_entities(); + factory_->delete_participant(participant_); + } + } + + DomainParticipant* participant() const + { + return participant_; + } + + Publisher* publisher() const + { + return publisher_; + } + + Subscriber* subscriber() const + { + return subscriber_; + } + + void create_topic( + std::shared_ptr& topic_holder, + const std::string& topic_name, + const std::string& type_name, + const TypeSupport& type_support) + { + std::lock_guard lock(mutex_); + + topic_holder = topics_[topic_name]; + if (!topic_holder) + { + EXPECT_EQ(ReturnCode_t::RETCODE_OK, type_support.register_type(participant_, type_name)); + auto topic = participant_->create_topic("testing/" + topic_name, type_name, TOPIC_QOS_DEFAULT); + ASSERT_NE(nullptr, topic); + topic_holder = std::make_shared(participant_, topic); + topics_[topic_name] = topic_holder; + } + else + { + EXPECT_EQ(type_name, topic_holder->topic()->get_type_name()); + } + } + + void create_publication( + std::shared_ptr& publication, + const std::shared_ptr& topic_holder, + const DataWriterQos& qos, + DataWriterListener* listener, + bool notify = true) + { + std::lock_guard lock(mutex_); + + ASSERT_NE(nullptr, publisher_); + DataWriter* writer = publisher_->create_datawriter(topic_holder->topic(), qos, listener); + ASSERT_NE(nullptr, writer); + publication = std::make_shared(publisher_, writer); + if (notify) + { + notify_publication_creation(publication); + } + } + + void delete_publication( + std::shared_ptr& publication) + { + if (publication) + { + std::lock_guard lock(mutex_); + + notify_publication_deletion(publication); + publication.reset(); + } + } + + void create_subscription( + std::shared_ptr& subscription, + const std::shared_ptr& topic_holder, + const DataReaderQos& qos, + DataReaderListener* listener, + bool notify = true) + { + std::lock_guard lock(mutex_); + + ASSERT_NE(nullptr, subscriber_); + DataReader* reader = subscriber_->create_datareader(topic_holder->topic(), qos, listener); + ASSERT_NE(nullptr, reader); + subscription = std::make_shared(subscriber_, reader); + if (notify) + { + notify_subscription_creation(subscription); + } + } + + void delete_subscription( + std::shared_ptr& subscription) + { + if (subscription) + { + std::lock_guard lock(mutex_); + + notify_subscription_deletion(subscription); + subscription.reset(); + } + } + +private: + + void notify_publication_creation( + const std::shared_ptr& publication) + { + if (nullptr != discovery_info_pub_) + { + RosDiscoveryInfoPubSubType::type discovery_info; + std::stringstream ss; + ss << "Publication " << publication->writer()->guid() << " created on topic " << + publication->writer()->get_topic()->get_name(); + discovery_info.message(ss.str()); + discovery_info_pub_->writer()->write(&discovery_info); + } + } + + void notify_publication_deletion( + const std::shared_ptr& publication) + { + if (nullptr != discovery_info_pub_) + { + RosDiscoveryInfoPubSubType::type discovery_info; + std::stringstream ss; + ss << "Publication " << publication->writer()->guid() << " deleted on topic " << + publication->writer()->get_topic()->get_name(); + discovery_info.message(ss.str()); + discovery_info_pub_->writer()->write(&discovery_info); + } + } + + void notify_subscription_creation( + const std::shared_ptr& subscription) + { + if (nullptr != discovery_info_pub_) + { + RosDiscoveryInfoPubSubType::type discovery_info; + std::stringstream ss; + ss << "Subscription " << subscription->reader()->guid() << " created on topic " << + subscription->reader()->get_topicdescription()->get_name(); + discovery_info.message(ss.str()); + discovery_info_pub_->writer()->write(&discovery_info); + } + } + + void notify_subscription_deletion( + const std::shared_ptr& subscription) + { + if (nullptr != discovery_info_pub_) + { + RosDiscoveryInfoPubSubType::type discovery_info; + std::stringstream ss; + ss << "Subscription " << subscription->reader()->guid() << " deleted on topic " << + subscription->reader()->get_topicdescription()->get_name(); + discovery_info.message(ss.str()); + discovery_info_pub_->writer()->write(&discovery_info); + } + } + + std::mutex mutex_; + std::shared_ptr factory_{}; + DomainParticipant* participant_ = nullptr; + Publisher* publisher_ = nullptr; + Subscriber* subscriber_ = nullptr; + std::map> topics_{}; + std::shared_ptr discovery_info_pub_{}; + std::shared_ptr discovery_info_sub_{}; +}; + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__CONTEXT_HPP diff --git a/test/blackbox/common/ros2/DataReaderHolder.hpp b/test/blackbox/common/ros2/DataReaderHolder.hpp new file mode 100644 index 00000000000..548f8450b9f --- /dev/null +++ b/test/blackbox/common/ros2/DataReaderHolder.hpp @@ -0,0 +1,38 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAREADERHOLDER_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAREADERHOLDER_HPP + +#include + +#include +#include +#include + +#include "./GenericHolder.hpp" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +GENERIC_HOLDER_CLASS(Subscriber, DataReader, delete_datareader, reader) + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAREADERHOLDER_HPP diff --git a/test/blackbox/common/ros2/DataWriterHolder.hpp b/test/blackbox/common/ros2/DataWriterHolder.hpp new file mode 100644 index 00000000000..8499a0d36a0 --- /dev/null +++ b/test/blackbox/common/ros2/DataWriterHolder.hpp @@ -0,0 +1,38 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAWRITERHOLDER_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAWRITERHOLDER_HPP + +#include + +#include +#include +#include + +#include "./GenericHolder.hpp" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +GENERIC_HOLDER_CLASS(Publisher, DataWriter, delete_datawriter, writer) + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__DATAWRITERHOLDER_HPP diff --git a/test/blackbox/common/ros2/GenericHolder.hpp b/test/blackbox/common/ros2/GenericHolder.hpp new file mode 100644 index 00000000000..2a449e83b9d --- /dev/null +++ b/test/blackbox/common/ros2/GenericHolder.hpp @@ -0,0 +1,77 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__GENERICHOLDER_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__GENERICHOLDER_HPP + +#include + +#include + +// *INDENT-OFF* Uncrustify makes a mess with this kind of macros +/** + * @brief Generic holder class for a DDS entity. + * This macro generates a holder class for a DDS entity along with it's factory so that the entity is automatically + * released when the holder is destroyed. + * The generated class has a custom-named getter method to access the entity. + * The macro allows to specify the factory class, the entity class, the factory's method to release the entity and the + * getter method name. + * + * @param _Factory The class of the factory that created the entity (e.g. Publisher). + * @param _Entity The class of the entity (e.g., DataWriter). + * @param _Release The method of the factory to release the entity (e.g., delete_datawriter). + * @param _Getter The name of the getter method to access the entity (for instance, writer). + * + * Examples: + * GENERIC_HOLDER_CLASS(Publisher, DataWriter, delete_datawriter, writer) generates DataWriterHolder. + * GENERIC_HOLDER_CLASS(Subscriber, DataReader, delete_datareader, reader) generates DataReaderHolder. + * GENERIC_HOLDER_CLASS(DomainParticipant, Publisher, delete_publisher, publisher) generates PublisherHolder. + * GENERIC_HOLDER_CLASS(DomainParticipant, Subscriber, delete_subscriber, subscriber) generates SubscriberHolder. + * GENERIC_HOLDER_CLASS(DomainParticipant, Topic, delete_topic, topic) generates TopicHolder. + * GENERIC_HOLDER_CLASS(DomainParticipantFactory, DomainParticipant, delete_participant, participant) generates DomainParticipantHolder. + */ +#define GENERIC_HOLDER_CLASS(_Factory, _Entity, _Release, _Getter) \ +class _Entity##Holder \ +{ \ +public: \ + _Entity##Holder( \ + _Factory* factory, \ + _Entity* entity) \ + : factory_(factory) \ + , entity_(entity) \ + { \ + } \ + \ + ~_Entity##Holder() \ + { \ + if (nullptr != factory_ && nullptr != entity_) \ + { \ + EXPECT_EQ(ReturnCode_t::RETCODE_OK, factory_->_Release(entity_)); \ + } \ + } \ + \ + _Entity* _Getter() \ + { \ + return entity_; \ + } \ + \ +private: \ + \ + _Factory* factory_ = nullptr; \ + _Entity* entity_ = nullptr; \ + \ +}; +// *INDENT-ON* + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__GENERICHOLDER_HPP diff --git a/test/blackbox/common/ros2/Node.hpp b/test/blackbox/common/ros2/Node.hpp new file mode 100644 index 00000000000..4cd9d870a9e --- /dev/null +++ b/test/blackbox/common/ros2/Node.hpp @@ -0,0 +1,204 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__NODE_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__NODE_HPP + +#include +#include +#include +#include + +#include "./Context.hpp" +#include "./DataReaderHolder.hpp" +#include "./DataWriterHolder.hpp" +#include "./TopicHolder.hpp" +#include "../../types/HelloWorldPubSubTypes.h" +#include +#include + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +class Node +{ + using BuiltinPubSubType = HelloWorldPubSubType; + +public: + + Node( + const std::shared_ptr& context, + const std::string& node_name) + : context_(context) + , node_name_(node_name) + { + } + + virtual ~Node() = default; + + void start() + { + std::lock_guard _(mutex_); + + if (started_) + { + return; + } + + started_ = true; + create_builtin(); + do_start(); + } + + void stop() + { + std::lock_guard _(mutex_); + + if (!started_) + { + return; + } + + started_ = false; + do_stop(); + destroy_builtin(); + } + +protected: + + std::shared_ptr context_{}; + std::string node_name_{}; + + mutable std::mutex mutex_{}; + bool started_ = false; + + virtual void do_start() = 0; + + virtual void do_stop() = 0; + +private: + + enum class EndpointKind + { + PUB_ONLY, + SUB_ONLY, + PUB_AND_SUB + }; + + void create_builtin() + { + create_rosout_pub(); + // "rt/rosout", "rcl_interfaces::msg::dds_::Log_", KEEP_LAST(1000), RELIABLE, TRANSIENT_LOCAL, WRITER_ONLY + create_parameters_server(); + create_type_description_server(); + } + + void create_rosout_pub() + { + create_builtin_topic("rt/rosout", "rcl_interfaces::msg::dds_::Log_", + RELIABLE_RELIABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, EndpointKind::PUB_ONLY); + } + + void create_parameters_server() + { + create_service("get_parameters", "rcl_interfaces::srv::dds_::GetParameters"); + create_service("get_parameter_types", "rcl_interfaces::srv::dds_::GetParameterTypes"); + create_service("set_parameters", "rcl_interfaces::srv::dds_::SetParameters"); + create_service("set_parameters_atomically", "rcl_interfaces::srv::dds_::SetParametersAtomically"); + create_service("describe_parameters", "rcl_interfaces::srv::dds_::DescribeParameters"); + create_service("list_parameters", "rcl_interfaces::srv::dds_::ListParameters"); + create_builtin_topic("rt/parameter_events", "rcl_interfaces::msg::dds_::ParameterEvent_", + RELIABLE_RELIABILITY_QOS, VOLATILE_DURABILITY_QOS, EndpointKind::PUB_AND_SUB); + } + + void create_type_description_server() + { + create_service("get_type_description", "type_description_interfaces::srv::dds_::GetTypeDescription"); + } + + void create_service( + const std::string& service_name, + const std::string& service_type) + { + create_builtin_topic("rq/" + node_name_ + "/" + service_name + "Request", service_type + "_Request_", + RELIABLE_RELIABILITY_QOS, VOLATILE_DURABILITY_QOS, EndpointKind::SUB_ONLY); + create_builtin_topic("rr/" + node_name_ + "/" + service_name + "Reply", service_type + "_Response_", + RELIABLE_RELIABILITY_QOS, VOLATILE_DURABILITY_QOS, EndpointKind::PUB_ONLY); + } + + void create_builtin_topic( + const std::string& topic_name, + const std::string& type_name, + ReliabilityQosPolicyKind reliability, + DurabilityQosPolicyKind durability, + EndpointKind kind) + { + TypeSupport type_support(new BuiltinPubSubType()); + std::shared_ptr topic_holder; + context_->create_topic(topic_holder, topic_name, type_name, type_support); + if (kind == EndpointKind::PUB_ONLY || kind == EndpointKind::PUB_AND_SUB) + { + DataWriterQos qos = context_->publisher()->get_default_datawriter_qos(); + qos.reliability().kind = reliability; + qos.durability().kind = durability; + qos.history().kind = KEEP_LAST_HISTORY_QOS; + qos.history().depth = 1000; + std::shared_ptr writer; + context_->create_publication(writer, topic_holder, qos, nullptr, true); + builtin_writers_.push_back(writer); + } + if (kind == EndpointKind::SUB_ONLY || kind == EndpointKind::PUB_AND_SUB) + { + DataReaderQos qos = context_->subscriber()->get_default_datareader_qos(); + qos.reliability().kind = reliability; + qos.durability().kind = durability; + qos.history().kind = KEEP_LAST_HISTORY_QOS; + qos.history().depth = 1000; + std::shared_ptr reader; + context_->create_subscription(reader, topic_holder, qos, nullptr, true); + builtin_readers_.push_back(reader); + } + builtin_topics_.push_back(topic_holder); + } + + void destroy_builtin() + { + for (auto& reader : builtin_readers_) + { + context_->delete_subscription(reader); + } + builtin_readers_.clear(); + + for (auto& writer : builtin_writers_) + { + context_->delete_publication(writer); + } + builtin_writers_.clear(); + + builtin_topics_.clear(); + } + + std::vector> builtin_readers_; + std::vector> builtin_writers_; + std::vector> builtin_topics_; +}; + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__NODE_HPP diff --git a/test/blackbox/common/ros2/PublicationNode.hpp b/test/blackbox/common/ros2/PublicationNode.hpp new file mode 100644 index 00000000000..388f05bb58d --- /dev/null +++ b/test/blackbox/common/ros2/PublicationNode.hpp @@ -0,0 +1,123 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__PUBLICATIONNODE_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__PUBLICATIONNODE_HPP + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./Context.hpp" +#include "./DataWriterHolder.hpp" +#include "./Node.hpp" +#include "./TopicHolder.hpp" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +struct PublicationListener : public DataWriterListener +{ +}; + +struct PublicationNode : public Node +{ + PublicationNode( + const std::shared_ptr& context, + const std::string& node_name, + const std::string& topic_name, + const TypeSupport& type_support) + : Node(context, node_name) + , topic_name_(topic_name) + , type_support_(type_support) + , qos_(context->publisher()->get_default_datawriter_qos()) + { + } + + ~PublicationNode() override + { + stop(); + } + + void set_qos( + const DataWriterQos& qos) + { + qos_ = qos; + } + + void publish( + void* data) + { + std::lock_guard _(mutex_); + + if (!started_) + { + return; + } + + if (!publication_) + { + std::cout << "Publication not created yet" << std::endl; + return; + } + + auto writer = publication_->writer(); + ASSERT_NE(nullptr, writer); + writer->write(data); + } + +protected: + + void do_start() override + { + context_->create_topic(topic_holder_, topic_name_, type_support_.get_type_name(), type_support_); + context_->create_publication(publication_, topic_holder_, qos_, listener_.get()); + ASSERT_NE(nullptr, publication_); + } + + void do_stop() override + { + context_->delete_publication(publication_); + publication_.reset(); + topic_holder_.reset(); + } + + std::string topic_name_{}; + TypeSupport type_support_{}; + DataWriterQos qos_{}; + std::shared_ptr topic_holder_{}; + std::shared_ptr listener_ = std::make_shared(); + std::shared_ptr publication_{}; +}; + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__PUBLICATIONNODE_HPP diff --git a/test/blackbox/common/ros2/SubscriptionNode.hpp b/test/blackbox/common/ros2/SubscriptionNode.hpp new file mode 100644 index 00000000000..2943dd66e55 --- /dev/null +++ b/test/blackbox/common/ros2/SubscriptionNode.hpp @@ -0,0 +1,122 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__SUBSCRIPTIONNODE_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__SUBSCRIPTIONNODE_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./Context.hpp" +#include "./DataReaderHolder.hpp" +#include "./Node.hpp" +#include "./TopicHolder.hpp" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +struct SubscriptionListener : public DataReaderListener +{ + void on_liveliness_changed( + DataReader* reader, + const LivelinessChangedStatus& status) override + { + static_cast(reader); + + if (liveliness_callback) + { + liveliness_callback(status); + } + } + + std::function liveliness_callback; +}; + +struct SubscriptionNode : public Node +{ + SubscriptionNode( + const std::shared_ptr& context, + const std::string& node_name, + const std::string& topic_name, + const TypeSupport& type_support) + : Node(context, node_name) + , topic_name_(topic_name) + , type_support_(type_support) + , qos_(context->subscriber()->get_default_datareader_qos()) + { + } + + ~SubscriptionNode() override + { + stop(); + } + + void set_listener( + const std::shared_ptr& listener) + { + listener_ = listener; + } + + void set_qos( + const DataReaderQos& qos) + { + qos_ = qos; + } + +protected: + + void do_start() override + { + context_->create_topic(topic_holder_, topic_name_, type_support_.get_type_name(), type_support_); + context_->create_subscription(subscription_, topic_holder_, qos_, listener_.get()); + } + + void do_stop() override + { + context_->delete_subscription(subscription_); + subscription_.reset(); + topic_holder_.reset(); + } + + std::string topic_name_{}; + TypeSupport type_support_{}; + DataReaderQos qos_{}; + std::shared_ptr topic_holder_{}; + std::shared_ptr listener_ = std::make_shared(); + std::shared_ptr subscription_{}; +}; + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__SUBSCRIPTIONNODE_HPP diff --git a/test/blackbox/common/ros2/TopicHolder.hpp b/test/blackbox/common/ros2/TopicHolder.hpp new file mode 100644 index 00000000000..0be359e1c51 --- /dev/null +++ b/test/blackbox/common/ros2/TopicHolder.hpp @@ -0,0 +1,38 @@ +// Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FASTDDS_TEST_BLACKBOX_COMMON_ROS2__TOPICHOLDER_HPP +#define FASTDDS_TEST_BLACKBOX_COMMON_ROS2__TOPICHOLDER_HPP + +#include + +#include +#include +#include + +#include "./GenericHolder.hpp" + +namespace eprosima { +namespace testing { +namespace ros2 { + +using namespace eprosima::fastdds::dds; + +GENERIC_HOLDER_CLASS(DomainParticipant, Topic, delete_topic, topic) + +} // namespace ros2 +} // namespace testing +} // namespace eprosima + +#endif // FASTDDS_TEST_BLACKBOX_COMMON_ROS2__TOPICHOLDER_HPP