Skip to content

Instantly share code, notes, and snippets.

@erelson
Last active April 17, 2019 21:55
Show Gist options
  • Save erelson/7a1e88e3f87e6aa61bba4ef4220e06d3 to your computer and use it in GitHub Desktop.
Save erelson/7a1e88e3f87e6aa61bba4ef4220e06d3 to your computer and use it in GitHub Desktop.
Upstart to Systemd for Fetch research robot

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:

@jdilellio
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment