Skip to content

Commit 6e116c6

Browse files
authored
Add cleanup_controller lifecycle transition (backport #2414) (#2915)
1 parent c043c43 commit 6e116c6

File tree

13 files changed

+385
-2
lines changed

13 files changed

+385
-2
lines changed

controller_manager/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ if(BUILD_TESTING)
140140
ros2_control_test_assets::ros2_control_test_assets
141141
)
142142

143+
ament_add_gmock(test_cleanup_controller
144+
test/test_cleanup_controller.cpp
145+
APPEND_ENV AMENT_PREFIX_PATH=${ament_index_build_path}_$<CONFIG>
146+
)
147+
target_link_libraries(test_cleanup_controller
148+
controller_manager
149+
test_controller
150+
test_controller_failed_init
151+
ros2_control_test_assets::ros2_control_test_assets
152+
)
153+
143154
ament_add_gmock(test_controllers_chaining_with_controller_manager
144155
test/test_controllers_chaining_with_controller_manager.cpp
145156
)

controller_manager/controller_manager/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
set_hardware_component_state,
2424
switch_controllers,
2525
unload_controller,
26+
cleanup_controller,
2627
get_parameter_from_param_files,
2728
set_controller_parameters,
2829
set_controller_parameters_from_param_files,
@@ -40,6 +41,7 @@
4041
"set_hardware_component_state",
4142
"switch_controllers",
4243
"unload_controller",
44+
"cleanup_controller",
4345
"get_parameter_from_param_files",
4446
"set_controller_parameters",
4547
"set_controller_parameters_from_param_files",

controller_manager/controller_manager/controller_manager_services.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
SetHardwareComponentState,
2424
SwitchController,
2525
UnloadController,
26+
CleanupController,
2627
)
2728

2829
import rclpy
@@ -323,6 +324,21 @@ def unload_controller(
323324
)
324325

325326

327+
def cleanup_controller(
328+
node, controller_manager_name, controller_name, service_timeout=0.0, call_timeout=10.0
329+
):
330+
request = CleanupController.Request()
331+
request.name = controller_name
332+
return service_caller(
333+
node,
334+
f"{controller_manager_name}/cleanup_controller",
335+
CleanupController,
336+
request,
337+
service_timeout,
338+
call_timeout,
339+
)
340+
341+
326342
def get_params_files_with_controller_parameters(
327343
node, controller_name: str, namespace: str, parameter_files: list
328344
):

controller_manager/include/controller_manager/controller_manager.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include "controller_manager/controller_spec.hpp"
3030
#include "controller_manager_msgs/msg/controller_manager_activity.hpp"
31+
#include "controller_manager_msgs/srv/cleanup_controller.hpp"
3132
#include "controller_manager_msgs/srv/configure_controller.hpp"
3233
#include "controller_manager_msgs/srv/list_controller_types.hpp"
3334
#include "controller_manager_msgs/srv/list_controllers.hpp"
@@ -108,6 +109,8 @@ class ControllerManager : public rclcpp::Node
108109

109110
controller_interface::return_type unload_controller(const std::string & controller_name);
110111

112+
controller_interface::return_type cleanup_controller(const std::string & controller_name);
113+
111114
std::vector<ControllerSpec> get_loaded_controllers() const;
112115

113116
template <
@@ -314,6 +317,10 @@ class ControllerManager : public rclcpp::Node
314317
const std::shared_ptr<controller_manager_msgs::srv::UnloadController::Request> request,
315318
std::shared_ptr<controller_manager_msgs::srv::UnloadController::Response> response);
316319

320+
void cleanup_controller_service_cb(
321+
const std::shared_ptr<controller_manager_msgs::srv::CleanupController::Request> request,
322+
std::shared_ptr<controller_manager_msgs::srv::CleanupController::Response> response);
323+
317324
void list_controller_types_srv_cb(
318325
const std::shared_ptr<controller_manager_msgs::srv::ListControllerTypes::Request> request,
319326
std::shared_ptr<controller_manager_msgs::srv::ListControllerTypes::Response> response);
@@ -650,6 +657,8 @@ class ControllerManager : public rclcpp::Node
650657
switch_controller_service_;
651658
rclcpp::Service<controller_manager_msgs::srv::UnloadController>::SharedPtr
652659
unload_controller_service_;
660+
rclcpp::Service<controller_manager_msgs::srv::CleanupController>::SharedPtr
661+
cleanup_controller_service_;
653662

654663
rclcpp::Service<controller_manager_msgs::srv::ListHardwareComponents>::SharedPtr
655664
list_hardware_components_service_;

