This document outlines what changes were made to switch from upstart to near-equivalent behavior with systemd for Fetch research robots. Useful links are at the end!
First we look at the fairly simple robot drivers upstart file's conversion from robot.conf to robot.service. The original (distributed with the fetch-system-config debian):
env ROS_LOG_DIR=/var/log/ros
start on roscore_is_up
stop on roscore_is_down
# Enable drivers + UKF filter to avoid swapping and run at a higher priority
limit memlock unlimited unlimited
limit nice 35 35
respawn
script
exec su ros -c ". /opt/ros/indigo/setup.bash && roslaunch /etc/ros/indigo/robot.launch --wait"
end script
Jumping straight into conversions:
-env ROS_LOG_DIR=/var/log/ros
+[Service]
+Environment="ROS_LOG_DIR=/var/log/ros"
Use Environment="XXX=abc"
in the Service section.
-start on roscore_is_up
-stop on roscore_is_down
+[Unit]
+After=roscore.service
+BindsTo=roscore.service
+
+[Install]
+WantedBy=roscore.service
There's several systemd keywords we want here. In the Unit and Install sections we want
After=roscore
so that it starts once roscore is up.BindsTo=roscore
so that it stops with roscore. See man page here.WantedBy=roscore.service
(might be replaced by BindsTo)
-respawn
+[Service]
+Restart=on-failure
We just need Restart=on-failure
in the Service section.
-script
- exec su ros -c ". /opt/ros/melodic/setup.bash && roslaunch /etc/ros/melodic/robot.launch --wait"
-end script
+User=ros
+ExecStart="/opt/ros/melodic/setup.bash && roslaunch /etc/ros/melodic/robot.launch --wait"
ExecStart=/bin/blah
will run stuff. Then, we can specify the user via the
User=
in the Service section. What about the behvior of exec?
We also need to change roslaunch to an absolute path (requirement of systemd),
in this case /opt/ros/melodic/bin/roslaunch
Now we go over the changes for the more complex roscore.init->roscore.service. The full upstart file was:
env ROS_LOG_DIR=/var/log/ros
start on (filesystem and net-device-up IFACE!=lo)
stop on runlevel [016]
respawn
pre-start script
mkdir -p /var/log/ros/
chown ros:ros /var/log/ros/
touch /var/run/roscore.pid
chmod 644 /var/run/roscore.pid
chown ros:ros /var/run/roscore.pid
end script
# Wait for roscore to actually initialize
post-start script
echo "waiting for roscore to come up"
. /opt/ros/indigo/setup.sh
ret=`rosnode list`
while [ "$ret" = '' ]
do
ret=`rosnode list`
sleep 1;
done
initctl emit roscore_is_up
end script
script
exec su ros -c ". /opt/ros/indigo/setup.bash && roscore"
end script
pre-stop script
initctl emit roscore_is_down
end script
-env ROS_LOG_DIR=/var/log/ros
+[Service]
+Environment="ROS_LOG_DIR=/var/log/ros"
Again use Environment="XXX=abc"
in the Service section.
-start on (filesystem and net-device-up IFACE!=lo)
+[Unit]
+After=multi-user.target
+
+[Install]
+WantedBy=multi-user.target
Waiting for the system to be up is probably satisfied by using
some keywords with multi-user.target
. It's possible this isn't 100%
robust, however.
-stop on runlevel [016]
The stop on runlevel [016]
is an old-ish trick to ensure roscore shuts down
fast enough whent he system is being shutdown.
For more, see:
-respawn
+[Service]
+Restart=on-failure
As before, in the Service section, we want Restart=on-failure
-pre-start script
- mkdir -p /var/log/ros/
- chown ros:ros /var/log/ros/
-
- touch /var/run/roscore.pid
- chmod 644 /var/run/roscore.pid
- chown ros:ros /var/run/roscore.pid
-end script
+ExecStartPre=/bin/bash /opt/ros/roscore_prestart.bash
This block is very script-like, so we opt to follow the systemd recommendation
and make this a separate script in /opt/ros/ that gets invoked with the ExecStartPre
command in the Service section. But we also comment out the last three
lines of the script, as they appear to be uneeded. It is possible however that
we ought to be using Type=forking
for our processes, and if so, these three
lines would probably be replaced by using
RuntimeDirectory=
and PIDFile=
-# Wait for roscore to actually initialize
-post-start script
- echo "waiting for roscore to come up"
- . /opt/ros/melodic/setup.sh
- ret=`rosnode list`
- while [ "$ret" = '' ]
- do
- ret=`rosnode list`
- sleep 1;
- done
- initctl emit roscore_is_up
-end script
+ExecStartPost=/bin/bash /opt/ros/roscore_poststart.bash
This block is very script-like, so we again make this a separate script in
/opt/ros/ that gets invoked with the ExecStartPost
command in the Service
section.
-script
- exec su ros -c ". /opt/ros/melodic/setup.bash && roscore"
-end script
+ExecStart=/bin/bash -c ". /opt/ros/melodic/setup.bash && roscore"
And a simple ExecStart line again.
-pre-stop script
- initctl emit roscore_is_down
-end script
We don't need to reproduce this section as we don't use signals.
Trivia:
- Currently unsure whether we need
Type=forking
for either of these. - At the time of this writing, 18.04 uses systemd version 237
The end results:
Roscore (roscore.service
):
[Unit]
Description=Job that launches roscore once the system has started
After=multi-user.target
[Install]
WantedBy=multi-user.target
[Service]
Environment="ROS_LOG_DIR=/var/log/ros"
Restart=on-failure
ExecStartPre=/bin/bash /opt/ros/roscore_prestart.bash
ExecStartPost=/bin/bash /opt/ros/roscore_poststart.bash
User=ros
ExecStart=/bin/bash -c ". /opt/ros/melodic/setup.bash && roscore"
Robot drivers (robot.service
):
[Unit]
Description=Job that launches the robot drivers once roscore has started
After=roscore.service
BindsTo=roscore.service
[Install]
WantedBy=roscore.service
[Service]
Environment="ROS_LOG_DIR=/var/log/ros"
Restart=on-failure
User=ros
ExecStart=/bin/bash -c ". /opt/ros/melodic/setup.bash && roslaunch /etc/ros/melodic/robot.launch --wait"
And we add them and make them work with:
# on the robot, assuming melodic debians exist
sudo cp roscore.service robot.service /etc/systemd/system/
sudo cp roscore_prestart.bash roscore/poststart.bash /etc/ros/
sudo systemctl daemon-reload
sudo systemctl enable roscore.service
sudo systemctl enable robot.service
# And the Frankenstein moment!
sudo systemctl start roscore
sudo systemctl list-units --type service -a | grep roscore # Ugh need simpler command
Other:
- We can validate unit/service files via e.g.
system-analyzer verify /etc/systemd/system/robot.service
and find syntax errors.
Useful links:
- Ubuntu guide to upstart->systemd: https://wiki.ubuntu.com/SystemdForUpstartUsers
- Systemd man page for "units": https://www.freedesktop.org/software/systemd/man/systemd.unit.html
- Systemd man page for "services": https://www.freedesktop.org/software/systemd/man/systemd.service.html
Looks good to me although I'd like to see how it runs as the real proof.
Any changes I would make would be more cosmetic or personal style than functional.