From 274732eb48cdce03046bed2aec5e74c5f2e1dbcb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 07:37:44 +0200 Subject: [PATCH] Add XML configuration for FlowControllerDescriptor to 2.x (#4893) (#4907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add XML configuration for FlowControllerDescriptor to 2.x (#4893) * Refs #21136: Replace const char* with string Signed-off-by: Mario Dominguez * Refs #21136: Update fastRTPS_profiles.xsd Signed-off-by: Mario Dominguez * Refs #21136: Implement flow controller descriptor list in XML related source files Signed-off-by: Mario Dominguez * Refs #21136: Update tests Signed-off-by: Mario Dominguez * Refs #21136: versions.md Signed-off-by: Mario Dominguez * Refs #21136: Linter Signed-off-by: Mario Dominguez * Refs #21136: Apply rev Signed-off-by: Mario Dominguez --------- Signed-off-by: Mario Dominguez (cherry picked from commit e6044e01172525295733737e574e69f74ff507ca) # Conflicts: # include/fastdds/rtps/flowcontrol/FlowControllerDescriptor.hpp # resources/xsd/fastRTPS_profiles.xsd # test/unittest/dds/profiles/test_xml_profiles.xml # test/unittest/dds/profiles/test_xml_profiles_for_string.xml # test/unittest/xmlparser/XMLParserTests.cpp # test/unittest/xmlparser/XMLProfileParserTests.cpp # test/unittest/xmlparser/test_xml_profile.xml # test/unittest/xmlparser/test_xml_profile_env_var.xml # versions.md * Bugfix: Revert XML Flow controller names to `const char*` (#4911) * Refs #21054: Revert to const char* for flow controller names Signed-off-by: Mario Dominguez * Refs #21054: Handle flow controller names in a XMLParser collection Signed-off-by: Mario Dominguez * Refs #21054: Apply Miguel suggestion Signed-off-by: Mario Dominguez * Refs #21054: Apply second suggestion Signed-off-by: Mario Dominguez --------- Signed-off-by: Mario Dominguez * Refs #21244: Solve conflicts and remove threadsettings from flow_controller_descriptor Signed-off-by: Mario Dominguez --------- Signed-off-by: Mario Dominguez Co-authored-by: Mario Domínguez López <116071334+Mario-DL@users.noreply.github.com> Co-authored-by: Mario Dominguez --- .../flowcontrol/FlowControllerDescriptor.hpp | 3 +- include/fastrtps/xmlparser/XMLParser.h | 21 + include/fastrtps/xmlparser/XMLParserCommon.h | 11 + resources/xsd/fastRTPS_profiles.xsd | 39 +- .../rtps/participant/RTPSParticipantImpl.cpp | 2 +- src/cpp/rtps/xmlparser/XMLElementParser.cpp | 162 +++++++ src/cpp/rtps/xmlparser/XMLParser.cpp | 18 + src/cpp/rtps/xmlparser/XMLParserCommon.cpp | 10 + src/cpp/rtps/xmlparser/XMLProfileManager.cpp | 3 + .../dds/profiles/test_xml_profiles.xml | 17 + .../profiles/test_xml_profiles_for_string.xml | 14 +- .../xmlparser/XMLElementParserTests.cpp | 151 +++++++ test/unittest/xmlparser/XMLParserTests.cpp | 5 + .../xmlparser/XMLProfileParserTests.cpp | 6 + test/unittest/xmlparser/test_xml_profile.xml | 420 ++++++++++++++++++ .../xmlparser/test_xml_profile_env_var.xml | 420 ++++++++++++++++++ test/unittest/xmlparser/test_xml_profiles.xml | 9 + .../xmlparser/test_xml_profiles_rooted.xml | 8 + .../xmlparser/wrapper/XMLParserTest.hpp | 8 + versions.md | 2 +- 20 files changed, 1319 insertions(+), 10 deletions(-) create mode 100644 test/unittest/xmlparser/test_xml_profile.xml create mode 100644 test/unittest/xmlparser/test_xml_profile_env_var.xml diff --git a/include/fastdds/rtps/flowcontrol/FlowControllerDescriptor.hpp b/include/fastdds/rtps/flowcontrol/FlowControllerDescriptor.hpp index 8e5c489eb74..45005e500d8 100644 --- a/include/fastdds/rtps/flowcontrol/FlowControllerDescriptor.hpp +++ b/include/fastdds/rtps/flowcontrol/FlowControllerDescriptor.hpp @@ -15,6 +15,7 @@ #ifndef FASTDDS_RTPS_FLOWCONTROL_FLOWCONTROLLERDESCRIPTOR_HPP #define FASTDDS_RTPS_FLOWCONTROL_FLOWCONTROLLERDESCRIPTOR_HPP + #include "FlowControllerConsts.hpp" #include "FlowControllerSchedulerPolicy.hpp" @@ -31,7 +32,7 @@ namespace rtps { struct FlowControllerDescriptor { //! Name of the flow controller. - const char* name = nullptr; + const char* name = FASTDDS_FLOW_CONTROLLER_DEFAULT; //! Scheduler policy used by the flow controller. //! diff --git a/include/fastrtps/xmlparser/XMLParser.h b/include/fastrtps/xmlparser/XMLParser.h index 5683765c1f9..79af7f05b61 100644 --- a/include/fastrtps/xmlparser/XMLParser.h +++ b/include/fastrtps/xmlparser/XMLParser.h @@ -27,6 +27,7 @@ #include #include +#include #include namespace tinyxml2 { @@ -91,6 +92,8 @@ class XMLParser public: + using FlowControllerDescriptorList = std::vector>; + /** * Load the default XML file. * @return XMLP_ret::XML_OK on success, XMLP_ret::XML_ERROR in other case. @@ -160,6 +163,14 @@ class XMLParser RTPS_DllAPI static XMLP_ret loadXMLDynamicTypes( tinyxml2::XMLElement& types); + + /** + * Clears the private static collections. + * + * @return XMLP_ret::XML_OK on success, XMLP_ret::XML_ERROR in other case. + */ + RTPS_DllAPI static XMLP_ret clear(); + protected: RTPS_DllAPI static XMLP_ret parseXML( @@ -496,6 +507,11 @@ class XMLParser rtps::ThroughputControllerDescriptor& throughputController, uint8_t ident); + RTPS_DllAPI static XMLP_ret getXMLFlowControllerDescriptorList( + tinyxml2::XMLElement* elem, + FlowControllerDescriptorList& flow_controller_descriptor_list, + uint8_t ident); + RTPS_DllAPI static XMLP_ret getXMLPortParameters( tinyxml2::XMLElement* elem, rtps::PortParameters& port, @@ -615,6 +631,11 @@ class XMLParser tinyxml2::XMLElement* elem, eprosima::fastdds::rtps::BuiltinTransports* bt, uint8_t ident); + +private: + + static std::mutex collections_mtx_; + static std::set flow_controller_descriptor_names_; }; } // namespace xmlparser diff --git a/include/fastrtps/xmlparser/XMLParserCommon.h b/include/fastrtps/xmlparser/XMLParserCommon.h index 7ffd2e9133a..8943febe5ba 100644 --- a/include/fastrtps/xmlparser/XMLParserCommon.h +++ b/include/fastrtps/xmlparser/XMLParserCommon.h @@ -114,6 +114,7 @@ extern const char* PART_ID; extern const char* IP4_TO_SEND; extern const char* IP6_TO_SEND; extern const char* THROUGHPUT_CONT; +extern const char* FLOW_CONTROLLER_DESCRIPTOR_LIST; extern const char* USER_TRANS; extern const char* USE_BUILTIN_TRANS; extern const char* BUILTIN_TRANS; @@ -257,6 +258,16 @@ extern const char* ALLOCATED_SAMPLES; extern const char* EXTRA_SAMPLES; extern const char* BYTES_PER_SECOND; extern const char* PERIOD_MILLISECS; +extern const char* FLOW_CONTROLLER_DESCRIPTOR; +extern const char* SCHEDULER; +extern const char* MAX_BYTES_PER_PERIOD; +extern const char* PERIOD_MS; +extern const char* FLOW_CONTROLLER_NAME; +extern const char* FIFO; +extern const char* HIGH_PRIORITY; +extern const char* ROUND_ROBIN; +extern const char* PRIORITY_WITH_RESERVATION; +extern const char* FLOW_CONTROLLER_NAME; extern const char* PORT_BASE; extern const char* DOMAIN_ID_GAIN; extern const char* PARTICIPANT_ID_GAIN; diff --git a/resources/xsd/fastRTPS_profiles.xsd b/resources/xsd/fastRTPS_profiles.xsd index dc36d33a52c..7b975d02eaf 100644 --- a/resources/xsd/fastRTPS_profiles.xsd +++ b/resources/xsd/fastRTPS_profiles.xsd @@ -37,10 +37,6 @@ - - - - @@ -616,6 +612,7 @@ + @@ -626,6 +623,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -736,6 +766,7 @@ + diff --git a/src/cpp/rtps/participant/RTPSParticipantImpl.cpp b/src/cpp/rtps/participant/RTPSParticipantImpl.cpp index 21dba7ca80c..471d3256d0f 100644 --- a/src/cpp/rtps/participant/RTPSParticipantImpl.cpp +++ b/src/cpp/rtps/participant/RTPSParticipantImpl.cpp @@ -679,7 +679,7 @@ bool RTPSParticipantImpl::create_writer( GUID_t guid(m_guid.guidPrefix, entId); fastdds::rtps::FlowController* flow_controller = nullptr; - const char* flow_controller_name = param.flow_controller_name; + std::string flow_controller_name = param.flow_controller_name; // Support of old flow controller style. if (param.throughputController.bytesPerPeriod != UINT32_MAX && param.throughputController.periodMillisecs != 0) diff --git a/src/cpp/rtps/xmlparser/XMLElementParser.cpp b/src/cpp/rtps/xmlparser/XMLElementParser.cpp index e58b99ec8c0..1f6b630e195 100644 --- a/src/cpp/rtps/xmlparser/XMLElementParser.cpp +++ b/src/cpp/rtps/xmlparser/XMLElementParser.cpp @@ -26,6 +26,9 @@ using namespace eprosima::fastrtps; using namespace eprosima::fastrtps::rtps; using namespace eprosima::fastrtps::xmlparser; +std::mutex XMLParser::collections_mtx_; +std::set XMLParser::flow_controller_descriptor_names_; + XMLP_ret XMLParser::getXMLParticipantAllocationAttributes( tinyxml2::XMLElement* elem, rtps::RTPSParticipantAllocationAttributes& allocation, @@ -754,6 +757,148 @@ XMLP_ret XMLParser::getXMLThroughputController( return XMLP_ret::XML_OK; } +XMLP_ret XMLParser::getXMLFlowControllerDescriptorList( + tinyxml2::XMLElement* elem, + FlowControllerDescriptorList& flow_controller_descriptor_list, + uint8_t ident) +{ + /* + + + + + + */ + + tinyxml2::XMLElement* p_aux0 = nullptr; + p_aux0 = elem->FirstChildElement(FLOW_CONTROLLER_DESCRIPTOR); + if (nullptr == p_aux0) + { + logError(XMLPARSER, "Node '" << elem->Value() << "' without content"); + return XMLP_ret::XML_ERROR; + } + + while (nullptr != p_aux0) + { + /* + + + + + + + + + + + + + + + + + */ + + tinyxml2::XMLElement* p_aux1; + bool name_defined = false; + std::set tags_present; + + auto flow_controller_descriptor = std::make_shared(); + + for (p_aux1 = p_aux0->FirstChildElement(); p_aux1 != NULL; p_aux1 = p_aux1->NextSiblingElement()) + { + const char* name = p_aux1->Name(); + + if (tags_present.count(name) != 0) + { + logError(XMLPARSER, + "Duplicated element found in 'flowControllerDescriptorType'. Name: " << name); + return XMLP_ret::XML_ERROR; + } + else + { + tags_present.emplace(name); + } + + if (strcmp(name, NAME) == 0) + { + std::lock_guard lock(collections_mtx_); + // name - stringType + const char* element = p_aux1->GetText(); + if (nullptr == element) + { + logError(XMLPARSER, "Node '" << NAME << "' without content"); + return XMLP_ret::XML_ERROR; + } + auto element_inserted = flow_controller_descriptor_names_.insert(element); + if (element_inserted.first == flow_controller_descriptor_names_.end()) + { + logError(XMLPARSER, + "Insertion error for flow controller node '" << FLOW_CONTROLLER_NAME << "'"); + return XMLP_ret::XML_ERROR; + } + flow_controller_descriptor->name = element_inserted.first->c_str(); + name_defined = true; + } + else if (strcmp(name, SCHEDULER) == 0) + { + const char* text = p_aux1->GetText(); + if (nullptr == text) + { + logError(XMLPARSER, "Node '" << SCHEDULER << "' without content"); + return XMLP_ret::XML_ERROR; + } + + // scheduler - flowControllerSchedulerPolicy + if (!get_element_enum_value(text, flow_controller_descriptor->scheduler, + FIFO, fastdds::rtps::FlowControllerSchedulerPolicy::FIFO, + HIGH_PRIORITY, fastdds::rtps::FlowControllerSchedulerPolicy::HIGH_PRIORITY, + ROUND_ROBIN, fastdds::rtps::FlowControllerSchedulerPolicy::ROUND_ROBIN, + PRIORITY_WITH_RESERVATION, + fastdds::rtps::FlowControllerSchedulerPolicy::PRIORITY_WITH_RESERVATION)) + { + logError(XMLPARSER, "Node '" << SCHEDULER << "' with bad content"); + return XMLP_ret::XML_ERROR; + } + } + else if (strcmp(name, MAX_BYTES_PER_PERIOD) == 0) + { + // max_bytes_per_period - int32Type + if (XMLP_ret::XML_OK != getXMLInt(p_aux1, &flow_controller_descriptor->max_bytes_per_period, ident)) + { + return XMLP_ret::XML_ERROR; + } + } + else if (strcmp(name, PERIOD_MS) == 0) + { + // period_ms - uint64Type + if (XMLP_ret::XML_OK != getXMLUint(p_aux1, (uint16_t*)&flow_controller_descriptor->period_ms, ident)) + { + return XMLP_ret::XML_ERROR; + } + } + else + { + logError(XMLPARSER, + "Invalid element found into 'flowControllerDescriptorType'. Name: " << name); + return XMLP_ret::XML_ERROR; + } + } + + if (!name_defined) + { + logError(XMLPARSER, "Flow Controller Descriptor requires a 'name'"); + return XMLP_ret::XML_ERROR; + } + + flow_controller_descriptor_list.push_back(flow_controller_descriptor); + p_aux0 = p_aux0->NextSiblingElement(FLOW_CONTROLLER_DESCRIPTOR); + + } + + return XMLP_ret::XML_OK; +} + XMLP_ret XMLParser::getXMLTopicAttributes( tinyxml2::XMLElement* elem, TopicAttributes& topic, @@ -2480,6 +2625,23 @@ XMLP_ret XMLParser::getXMLPublishModeQos( return XMLP_ret::XML_ERROR; } } + else if (strcmp(name, FLOW_CONTROLLER_NAME) == 0) + { + std::lock_guard lock(collections_mtx_); + const char* element = p_aux0->GetText(); + if (nullptr == element) + { + logError(XMLPARSER, "Node '" << FLOW_CONTROLLER_NAME << "' without content"); + return XMLP_ret::XML_ERROR; + } + auto element_inserted = flow_controller_descriptor_names_.insert(element); + if (element_inserted.first == flow_controller_descriptor_names_.end()) + { + logError(XMLPARSER, "Insertion error for node '" << FLOW_CONTROLLER_NAME << "'"); + return XMLP_ret::XML_ERROR; + } + publishMode.flow_controller_name = element_inserted.first->c_str(); + } else { logError(XMLPARSER, "Invalid element found into 'publishModeQosPolicyType'. Name: " << name); diff --git a/src/cpp/rtps/xmlparser/XMLParser.cpp b/src/cpp/rtps/xmlparser/XMLParser.cpp index 3be03efe479..d4d7b981fd0 100644 --- a/src/cpp/rtps/xmlparser/XMLParser.cpp +++ b/src/cpp/rtps/xmlparser/XMLParser.cpp @@ -1794,6 +1794,7 @@ XMLP_ret XMLParser::fillDataNode( + @@ -1963,6 +1964,16 @@ XMLP_ret XMLParser::fillDataNode( } logWarning(XML_PARSER, THROUGHPUT_CONT << " XML tag is deprecated"); } + else if (strcmp(name, FLOW_CONTROLLER_DESCRIPTOR_LIST) == 0) + { + // flow_controller_descriptors + if (XMLP_ret::XML_OK != + getXMLFlowControllerDescriptorList(p_aux0, + participant_node.get()->rtps.flow_controllers, ident)) + { + return XMLP_ret::XML_ERROR; + } + } else if (strcmp(name, USER_TRANS) == 0) { // userTransports @@ -2287,6 +2298,13 @@ XMLP_ret XMLParser::fillDataNode( return XMLP_ret::XML_OK; } +XMLP_ret XMLParser::clear() +{ + std::lock_guard lock(collections_mtx_); + flow_controller_descriptor_names_.clear(); + return XMLP_ret::XML_OK; +} + } // namespace xmlparser } // namespace fastrtps } // namespace eprosima diff --git a/src/cpp/rtps/xmlparser/XMLParserCommon.cpp b/src/cpp/rtps/xmlparser/XMLParserCommon.cpp index d4339a436bc..e2a1775c2ac 100644 --- a/src/cpp/rtps/xmlparser/XMLParserCommon.cpp +++ b/src/cpp/rtps/xmlparser/XMLParserCommon.cpp @@ -98,6 +98,7 @@ const char* PHYSICAL_PORT = "physical_port"; const char* USER_DATA = "userData"; const char* PART_ID = "participantID"; const char* THROUGHPUT_CONT = "throughputController"; +const char* FLOW_CONTROLLER_DESCRIPTOR_LIST = "flow_controller_descriptor_list"; const char* USER_TRANS = "userTransports"; const char* USE_BUILTIN_TRANS = "useBuiltinTransports"; const char* BUILTIN_TRANS = "builtinTransports"; @@ -244,6 +245,15 @@ const char* ALLOCATED_SAMPLES = "allocated_samples"; const char* EXTRA_SAMPLES = "extra_samples"; const char* BYTES_PER_SECOND = "bytesPerPeriod"; const char* PERIOD_MILLISECS = "periodMillisecs"; +const char* FLOW_CONTROLLER_DESCRIPTOR = "flow_controller_descriptor"; +const char* SCHEDULER = "scheduler"; +const char* MAX_BYTES_PER_PERIOD = "max_bytes_per_period"; +const char* PERIOD_MS = "period_ms"; +const char* FLOW_CONTROLLER_NAME = "flow_controller_name"; +const char* FIFO = "FIFO"; +const char* HIGH_PRIORITY = "HIGH_PRIORITY"; +const char* ROUND_ROBIN = "ROUND_ROBIN"; +const char* PRIORITY_WITH_RESERVATION = "PRIORITY_WITH_RESERVATION"; const char* PORT_BASE = "portBase"; const char* DOMAIN_ID_GAIN = "domainIDGain"; const char* PARTICIPANT_ID_GAIN = "participantIDGain"; diff --git a/src/cpp/rtps/xmlparser/XMLProfileManager.cpp b/src/cpp/rtps/xmlparser/XMLProfileManager.cpp index f2d9b7a88c3..9d29230b2b7 100644 --- a/src/cpp/rtps/xmlparser/XMLProfileManager.cpp +++ b/src/cpp/rtps/xmlparser/XMLProfileManager.cpp @@ -778,4 +778,7 @@ void XMLProfileManager::DeleteInstance() } dynamic_types_.clear(); } + + // Clear XML Parser collections + XMLParser::clear(); } diff --git a/test/unittest/dds/profiles/test_xml_profiles.xml b/test/unittest/dds/profiles/test_xml_profiles.xml index b2a56011742..fd287421d06 100644 --- a/test/unittest/dds/profiles/test_xml_profiles.xml +++ b/test/unittest/dds/profiles/test_xml_profiles.xml @@ -130,6 +130,14 @@ true test_name + + + test_flow_controller + 2048 + 45 + FIFO + + @@ -259,6 +267,14 @@ true default_name + + + test_default_flow_controller + 2048 + 45 + FIFO + + @@ -418,6 +434,7 @@ ASYNCHRONOUS + test_default_flow_controller diff --git a/test/unittest/dds/profiles/test_xml_profiles_for_string.xml b/test/unittest/dds/profiles/test_xml_profiles_for_string.xml index eebe02e99fc..981f83d90e4 100644 --- a/test/unittest/dds/profiles/test_xml_profiles_for_string.xml +++ b/test/unittest/dds/profiles/test_xml_profiles_for_string.xml @@ -130,6 +130,14 @@ true test_name + + + test_flow_controller + 2048 + 45 + FIFO + + @@ -261,7 +269,7 @@ default_name - + NO_KEY @@ -481,7 +489,7 @@ 0 - + NO_KEY @@ -578,7 +586,7 @@ 5 - + WITH_KEY diff --git a/test/unittest/xmlparser/XMLElementParserTests.cpp b/test/unittest/xmlparser/XMLElementParserTests.cpp index 7c3cf6ffd49..c6d4ddb076c 100644 --- a/test/unittest/xmlparser/XMLElementParserTests.cpp +++ b/test/unittest/xmlparser/XMLElementParserTests.cpp @@ -1957,6 +1957,101 @@ TEST_F(XMLParserTests, getXMLBuiltinAttributes_NegativeClauses) EXPECT_EQ(XMLP_ret::XML_ERROR, XMLParserTest::getXMLBuiltinAttributes_wrapper(titleElement, builtin, ident)); } +/* + * This test checks parsing of flow_controller_descriptor_list elements. + */ +TEST_F(XMLParserTests, getXMLFlowControllerDescriptorList) +{ + uint8_t ident = 1; + + /* + * Aux mapping for FlowControllerSchedulerPolicy + */ + + auto scheduler_policy_map = std::map + { + {"FIFO", eprosima::fastdds::rtps::FlowControllerSchedulerPolicy::FIFO}, + {"ROUND_ROBIN", eprosima::fastdds::rtps::FlowControllerSchedulerPolicy::ROUND_ROBIN}, + {"HIGH_PRIORITY", eprosima::fastdds::rtps::FlowControllerSchedulerPolicy::HIGH_PRIORITY}, + {"PRIORITY_WITH_RESERVATION", eprosima::fastdds::rtps::FlowControllerSchedulerPolicy::PRIORITY_WITH_RESERVATION} + }; + + /* Define the test cases */ + std::vector, XMLP_ret>> test_cases = + { + /* + * name, scheduler, max_bytes_per_period, period_ms, extra_xml_tag + */ + {{"test_flow_controller", "FIFO", "120", "50", "" } \ + , XMLP_ret::XML_OK}, + {{"test_flow_controller", "ROUND_ROBIN", "2500", "100", "" } \ + , XMLP_ret::XML_OK}, + {{"test_flow_controller", "HIGH_PRIORITY", "2500", "100", "" } \ + , XMLP_ret::XML_OK}, + {{"test_flow_controller", "PRIORITY_WITH_RESERVATION", "2500", "100", "" } \ + , XMLP_ret::XML_OK}, + {{"test_flow_controller", "INVALID", "120", "50", "" } \ + , XMLP_ret::XML_ERROR}, // Invalid scheduler + {{"test_flow_controller", "HIGH_PRIORITY", "120", "-10", "" } \ + , XMLP_ret::XML_ERROR}, // negative period_ms + {{"test_flow_controller", "HIGH_PRIORITY", "120", "50", "" } \ + , XMLP_ret::XML_ERROR}, // Invalid tag + {{"", "HIGH_PRIORITY", "120", "50", "" } \ + , XMLP_ret::XML_ERROR}, // empty name + {{"test_flow_controller", "HIGH_PRIORITY", "120", "50", "another_name" } \ + , XMLP_ret::XML_ERROR}, // duplicated name tag + {{"test_flow_controller", "HIGH_PRIORITY", "120", "50", "FIFO" } \ + , XMLP_ret::XML_ERROR}, // duplicated scheduler tag + {{"test_flow_controller", "HIGH_PRIORITY", "120", "50", "96" } \ + , XMLP_ret::XML_ERROR}, // duplicated max_bytes_per_period tag + {{"test_flow_controller", "HIGH_PRIORITY", "120", "50", "96" } \ + , XMLP_ret::XML_ERROR}, // duplicated period_ms tag + }; + + /* Run the tests */ + for (auto test_case : test_cases) + { + std::vector& params = test_case.first; + XMLP_ret& expectation = test_case.second; + + using namespace eprosima::fastdds::rtps; + XMLParserTest::FlowControllerDescriptorList flow_controller_descriptor_list; + tinyxml2::XMLDocument xml_doc; + tinyxml2::XMLElement* titleElement; + + // Create XML snippet + std::string xml = + "" + " " + " " + params[0] + "" + " " + params[1] + "" + " " + params[2] + "" + " " + params[3] + "" + + params[4] + + " " + ""; + + // Parse the XML snippet + ASSERT_EQ(tinyxml2::XMLError::XML_SUCCESS, xml_doc.Parse(xml.c_str())) << xml; + + // Extract FlowControllersDescriptors + titleElement = xml_doc.RootElement(); + ASSERT_EQ(expectation, + XMLParserTest::getXMLFlowControllerDescriptorList_wrapper(titleElement, flow_controller_descriptor_list, + ident)); + + // Validate in the OK cases + if (expectation == XMLP_ret::XML_OK) + { + ASSERT_EQ(flow_controller_descriptor_list.at(0)->name, params[0]); + ASSERT_EQ(flow_controller_descriptor_list.at(0)->scheduler, scheduler_policy_map[params[1]]); + ASSERT_EQ(flow_controller_descriptor_list.at(0)->max_bytes_per_period, + static_cast(std::stoi(params[2]))); + ASSERT_EQ(flow_controller_descriptor_list.at(0)->period_ms, static_cast(std::stoi(params[3]))); + } + } +} + /* * This test checks the negative cases in the xml child element of * 1. Check an invalid tag of: @@ -2014,6 +2109,62 @@ TEST_F(XMLParserTests, getXMLThroughputController_NegativeClauses) XMLParserTest::getXMLThroughputController_wrapper(titleElement, throughputController, ident)); } +/* + * This test checks the negative cases in the xml child element of + * 1. Check an invalid tag of: + * + * 2. Check invalid element + */ +TEST_F(XMLParserTests, getXMLFlowControllerDescriptorList_NegativeClauses) +{ + uint8_t ident = 1; + XMLParserTest::FlowControllerDescriptorList flow_controller_descriptor_list; + tinyxml2::XMLDocument xml_doc; + tinyxml2::XMLElement* titleElement; + + // Parametrized XML + const char* xml_p = + "\ + \ + %s\ + \ + "; + constexpr size_t xml_len {1000}; + char xml[xml_len]; + const char* field_p = + "\ + <%s>\ + \ + \ + "; + constexpr size_t field_len {500}; + char field[field_len]; + + std::vector field_vec = + { + "flow_controller_descriptor" + }; + + for (std::string tag : field_vec) + { + snprintf(field, field_len, field_p, tag.c_str(), tag.c_str()); + snprintf(xml, xml_len, xml_p, field); + ASSERT_EQ(tinyxml2::XMLError::XML_SUCCESS, xml_doc.Parse(xml)); + titleElement = xml_doc.RootElement(); + EXPECT_EQ(XMLP_ret::XML_ERROR, + XMLParserTest::getXMLFlowControllerDescriptorList_wrapper(titleElement, flow_controller_descriptor_list, + ident)); + } + + // Invalid element + snprintf(xml, xml_len, xml_p, " "); + ASSERT_EQ(tinyxml2::XMLError::XML_SUCCESS, xml_doc.Parse(xml)); + titleElement = xml_doc.RootElement(); + EXPECT_EQ(XMLP_ret::XML_ERROR, + XMLParserTest::getXMLFlowControllerDescriptorList_wrapper(titleElement, flow_controller_descriptor_list, + ident)); +} + /* * This test checks the negative cases in the xml child element of * 1. Check an invalid tag of: diff --git a/test/unittest/xmlparser/XMLParserTests.cpp b/test/unittest/xmlparser/XMLParserTests.cpp index 9ffcae1bd84..39baa31dc83 100644 --- a/test/unittest/xmlparser/XMLParserTests.cpp +++ b/test/unittest/xmlparser/XMLParserTests.cpp @@ -423,6 +423,8 @@ TEST_F(XMLParserTests, Data) EXPECT_EQ(rtps_atts.participantID, 9898); EXPECT_EQ(rtps_atts.throughputController.bytesPerPeriod, 2048u); EXPECT_EQ(rtps_atts.throughputController.periodMillisecs, 45u); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->max_bytes_per_period, 2048); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->period_ms, 45u); EXPECT_EQ(rtps_atts.useBuiltinTransports, true); EXPECT_EQ(std::string(rtps_atts.getName()), "test_name"); EXPECT_EQ(rtps_atts.userData, std::vector({0x56, 0x30, 0x0, 0xce})); @@ -517,6 +519,8 @@ TEST_F(XMLParserTests, DataBuffer) EXPECT_EQ(rtps_atts.participantID, 9898); EXPECT_EQ(rtps_atts.throughputController.bytesPerPeriod, 2048u); EXPECT_EQ(rtps_atts.throughputController.periodMillisecs, 45u); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->max_bytes_per_period, 2048); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->period_ms, 45u); EXPECT_EQ(rtps_atts.useBuiltinTransports, true); EXPECT_EQ(std::string(rtps_atts.getName()), "test_name"); EXPECT_EQ(rtps_atts.userData, std::vector({0x56, 0x30, 0x0, 0xce})); @@ -1386,6 +1390,7 @@ TEST_F(XMLParserTests, fillDataNodeParticipantNegativeClauses) "", "", "", + "", "", "", "", diff --git a/test/unittest/xmlparser/XMLProfileParserTests.cpp b/test/unittest/xmlparser/XMLProfileParserTests.cpp index 54e5a864540..4fddeff92b6 100644 --- a/test/unittest/xmlparser/XMLProfileParserTests.cpp +++ b/test/unittest/xmlparser/XMLProfileParserTests.cpp @@ -302,6 +302,8 @@ TEST_F(XMLProfileParserTests, XMLParserParticipant) EXPECT_EQ(rtps_atts.participantID, 9898); EXPECT_EQ(rtps_atts.throughputController.bytesPerPeriod, 2048u); EXPECT_EQ(rtps_atts.throughputController.periodMillisecs, 45u); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->max_bytes_per_period, 2048); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->period_ms, 45u); EXPECT_EQ(rtps_atts.useBuiltinTransports, true); EXPECT_EQ(std::string(rtps_atts.getName()), "test_name"); } @@ -380,6 +382,8 @@ TEST_F(XMLProfileParserTests, XMLParserDefaultParticipantProfile) EXPECT_EQ(rtps_atts.participantID, 9898); EXPECT_EQ(rtps_atts.throughputController.bytesPerPeriod, 2048u); EXPECT_EQ(rtps_atts.throughputController.periodMillisecs, 45u); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->max_bytes_per_period, 2048); + EXPECT_EQ(rtps_atts.flow_controllers.at(0)->period_ms, 45u); EXPECT_EQ(rtps_atts.useBuiltinTransports, true); EXPECT_EQ(std::string(rtps_atts.getName()), "test_name"); } @@ -419,6 +423,7 @@ TEST_F(XMLProfileParserTests, XMLParserPublisher) EXPECT_EQ(pub_qos.m_partition.names()[0], "partition_name_a"); EXPECT_EQ(pub_qos.m_partition.names()[1], "partition_name_b"); EXPECT_EQ(pub_qos.m_publishMode.kind, ASYNCHRONOUS_PUBLISH_MODE); + EXPECT_EQ(0, strcmp(pub_qos.m_publishMode.flow_controller_name, "test_flow_controller")); EXPECT_EQ(pub_times.initialHeartbeatDelay, c_TimeZero); EXPECT_EQ(pub_times.heartbeatPeriod.seconds, 11); EXPECT_EQ(pub_times.heartbeatPeriod.nanosec, 32u); @@ -488,6 +493,7 @@ TEST_F(XMLProfileParserTests, XMLParserDefaultPublisherProfile) EXPECT_EQ(pub_qos.m_partition.names()[0], "partition_name_a"); EXPECT_EQ(pub_qos.m_partition.names()[1], "partition_name_b"); EXPECT_EQ(pub_qos.m_publishMode.kind, ASYNCHRONOUS_PUBLISH_MODE); + EXPECT_EQ(0, strcmp(pub_qos.m_publishMode.flow_controller_name, "test_flow_controller")); EXPECT_EQ(pub_times.initialHeartbeatDelay, c_TimeZero); EXPECT_EQ(pub_times.heartbeatPeriod.seconds, 11); EXPECT_EQ(pub_times.heartbeatPeriod.nanosec, 32u); diff --git a/test/unittest/xmlparser/test_xml_profile.xml b/test/unittest/xmlparser/test_xml_profile.xml new file mode 100644 index 00000000000..0a54905b081 --- /dev/null +++ b/test/unittest/xmlparser/test_xml_profile.xml @@ -0,0 +1,420 @@ + + + + + + + true + + + -1 + 0 + 0 + -1 + + + -1 + 0 + 0 + -1 + + + + + 123 + + + + 4 + 1 + + + 10 + 20 + 2 + + + 10 + 20 + 2 + + + 10 + 20 + 2 + + + 127 + true + + + + + +
192.168.1.2
+ 2019 +
+
+
+ + + +
239.255.0.1
+ 2021 +
+
+
+ true + + +
10.10.10.10
+ 2001 +
+
+ 32 + 1000 + + + SIMPLE + SIMPLE + FILTER_SAME_PROCESS | FILTER_DIFFERENT_HOST + + 10 + 333 + + + DURATION_INFINITY + + + 2 + + 1 + 827 + + + + false + true + + + false + false + + + +
192.168.1.5
+ 9999 +
+
+ + +
192.168.1.6
+ 6666 +
+
+
+ + + +
239.255.0.2
+ 32 +
+
+ + +
239.255.0.3
+ 2112 +
+
+
+ + +
10.10.10.10
+ 2002 +
+
+ + + +
239.255.0.1
+ 21120 +
+
+
+ PREALLOCATED + PREALLOCATED + 1000 + 2000 + 55 + + true + true + +
+ + 12 + 34 + 56 + 78 + 90 + 123 + 456 + + 9898 + true + ON + test_name + + 56.30.0.ce + + + + test_flow_controller + HIGH_PRIORITY + 2048 + 45 + + +
+
+ + + + + KEEP_LAST + 50 + + + 432 + 1 + 100 + 123 + + + + + TRANSIENT_LOCAL + + + MANUAL_BY_PARTICIPANT + + 1 + 2 + + + DURATION_INFINITY + + + + BEST_EFFORT + + 0 + 0 + + + + + partition_name_a + partition_name_b + + + + ASYNCHRONOUS + test_flow_controller + + + 56.30.0.1 + + + 5.3.1.0 + + + 5.3.1.0.F1 + + + + + 0 + 0 + + + 11 + 32 + + + 0 + 0 + + + 121 + 332 + + + + + +
192.168.1.3
+ 197 +
+
+ + +
192.168.1.9
+ 219 +
+
+
+ + + +
239.255.0.1
+ 2020 +
+
+ + + + + + 1989 + + +
+ true + + +
10.10.10.10
+ 2001 +
+
+ DYNAMIC + 67 + 87 + + 10 + 10 + 0 + +
+ + + + + KEEP_ALL + 1001 + + + 52 + 25 + 32 + 37 + + + + + PERSISTENT + + + MANUAL_BY_TOPIC + + 11 + 22 + + + 0 + 0 + + + + RELIABLE + + DURATION_INFINITY + + + + + partition_name_c + partition_name_d + partition_name_e + partition_name_f + + + + 56.30.0.1 + + + 5.3.1.0 + + + 5.3.1.0.F1 + + + + + 0 + 0 + + + 18 + 81 + + + + + +
192.168.1.10
+ 196 +
+
+ + + 212 + + +
+ + + +
239.255.0.10
+ 220 +
+
+ + + + + + +
239.255.0.11
+ 9891 +
+
+
+ true + + +
10.10.10.10
+ 2001 +
+
+ true + PREALLOCATED_WITH_REALLOC + 13 + 31 + + 10 + 10 + 0 + +
+ + + KEEP_ALL + 1001 + + +
+ + FULL + +
diff --git a/test/unittest/xmlparser/test_xml_profile_env_var.xml b/test/unittest/xmlparser/test_xml_profile_env_var.xml new file mode 100644 index 00000000000..4d86519bd15 --- /dev/null +++ b/test/unittest/xmlparser/test_xml_profile_env_var.xml @@ -0,0 +1,420 @@ + + + + + ${XML_PROFILES_ENV_VAR_1} + + + + ${XML_PROFILES_ENV_VAR_2} + ${XML_PROFILES_ENV_VAR_3} + + + ${XML_PROFILES_ENV_VAR_4} + ${XML_PROFILES_ENV_VAR_5} + ${XML_PROFILES_ENV_VAR_6} + + + ${XML_PROFILES_ENV_VAR_7} + ${XML_PROFILES_ENV_VAR_8} + ${XML_PROFILES_ENV_VAR_9} + + + ${XML_PROFILES_ENV_VAR_10} + ${XML_PROFILES_ENV_VAR_11} + ${XML_PROFILES_ENV_VAR_12} + + + ${XML_PROFILES_ENV_VAR_13} + ${XML_PROFILES_ENV_VAR_14} + + + + + +
${XML_PROFILES_ENV_VAR_15}
+ ${XML_PROFILES_ENV_VAR_16} +
+
+
+ + + +
${XML_PROFILES_ENV_VAR_17}
+ ${XML_PROFILES_ENV_VAR_18} +
+
+
+ ${XML_PROFILES_ENV_VAR_19} + + +
${XML_PROFILES_ENV_VAR_20}
+ ${XML_PROFILES_ENV_VAR_21} +
+
+ ${XML_PROFILES_ENV_VAR_22} + ${XML_PROFILES_ENV_VAR_23} + + + ${XML_PROFILES_ENV_VAR_24} + ${XML_PROFILES_ENV_VAR_25} + ${XML_PROFILES_ENV_VAR_26} + + ${XML_PROFILES_ENV_VAR_27} + ${XML_PROFILES_ENV_VAR_28} + + + ${XML_PROFILES_ENV_VAR_29} + + + ${XML_PROFILES_ENV_VAR_30} + + ${XML_PROFILES_ENV_VAR_31} + ${XML_PROFILES_ENV_VAR_32} + + + + ${XML_PROFILES_ENV_VAR_33} + ${XML_PROFILES_ENV_VAR_34} + + + ${XML_PROFILES_ENV_VAR_35} + ${XML_PROFILES_ENV_VAR_36} + + + +
${XML_PROFILES_ENV_VAR_37}
+ ${XML_PROFILES_ENV_VAR_38} +
+
+ + +
${XML_PROFILES_ENV_VAR_39}
+ ${XML_PROFILES_ENV_VAR_40} +
+
+
+ + + +
${XML_PROFILES_ENV_VAR_41}
+ ${XML_PROFILES_ENV_VAR_42} +
+
+ + +
${XML_PROFILES_ENV_VAR_43}
+ ${XML_PROFILES_ENV_VAR_44} +
+
+
+ + +
${XML_PROFILES_ENV_VAR_45}
+ ${XML_PROFILES_ENV_VAR_46} +
+
+ + + +
${XML_PROFILES_ENV_VAR_47}
+ ${XML_PROFILES_ENV_VAR_48} +
+
+
+ ${XML_PROFILES_ENV_VAR_49} + ${XML_PROFILES_ENV_VAR_50} + ${XML_PROFILES_ENV_VAR_51} + ${XML_PROFILES_ENV_VAR_52} + ${XML_PROFILES_ENV_VAR_53} + + ${XML_PROFILES_ENV_VAR_54} + ${XML_PROFILES_ENV_VAR_55} + +
+ + ${XML_PROFILES_ENV_VAR_56} + ${XML_PROFILES_ENV_VAR_57} + ${XML_PROFILES_ENV_VAR_58} + ${XML_PROFILES_ENV_VAR_59} + ${XML_PROFILES_ENV_VAR_60} + ${XML_PROFILES_ENV_VAR_61} + ${XML_PROFILES_ENV_VAR_62} + + ${XML_PROFILES_ENV_VAR_63} + ${XML_PROFILES_ENV_VAR_64} + ${XML_PROFILES_ENV_VAR_162} + ${XML_PROFILES_ENV_VAR_65} + + ${XML_PROFILES_ENV_VAR_66} + + + + ${XML_PROFILES_ENV_VAR_163} + ${XML_PROFILES_ENV_VAR_164} + ${XML_PROFILES_ENV_VAR_165} + ${XML_PROFILES_ENV_VAR_166} + + +
+
+ + + + + ${XML_PROFILES_ENV_VAR_67} + ${XML_PROFILES_ENV_VAR_68} + + + ${XML_PROFILES_ENV_VAR_69} + ${XML_PROFILES_ENV_VAR_70} + ${XML_PROFILES_ENV_VAR_71} + ${XML_PROFILES_ENV_VAR_72} + + + + + ${XML_PROFILES_ENV_VAR_73} + + + ${XML_PROFILES_ENV_VAR_74} + + ${XML_PROFILES_ENV_VAR_75} + ${XML_PROFILES_ENV_VAR_76} + + + ${XML_PROFILES_ENV_VAR_77} + + + + ${XML_PROFILES_ENV_VAR_78} + + ${XML_PROFILES_ENV_VAR_79} + ${XML_PROFILES_ENV_VAR_80} + + + + + ${XML_PROFILES_ENV_VAR_81} + ${XML_PROFILES_ENV_VAR_82} + + + + ${XML_PROFILES_ENV_VAR_83} + ${XML_PROFILES_ENV_VAR_167} + + + ${XML_PROFILES_ENV_VAR_84} + + + ${XML_PROFILES_ENV_VAR_85} + + + ${XML_PROFILES_ENV_VAR_86} + + + + + ${XML_PROFILES_ENV_VAR_87} + ${XML_PROFILES_ENV_VAR_88} + + + ${XML_PROFILES_ENV_VAR_89} + ${XML_PROFILES_ENV_VAR_90} + + + ${XML_PROFILES_ENV_VAR_91} + ${XML_PROFILES_ENV_VAR_92} + + + ${XML_PROFILES_ENV_VAR_93} + ${XML_PROFILES_ENV_VAR_94} + + + + + +
${XML_PROFILES_ENV_VAR_95}
+ ${XML_PROFILES_ENV_VAR_96} +
+
+ + +
${XML_PROFILES_ENV_VAR_97}
+ ${XML_PROFILES_ENV_VAR_98} +
+
+
+ + + +
${XML_PROFILES_ENV_VAR_99}
+ ${XML_PROFILES_ENV_VAR_100} +
+
+ + ${XML_PROFILES_ENV_VAR_101} + + + + ${XML_PROFILES_ENV_VAR_102} + + +
+ ${XML_PROFILES_ENV_VAR_103} + + +
${XML_PROFILES_ENV_VAR_104}
+ ${XML_PROFILES_ENV_VAR_105} +
+
+ ${XML_PROFILES_ENV_VAR_106} + ${XML_PROFILES_ENV_VAR_107} + ${XML_PROFILES_ENV_VAR_108} + + ${XML_PROFILES_ENV_VAR_109} + ${XML_PROFILES_ENV_VAR_110} + ${XML_PROFILES_ENV_VAR_111} + +
+ + + + + ${XML_PROFILES_ENV_VAR_112} + ${XML_PROFILES_ENV_VAR_113} + + + ${XML_PROFILES_ENV_VAR_114} + ${XML_PROFILES_ENV_VAR_115} + ${XML_PROFILES_ENV_VAR_116} + ${XML_PROFILES_ENV_VAR_117} + + + + + ${XML_PROFILES_ENV_VAR_118} + + + ${XML_PROFILES_ENV_VAR_119} + + ${XML_PROFILES_ENV_VAR_120} + ${XML_PROFILES_ENV_VAR_121} + + + ${XML_PROFILES_ENV_VAR_122} + ${XML_PROFILES_ENV_VAR_123} + + + + ${XML_PROFILES_ENV_VAR_124} + + ${XML_PROFILES_ENV_VAR_125} + + + + + ${XML_PROFILES_ENV_VAR_126} + ${XML_PROFILES_ENV_VAR_127} + ${XML_PROFILES_ENV_VAR_128} + ${XML_PROFILES_ENV_VAR_129} + + + + ${XML_PROFILES_ENV_VAR_130} + + + ${XML_PROFILES_ENV_VAR_131} + + + ${XML_PROFILES_ENV_VAR_132} + + + + + ${XML_PROFILES_ENV_VAR_133} + ${XML_PROFILES_ENV_VAR_134} + + + ${XML_PROFILES_ENV_VAR_135} + ${XML_PROFILES_ENV_VAR_136} + + + + + +
${XML_PROFILES_ENV_VAR_137}
+ ${XML_PROFILES_ENV_VAR_138} +
+
+ + + ${XML_PROFILES_ENV_VAR_139} + + +
+ + + +
${XML_PROFILES_ENV_VAR_140}
+ ${XML_PROFILES_ENV_VAR_141} +
+
+ + + + + + +
${XML_PROFILES_ENV_VAR_142}
+ ${XML_PROFILES_ENV_VAR_143} +
+
+
+ ${XML_PROFILES_ENV_VAR_144} + + +
${XML_PROFILES_ENV_VAR_145}
+ ${XML_PROFILES_ENV_VAR_146} +
+
+ ${XML_PROFILES_ENV_VAR_147} + ${XML_PROFILES_ENV_VAR_148} + ${XML_PROFILES_ENV_VAR_149} + ${XML_PROFILES_ENV_VAR_150} + + ${XML_PROFILES_ENV_VAR_151} + ${XML_PROFILES_ENV_VAR_152} + ${XML_PROFILES_ENV_VAR_153} + +
+ + + ${XML_PROFILES_ENV_VAR_154} + ${XML_PROFILES_ENV_VAR_155} + + +
+ + ${XML_PROFILES_ENV_VAR_156} + + + + + ${XML_PROFILES_ENV_VAR_157} + + + ${XML_PROFILES_ENV_VAR_158} + ${XML_PROFILES_ENV_VAR_159} + ${XML_PROFILES_ENV_VAR_160} + ${XML_PROFILES_ENV_VAR_161} + + + ${XML_PROFILES_ENV_VAR_158} + ${XML_PROFILES_ENV_VAR_159} + ${XML_PROFILES_ENV_VAR_160} + ${XML_PROFILES_ENV_VAR_161} + + + +
diff --git a/test/unittest/xmlparser/test_xml_profiles.xml b/test/unittest/xmlparser/test_xml_profiles.xml index 5bc68852324..c939ef5bf3d 100644 --- a/test/unittest/xmlparser/test_xml_profiles.xml +++ b/test/unittest/xmlparser/test_xml_profiles.xml @@ -132,6 +132,14 @@ 2048 45
+ + + test_flow_controller + 2048 + 45 + FIFO + + true test_name @@ -186,6 +194,7 @@ ASYNCHRONOUS + test_flow_controller 56.30.0.1 diff --git a/test/unittest/xmlparser/test_xml_profiles_rooted.xml b/test/unittest/xmlparser/test_xml_profiles_rooted.xml index 0e9d162831d..7927cd28d81 100644 --- a/test/unittest/xmlparser/test_xml_profiles_rooted.xml +++ b/test/unittest/xmlparser/test_xml_profiles_rooted.xml @@ -120,6 +120,14 @@ 2048 45 + + + test_flow_controller + 2048 + 45 + FIFO + + true test_name diff --git a/test/unittest/xmlparser/wrapper/XMLParserTest.hpp b/test/unittest/xmlparser/wrapper/XMLParserTest.hpp index 2e783109b3c..8ba6e44e2e4 100644 --- a/test/unittest/xmlparser/wrapper/XMLParserTest.hpp +++ b/test/unittest/xmlparser/wrapper/XMLParserTest.hpp @@ -222,6 +222,14 @@ class XMLParserTest : public XMLParser return getXMLThroughputController(elem, throughputController, ident); } + static XMLP_ret getXMLFlowControllerDescriptorList_wrapper( + tinyxml2::XMLElement* elem, + FlowControllerDescriptorList& flow_controller_descriptors, + uint8_t ident) + { + return getXMLFlowControllerDescriptorList(elem, flow_controller_descriptors, ident); + } + static XMLP_ret getXMLTopicAttributes_wrapper( tinyxml2::XMLElement* elem, TopicAttributes& topic, diff --git a/versions.md b/versions.md index 4528fe0ba57..35c3e2401be 100644 --- a/versions.md +++ b/versions.md @@ -1,7 +1,7 @@ Forthcoming ----------- - +* Added new `flow_controller_descriptor_list` XML configuration. Version 2.6.8 -------------