controller_manager/src/controller_manager.cpp

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,10 @@ void ControllerManager::init_services()
890890
"~/unload_controller",
891891
std::bind(&ControllerManager::unload_controller_service_cb, this, _1, _2), qos_services,
892892
best_effort_callback_group_);
893+
cleanup_controller_service_ = create_service<controller_manager_msgs::srv::CleanupController>(
894+
"~/cleanup_controller",
895+
std::bind(&ControllerManager::cleanup_controller_service_cb, this, _1, _2), qos_services,
896+
best_effort_callback_group_);
893897
list_hardware_components_service_ =
894898
create_service<controller_manager_msgs::srv::ListHardwareComponents>(
895899
"~/list_hardware_components",
@@ -1139,9 +1143,14 @@ controller_interface::return_type ControllerManager::unload_controller(
11391143
return controller_interface::return_type::ERROR;
11401144
}
11411145

1146+
// call cleanup transition, if it is inactive
1147+
if (cleanup_controller(controller_name) != controller_interface::return_type::OK)
1148+
{
1149+
return controller_interface::return_type::ERROR;
1150+
}
1151+
11421152
RCLCPP_DEBUG(get_logger(), "Shutdown controller");
11431153
controller_chain_spec_cleanup(controller_chain_spec_, controller_name);
1144-
cleanup_controller_exported_interfaces(controller);
11451154
if (is_controller_inactive(*controller.c) || is_controller_unconfigured(*controller.c))
11461155
{
11471156
RCLCPP_DEBUG(
@@ -1192,6 +1201,55 @@ controller_interface::return_type ControllerManager::cleanup_controller(
11921201
return controller_interface::return_type::OK;
11931202
}
11941203

1204+
controller_interface::return_type ControllerManager::cleanup_controller(
1205+
const std::string & controller_name)
1206+
{
1207+
const auto & controllers = get_loaded_controllers();
1208+
1209+
auto found_it = std::find_if(
1210+
controllers.begin(), controllers.end(),
1211+
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
1212+
1213+
if (found_it == controllers.end())
1214+
{
1215+
RCLCPP_ERROR(
1216+
get_logger(),
1217+
"Could not cleanup controller with name '%s' because no controller with this name exists",
1218+
controller_name.c_str());
1219+
return controller_interface::return_type::ERROR;
1220+
}
1221+
auto controller = found_it->c;
1222+
1223+
if (is_controller_unconfigured(*controller))
1224+
{
1225+
// all good nothing to do!
1226+
return controller_interface::return_type::OK;
1227+
}
1228+
1229+
RCLCPP_INFO(get_logger(), "Cleanup controller '%s'", controller_name.c_str());
1230+
1231+
auto state = controller->get_lifecycle_state();
1232+
if (
1233+
state.id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE ||
1234+
state.id() == lifecycle_msgs::msg::State::PRIMARY_STATE_FINALIZED)
1235+
{
1236+
RCLCPP_ERROR(
1237+
get_logger(), "Controller '%s' can not be cleaned-up from '%s' state.",
1238+
controller_name.c_str(), state.label().c_str());
1239+
return controller_interface::return_type::ERROR;
1240+
}
1241+
1242+
RCLCPP_DEBUG(get_logger(), "Calling cleanup for controller '%s'", controller_name.c_str());
1243+
auto result = cleanup_controller(*found_it);
1244+
1245+
if (result == controller_interface::return_type::OK)
1246+
{
1247+
RCLCPP_DEBUG(get_logger(), "Successfully cleaned-up controller '%s'", controller_name.c_str());
1248+
}
1249+
1250+
return result;
1251+
}
1252+
11951253
void ControllerManager::shutdown_controller(
11961254
const controller_manager::ControllerSpec & controller) const
11971255
{
@@ -2780,6 +2838,21 @@ void ControllerManager::unload_controller_service_cb(
27802838
get_logger(), "unloading service finished for controller '%s' ", request->name.c_str());
27812839
}
27822840

2841+
void ControllerManager::cleanup_controller_service_cb(
2842+
const std::shared_ptr<controller_manager_msgs::srv::CleanupController::Request> request,
2843+
std::shared_ptr<controller_manager_msgs::srv::CleanupController::Response> response)
2844+
{
2845+
// lock services
2846+
RCLCPP_DEBUG(get_logger(), "cleanup service called for controller '%s' ", request->name.c_str());
2847+
std::lock_guard<std::mutex> guard(services_lock_);
2848+
RCLCPP_DEBUG(get_logger(), "cleanup service locked");
2849+
2850+
response->ok = cleanup_controller(request->name) == controller_interface::return_type::OK;
2851+
2852+
RCLCPP_DEBUG(
2853+
get_logger(), "cleanup service finished for controller '%s' ", request->name.c_str());
2854+
}
2855+
27832856
void ControllerManager::list_hardware_components_srv_cb(
27842857
const std::shared_ptr<controller_manager_msgs::srv::ListHardwareComponents::Request>,
27852858
std::shared_ptr<controller_manager_msgs::srv::ListHardwareComponents::Response> response)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2024 Stogl Robotics Consulting UG (haftungsbeschränkt)
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <gmock/gmock.h>
16+
#include <gtest/gtest.h>
17+
#include <memory>
18+
#include <string>
19+
#include <tuple>
20+
#include <vector>
21+
22+
#include "controller_interface/controller_interface.hpp"
23+
#include "controller_manager/controller_manager.hpp"
24+
#include "controller_manager_test_common.hpp"
25+
#include "lifecycle_msgs/msg/state.hpp"
26+
#include "test_controller/test_controller.hpp"
27+
#include "test_controller_failed_init/test_controller_failed_init.hpp"
28+
29+
using test_controller::TEST_CONTROLLER_CLASS_NAME;
30+
using ::testing::_;
31+
using ::testing::Return;
32+
const auto CONTROLLER_NAME_1 = "test_controller1";
33+
using strvec = std::vector<std::string>;
34+
35+
class TestCleanupController : public ControllerManagerFixture<controller_manager::ControllerManager>
36+
{
37+
};
38+
39+
TEST_F(TestCleanupController, cleanup_unknown_controller)
40+
{
41+
ASSERT_EQ(
42+
cm_->cleanup_controller("unknown_controller_name"), controller_interface::return_type::ERROR);
43+
}
44+
45+
TEST_F(TestCleanupController, cleanup_controller_failed_init)
46+
{
47+
auto controller_if = cm_->load_controller(
48+
"test_controller_failed_init",
49+
test_controller_failed_init::TEST_CONTROLLER_FAILED_INIT_CLASS_NAME);
50+
51+
ASSERT_EQ(
52+
cm_->cleanup_controller("test_controller_failed_init"),
53+
controller_interface::return_type::ERROR);
54+
}
55+
56+
TEST_F(TestCleanupController, cleanup_non_loaded_controller_fails)
57+
{
58+
// try cleanup non-loaded controller
59+
EXPECT_EQ(cm_->cleanup_controller(CONTROLLER_NAME_1), controller_interface::return_type::ERROR);
60+
}
61+
62+
TEST_F(TestCleanupController, cleanup_unconfigured_controller)
63+
{
64+
auto controller_if =
65+
cm_->load_controller(CONTROLLER_NAME_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
66+
ASSERT_NE(controller_if, nullptr);
67+
68+
ASSERT_EQ(
69+
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED,
70+
controller_if->get_lifecycle_state().id());
71+
ASSERT_EQ(cm_->cleanup_controller(CONTROLLER_NAME_1), controller_interface::return_type::OK);
72+
EXPECT_EQ(
73+
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED,
74+
controller_if->get_lifecycle_state().id());
75+
}
76+
77+
TEST_F(TestCleanupController, cleanup_inactive_controller)
78+
{
79+
auto controller_if =
80+
cm_->load_controller(CONTROLLER_NAME_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
81+
ASSERT_NE(controller_if, nullptr);
82+
ASSERT_EQ(
83+
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED,
84+
controller_if->get_lifecycle_state().id());
85+
86+
cm_->configure_controller(CONTROLLER_NAME_1);
87+
88+
EXPECT_EQ(
89+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, controller_if->get_lifecycle_state().id());
90+
ASSERT_EQ(cm_->cleanup_controller(CONTROLLER_NAME_1), controller_interface::return_type::OK);
91+
EXPECT_EQ(
92+
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED,
93+
controller_if->get_lifecycle_state().id());
94+
}
95+
96+
TEST_F(TestCleanupController, cleanup_active_controller)
97+
{
98+
auto controller_if =
99+
cm_->load_controller(CONTROLLER_NAME_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
100+
ASSERT_NE(controller_if, nullptr);
101+
ASSERT_EQ(
102+
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED,
103+
controller_if->get_lifecycle_state().id());
104+
105+
cm_->configure_controller(CONTROLLER_NAME_1);
106+
107+
EXPECT_EQ(
108+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, controller_if->get_lifecycle_state().id());
109+
110+
switch_test_controllers(strvec{CONTROLLER_NAME_1}, strvec{}, STRICT);
111+
112+
EXPECT_EQ(
113+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, controller_if->get_lifecycle_state().id());
114+
115+
ASSERT_EQ(cm_->cleanup_controller(CONTROLLER_NAME_1), controller_interface::return_type::ERROR);
116+
EXPECT_EQ(
117+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, controller_if->get_lifecycle_state().id());
118+
}

0 commit comments

Comments
 (0)