Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions articles/153_roslaunch_remote_launch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Multi-Machine and Remote Launching
## Goals
- Connect to a remote host and run nodes on it
- Ensure this capability does not compromise security

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We're currently working on launch integration for security. In order to make sure it scales to remote launching, we might want to consider ways to send security files over to the remote machine.

- Support arbitrary remote execution methods
- Monitor the status of and manage lifecycles of nodes across systems
- Shutdown nodes remotely
- Ability to detach the machine used to launch a system and have the system continue running
- Be able to reconnect to the system to monitor and manage nodes running remotely


To achieve our goals, this update will consist of two primary components: an ExecuteRemote action, and a LaunchServiceNode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Based on the re-scope of this design - it seems like this "Goals" section shouldn't be talking about the LaunchServiceNode since it's not specified within this design. If I remember correctly, we established that the LaunchServiceNode was independently interesting and designable. I do see that we want to talk about how these two things will interact, but I'm not sure if that belongs here in this doc anymore.


The ExecuteRemote action will build off of the refactor he is working on described here: https://github.com/ros2/design/pull/272
Since we don't want to limit remote execution to a single protocol, ExecuteRemote will be a base class and we will create an implementation using SSH which we will call ExecuteRemoteSSH.
Remote machines will be described by a Machine class which will also be a base class for protocol-specific implementations.
Together these components will provide the means to execute processes remotely and should cover most basic use cases.

The LaunchServiceNode(s) will expose ROS2 services and topics for handling startup and shutdown of processes on remote machines.

@emersonknapp emersonknapp Sep 4, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This LaunchServiceNode could be designed as an independent component - that is possible to use purely locally to detach a launch command from an ongoing launch context - it could then be used by the remote launching functionality.

This component won't be necessary for basic execution of remote nodes, but would allow remote system to persist beyond the life of the initial LaunchService.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am not clear on this use case, could you elaborate a little bit? especially on the difference from LifecycleNode except process statistics?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The design is intended to allow you to run a node pretty much like they currently would run, that is, bound to the lifespan of the launch process itself. When the launch process dies, the SSH connection dies, and anything still running on that SSH terminal dies with it.

The LaunchServiceNode is intended to work a little bit differently. It would be launched from the SSH terminal, but then detach and run without requiring the SSH connection to remain up. However, by doing that you lose the ability to directly kill a process if you actually want it to stop. Therefore, the LaunchServiceNode would provide a way to go back and ask for particular nodes on that server to be started/stopped ad hoc.

This is certainly somewhat similar to some of the functionality envisioned by LifecycleNodes, but would allow a way for us to remotely launch and manage nodes which were not natively designed to support the lifecycle feature. Lifecycle would provide nearly all the functionality we would need for this type of behavior, but should the node crash, Lifecycle won't let us bring it back for a full restart, and we also would be lacking other out-of-band control to deal with things such as unresponsive nodes.

