-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add options extensions to ros2launch and extensibility to the node action #216
Conversation
Ready for review. |
@gbiggs thank you for doing this. I will leave a code review to more qualified people, but I'll say we do believe this covers the functionality we would need for what we're doing. |
@kyrofa Let us know who you want to set as maintainer, and if the security WG wants to take the plugins one. |
@gbiggs I can take it and add the necessary people if that works. |
Great, thanks! I've added you as maintainer to the new package. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, only some minor nitpicks.
@@ -14,8 +14,10 @@ | |||
|
|||
"""Module for the Node action.""" | |||
|
|||
import importlib_metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Third party package, should be in the same block as yaml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ded9e1d
import os | ||
import pathlib | ||
from packaging.version import Version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Third party package, should be in the same block as yaml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ded9e1d
ros2launch/ros2launch/api/api.py
Outdated
) | ||
launch_description = result[0] | ||
if len(result) > 1: | ||
tails[name] = result[1:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this variable used solely for debugging? It isn't ever read from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was. Fixed in 22d14af
This pull request has been mentioned on ROS Discourse. There might be relevant details there: https://discourse.ros.org/t/ros-2-tsc-meeting-minutes-2021-02-18/19102/1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass!
* `NAME` (will be set to the entry point name) | ||
|
||
The following methods may be defined: | ||
* `add_arguments` to add arguments to the argparse parser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs nit: consider moving documentation to each method, and only listing which ones are required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 1e18575
@@ -145,6 +150,7 @@ def main(self, *, parser, args): | |||
else: | |||
raise RuntimeError('unexpected mode') | |||
launch_arguments.extend(args.launch_arguments) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs unrelated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This vanished when I rebased.
def prelaunch(self, launch_description, options, args): | ||
return (launch_description,) | ||
|
||
def postlaunch(self, launch_return_code, options, args): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs hmm, what is this options
argument for? BTW it doesn't seem like prelaunch
and postlaunch
are provided with it in any call site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it was vestigial of an earlier iteration of the changes. I've removed the arguments in 40ee929.
* `NAME` (will be set to the entry point name) | ||
|
||
The following methods may be defined: | ||
* `command_extension` to extend the command used to launch the node's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs nit: consider moving documentation to each method, and only listing which ones are required here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 6754e9a.
@@ -408,6 +453,10 @@ def execute(self, context: LaunchContext) -> Optional[List[Action]]: | |||
ros_specific_arguments['name'] = '__node:={}'.format(self.__expanded_node_name) | |||
if self.__expanded_node_namespace != '': | |||
ros_specific_arguments['ns'] = '__ns:={}'.format(self.__expanded_node_namespace) | |||
node_info = NodeActionExtension.NodeInfo(self.__package, self.__node_executable, self.node_name) | |||
for extension in self.__extensions.values(): | |||
ros_specific_arguments = extension.execute(context, ros_specific_arguments, node_info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs why not passing the node action itself instead of this ad-hoc data structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seemed cleaner to pass in a minimal data structure rather than have extensions need to know about the entire Node
action class and its internal variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I will point out that this data in particular (e.g. the package name) could be exposed via public API. You wouldn't have to access internal implementations details from the extension in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added public API to expose the package and executable, and removed the node_info
structure in 2d9f3d1.
@@ -342,6 +383,10 @@ def _perform_substitutions(self, context: LaunchContext) -> None: | |||
raise | |||
self.__final_node_name = prefix_namespace( | |||
self.__expanded_node_namespace, self.__expanded_node_name) | |||
|
|||
for extension in self.__extensions.values(): | |||
self.cmd.extend(extension.command_extension(context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs why a method to specifically extend the command? Isn't it possible to do so (even resolving substitutions) upon extension execution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember specifically, but I suspect I was following the existing pattern of the Node
class, which splits up building the command and executing it. Possibly it does things that way for debug output?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, neither NodeExtension.command_extension
nor NodeExtension.execute
deal with execution. Both configure the command line, in direct and indirect ways respectively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SecurityNodeActionExtension.execute
could fetch an enclave and extend the command line w/o the extra step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just fetching an enclave, it's doing stuff that has side effects. In the case of the security extension, it's actually creating and using files on disc. There's also a need to add an option to the ros2 launch
command line for the user to use, which is why there is the separate command_extension
method. The function documentation for that was wrong; I've fixed it in c2b2b7f.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm a bit confused. From what I can see in this patch, NodeActionExtension.command_extension
is not used by ros2 launch
directly, but within Node._perform_substitutions
to extend Node.cmd
. In which case, my point is that both NodeActionExtension.command
and NodeActionExtension.pre_execute
happen on Action.execute
. There's no configuration stage in between. An indirection through context locals would make sense if there was a context global to override (or not).
Anyhow, I won't block on this. But we do need that documentation sorted out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I was the one confused. Sorry about that. I was getting it mixed up with the ros2 launch
command line flag adding, which is handled by the options extension.
I looked over the code and I agree that it is possible to combine the two functions. I can make an argument against combining them that the current way follows the structure of the Node
action in that it partitions preparing the command line from preparing ROS-specific arguments and executing the command, but it's a weak argument. So I went ahead and changed it to match your suggestion in da09c67.
@@ -432,3 +481,86 @@ def expanded_node_namespace(self): | |||
def expanded_remapping_rules(self): | |||
"""Getter for expanded_remappings.""" | |||
return self.__expanded_remappings | |||
|
|||
def _get_extensions(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs nit: consider making this a free function or (better not, too much support logic inside the staticmethod
Node
class) that returns the list of extensions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function accesses the node's logger to output an error message that is specific to the function's internal implementation. I could refactor that out into returning an optional error message, but I'm wary of going to far for something that's meant to be temporary. How strongly do you feel about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How strongly do you feel about this?
It's a bit too much unrelated code. Also, as far as I can see the logger is fixed for the entire module. It should be straightforward to fetch it again if need be.
for something that's meant to be temporary.
Until we get around to replace it, it is permanent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the two functions free in 0d9cb67.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Until we get around to replace it, it is permanent
Fair point! :)
"supports '%s'" % (extension_point_version, extension_version)) | ||
|
||
|
||
def get_upper_bound_caret_version(version): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs consider moving these functions to another file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 6754e9a.
Also, this patch needs conflicts resolved. |
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
22d14af
to
24d5bbe
Compare
Rebased to fix this. I'll start addressing your other comments now. |
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
@hidmic Thanks for the comments. This is ready for another pass. |
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM but for a few comments.
Once we settle those we need CI.
@@ -432,3 +481,86 @@ def expanded_node_namespace(self): | |||
def expanded_remapping_rules(self): | |||
"""Getter for expanded_remappings.""" | |||
return self.__expanded_remappings | |||
|
|||
def _get_extensions(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How strongly do you feel about this?
It's a bit too much unrelated code. Also, as far as I can see the logger is fixed for the entire module. It should be straightforward to fetch it again if need be.
for something that's meant to be temporary.
Until we get around to replace it, it is permanent.
@@ -408,6 +453,10 @@ def execute(self, context: LaunchContext) -> Optional[List[Action]]: | |||
ros_specific_arguments['name'] = '__node:={}'.format(self.__expanded_node_name) | |||
if self.__expanded_node_namespace != '': | |||
ros_specific_arguments['ns'] = '__ns:={}'.format(self.__expanded_node_namespace) | |||
node_info = NodeActionExtension.NodeInfo(self.__package, self.__node_executable, self.node_name) | |||
for extension in self.__extensions.values(): | |||
ros_specific_arguments = extension.execute(context, ros_specific_arguments, node_info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I will point out that this data in particular (e.g. the package name) could be exposed via public API. You wouldn't have to access internal implementations details from the extension in that case.
@@ -342,6 +383,10 @@ def _perform_substitutions(self, context: LaunchContext) -> None: | |||
raise | |||
self.__final_node_name = prefix_namespace( | |||
self.__expanded_node_namespace, self.__expanded_node_name) | |||
|
|||
for extension in self.__extensions.values(): | |||
self.cmd.extend(extension.command_extension(context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, neither NodeExtension.command_extension
nor NodeExtension.execute
deal with execution. Both configure the command line, in direct and indirect ways respectively.
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
@@ -342,6 +383,10 @@ def _perform_substitutions(self, context: LaunchContext) -> None: | |||
raise | |||
self.__final_node_name = prefix_namespace( | |||
self.__expanded_node_namespace, self.__expanded_node_name) | |||
|
|||
for extension in self.__extensions.values(): | |||
self.cmd.extend(extension.command_extension(context)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm a bit confused. From what I can see in this patch, NodeActionExtension.command_extension
is not used by ros2 launch
directly, but within Node._perform_substitutions
to extend Node.cmd
. In which case, my point is that both NodeActionExtension.command
and NodeActionExtension.pre_execute
happen on Action.execute
. There's no configuration stage in between. An indirection through context locals would make sense if there was a context global to override (or not).
Anyhow, I won't block on this. But we do need that documentation sorted out.
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbiggs |
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
@hidmic I fixed the two flake8 errors I could see in the output. |
Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>
I fixed a few more flake8 fixes that I saw locally (and in CI). However, I am still seeing this warning:
I think what is happening here is that pytest is getting confused because the class starts with the name |
For reference setting the class atrtibute |
Sorry about that. That's a class I was using to do some quick tests. I thought I had deleted it, turns out I had committed it instead by mistake. I've pushed the removal of them. |
Signed-off-by: Geoffrey Biggs <gbiggs@killbots.net>
@clalancette shall we run a combined CI with osrf/ros2launch_security#1? |
Good idea, I'll go ahead and do that over on that pull request. |
This PR is a response to #180. In that PR, a request was made that extensibility be added to the launch system and the
--secure
option be added as an extension. This PR provides the extensibility support. The security extension is provided in a PR on a new repository.It adds:
ros2 launch
command. For example, an extension can add an option--secure
. Aside from getting the chance to add as many options as desired to the command line flags, extensions are given the chance to execute code prior to theLaunchService
starting, prior to the launch being executed, and after the launch process completes (i.e. on shutdown?).launch
to be more amenable to extensibility.For an example, see this PR which adds a security extension.
Once both PRs are accepted, the ros2launch_security repository will need to be transferred to the security WG.