(Obviously, these same arguments also apply to the LaunchServiceNode itself, so that particular entity should be as clean as possible to reduce the possibility of it getting into an unmanageable state itself.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question to explicitly state in the doc: Who starts the LaunchServiceNode? How is its lifecycle expected to be managed?

The topics and services will allow remote tools to view and manage the processes running remotely.
To prevent execution of completely arbitrary processes, the node will be given a list of commands it is allowed to run at startup, each with a unique ID assigned by the user.
Requests to start or stop processes would then use the unique ID to indicate to the node which process to start or stop.


## New Classes

#### launch.descriptions.Machine
##### Members
Members would be defined by child classes, since different protocols will require different types of information

#### launch.descriptions.SSHMachine
Contains information needed to run a process on another machine
##### Members
| Name | Type | Description |
|---|---|---|
| hostname | String | Hostname of the machine. |
| port | int | Port on to connect to (default 22) |
| ssh\_keys | String | Path to ssh key file |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should this be singular?

Suggested change
| ssh\_keys | String | Path to ssh key file |
| ssh\_key | String | Path to ssh key file |

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

good catch

| passphrase | String | Optional passphrase if the key uses one. |

#### launch.actions.ExecuteRemote
Base class for actions that use connection informationn contained in `machine` to start a process on a remote machine.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

typo: informationn

##### Members
| Name | Type | Description |
|---|---|---|
| process\_description |`launch.descriptions.Executable` | An object describing the process to be executed.|
| shell | boolean | If True, a shell is used to execute the process.|
| sigterm\_timeout | List[Substitution] | Time until shutdown should escalate to `SIGTERM`, as a string or a list of strings and `Substitution`s to be resolved at runtime, defaults to the `LaunchConfiguration` called `sigterm_timeout` |
| sigkill\_timeout | List[Substitution] | Time until escalating to `SIGKILL` after `SIGTERM`, as a string or a list of strings and `Substitution`s to be resolved at runtime, defaults to the `LaunchConfiguration` called `sigkill_timeout` |
| emulate\_tty | boolean | Emulate a tty (terminal), defaults to `False`, but can be overridden with the `LaunchConfiguration` called `emulate_tty`, the value of which is evaluated as true or false according to `evaluate_condition_expression`. |
| prefix | List[Substitution] | A set of commands/arguments to preceed the `cmd`, used for things like `gdb`/`valgrind` and defaults to the `LaunchConfiguration` called `launch-prefix` |
| output | ?? | Configuration for process output logging. Defaults to `log` i.e. log both `stdout` and `stderr` to launch main log file and stderr to the screen. |
| output\_format | ?? | For logging each output line, supporting `str.format()` substitutions with the following keys in scope: `line` to reference the raw output line and `this` to reference this action instance. |
| log\_cmd | boolean | If `True`, prints the final cmd before executing the process, which is useful for debugging when substitutions are involved. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe should have a note that this will avoid printing any key passphrases or other sensitive information

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Oh, good point, I will include a note about that.

| on\_exit | List[LaunchDescriptionEntity] | List of actions to execute upon process exit.|
| persistent\_connection | boolean | Whether the LaunchService should maintain a persistent connection to the ssh instance |
| machine | `launch.descriptions.SSHMachine` | The machine on which to execute the process |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Want this to take the abstract interface, right?

Suggested change
| machine | `launch.descriptions.SSHMachine` | The machine on which to execute the process |
| machine | `launch.descriptions.Machine` | The machine on which to execute the process |

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You are correct, thanks

##### Methods
| Name | Type | Description |
| ------------------ | --- | ------------------------------------------------------------ |
| get\_sub\_entities | List[LaunchDescriptionEntity] | Override. If on\_exit was provided in the constructor, returns that; otherwise returns an empty list. |
| execute | Optional[List[LaunchDescriptionEntity]] | Override. Establishes event handlers for process execution, passes the execution context to the process definition for substitution expansion, then uses `osrf_pycommon.process_utils.async_execute_process` to launch the defined process. |
| get\_asyncio\_future | Optional[asyncio.Future] | Override. Return an asyncio Future, used to let the launch system know when we're done. |


#### launch.actions.ExecuteRemoteSSH
Inherits from `launch.actions.ExecuteRemote` and manages the ssh connection
##### Members
Inherited
##### Methods
| Name | Type | Description |
|---|---|---|
| open\_ssh\_connection | SSHConnection | Object for interacting with the SSH connection |
| exec\_remote\_command | void | Executes a command on the remote machine

#### launch.substitutions.RemoteSubstitution
Uses a connection to a machine to run a command remotely and retuns the result as a string
##### Members
##### Constructor
| Argument | Type | Description |
|---|---|---|
| command | Text | The command to run on the remote machine |
| machine | `launch.descriptions.Machine` | An object decribing the machine that the command should be run on. |
##### Methods
| Name | Type | Description |
|---|---|---|
| describe | Text | Override method. Returns a description of this substitution as a string. |
| perform | Text | Override method. Perform the substitution, given the launch context, and return it as a string. |


## ROS

### Nodes
#### launch\_ros.LaunchServiceNode
This node runs on each machine in a system is is repsonsible for launching and managing remote processes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nitpick:

Suggested change
This node runs on each machine in a system is is repsonsible for launching and managing remote processes.
This node runs on each machine in a system is is responsible for launching and managing remote processes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: This node runs on each machine in a system is it is

#### Params
| Name | Type | Description |
|---|---|---|
| allowable\_processes | List[Executable] | List of executables the node is allowed to launch |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Explicit flag to allow remote launching any process/node? Early in development this could be super useful for testing out various nodes via remote context from developer machine to robot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or - is it not worth having this allow-list in here at all. Maybe SROS2 takes care of permissions well enough that we don't need to reimplement it. "If you use this you are exposing a vulnerability if you don't use SROS"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps, only allow the LaunchServiceNode to only start known ros nodes on the remote system, rather than arbitrary commands. Restricting to ROS-specific in the remote context, since the local launch can execute arbitrary SSH commands anyways

##### Topics
###### Sent
- heartbeat
###### Received
##### Services
- query\_status
- start\_process
- stop\_process
- shutdown

### Messages
#### Heartbeat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do all LaunchServiceNodes use a unique topic, or do all publish on the same topic (in which case would need to put machine/service ID in the messages)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this should be configurable, "heartbeat_topic", "process_status_topic"

Contains basic status information and enough information for launch related tools to find the node.
DDS might already provide enough info making this superfluous.
##### Fields
| Name | Type | Description |
|---|---|---|
| timestamp | `builtin_interface.Timestamp` | Time at which heartbeat message was sent |
| hostname | String | Hostname of machine sending the status |
| num\_procs | int | Number of processes currently being tracked by the LaunchServiceNode. |

#### ProcessStatus
##### Fields
| Name | Type | Description |
|---|---|---|
| timestamp | `builtin_interface.Timestamp` | Time at which message was sent |
| process\_name | String | Name of the process |
| process\_id | int | A unique numerical identifier for the process. This value is generated by the launch system and should not be expected to be the same as the proc id on the machine. |
| process\_status | int | An enumerated status value. STOPPED=0; RUNNING=1; SUSPENDED=2; |
| mem\_usage | double | Percent of memory used by the process |
| processor\_load | double | Percent of processor load coming from this process |
| err\_msg | String | Error string |

### Services

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Noting that each LaunchServiceNode will need to namespace itself to make service calls uniquely-fulfillable


#### QueryStatus
Service to be called by launch tools to get detailed information about the processes being managed by the LaunchServiceNode
##### Request
| Name | Type | Description |
|---|---|---|
Empty since no info is needed to answer the request.
##### Response
| Name | Type | Description |
|---|---|---|
| timestamp | `builtin_interface.Timestamp` | Time at which message was sent |
| hostname | String | Hostname of machine sending the status |
| total\_mem\_usage | double | Percent of memory used |
| total\_processor\_load | double | processor load as a percent |
| num\_procs | int | Number of processes being managed by the node |
| processes | List[ProcessStatus] | List of ProcessStatus objects |

#### StartProcess
##### Request
| Name | Type | Description |
|---|---|---|
| cmd | String | The command to be run when starting the process. |
| name | String | The name to use for the process. |
| uid | int | A unique numerical id for use within the launch system. |
| cwd | String | The directory from which to run the process on the remote machine. |
| env | List[common\_interfaces.KeyValue] | Dictionary of environment variables to be used from a clean environment. |
##### Response
| Name | Type | Description |
|---|---|---|
| success | boolean | Whether the process was sucessfully started or not |
| status | ProcessStatus | Status of the process |

#### StopProcess
##### Request
| Name | Type | Description |
|---|---|---|
| proc\_id | int | The launch id of the process that should be stopped |
##### Response
| Name | Type | Description |
|---|---|---|
| success | boolean | Whether the process was sucessfully stopped or not |
| status | ProcessStatus | Status of the process |

#### Shutdown
##### Request
Empty message
##### Response
| Name | Type | Description |
|---|---|---|
| success | boolean | Whether the process was sucessfully stopped or not |
| status | ProcessStatus | Status of the process |