LINUX.ORG.RU
решено ФорумAdmin

Запустить юнит при условии, что вспомогательная программа вернула 0

 


0

4

В юнитах systemd есть возможность задать условия, при невыполнении которых юнит не будет запускаться (например, ConditionFileExists). Мне же нужно, чтобы выполнилась вспомогательная программа и юнит стартовал, только если вспомогательная программа завершилась успешно (с кодом 0). Судя по man systemd.unit, для этого готового Condition'а не существует.

Можно, конечно, записать эту вспомогательную программу в ExecStartPre, но тогда эффект не тот, который хотелось бы: хочется, чтобы при невыполнении условия юнит просто не стартовал, а в способе через ExecStartPre юнит будет помечен как failed.

Есть ли какой-то минимально костыльный способ добиться того, что мне нужно?

твой скрипт при запуске удаляет и создаёт файл при корректном завершении (по коду 0) и корми его в ConditionFileExists - это слишком просто ?

dhampire ★★★ ()

cast intelfx же!

УПД: хотя, пардон, его же кастанёт тегом.

Deleted ()
Последнее исправление: ecko (всего исправлений: 1)
Ответ на: комментарий от Deleted

ЕМНИП, оно так не умеет, и надо писать шелл-прослойку.

Можно еще в список рассылки при желании написать :)

intelfx ★★★★★ ()
Ответ на: комментарий от dhampire

твой скрипт при запуске удаляет и создаёт файл при корректном завершении (по коду 0) и корми его в ConditionFileExists - это слишком просто ?

Я об этом думал, но не уверен, что это сработает. Например, если я пропишу вспомогательный скрипт как зависимость основного юнита, то systemd сначала обработает основной юнит, решит, нужно ли его запускать (удовлетворяются ли условия), а потом начнёт выполнять вспомогательный скрипт, и он его результата выполнения уже ничего не будет зависеть, потому что условие существования файла уже проверено. Это, конечно, надо проверить, но я думаю, что будет так, т.е. это вообще не сработает.

gentoo_root ★★★★★ ()
Ответ на: комментарий от intelfx

надо писать шелл-прослойку.

Об этом я тоже думал, чтобы из юнита запускать скрипт, он делал бы все проверки, а потом exec'ал бы нужный демон, но какое-то это некрасивое решение.

ЕМНИП, оно так не умеет

Я даже удивился, почему нет какого-нибудь ConditionExitSuccess, казалось бы, эта фича должна быть востребована.

Можно еще в список рассылки при желании написать :)

Похоже, что нужно таки писать ради добавления ConditionExitSuccess, может, даже патчик выкачу, если у меня будет желание заняться этим.

gentoo_root ★★★★★ ()
Ответ на: комментарий от gentoo_root

по man

ConditionArchitecture=, ConditionVirtualization=, ConditionHost=, ConditionKernelCommandLine=, ConditionSecurity=, ConditionCapability=, ConditionACPower=,
       ConditionNeedsUpdate=, ConditionFirstBoot=, ConditionPathExists=, ConditionPathExistsGlob=, ConditionPathIsDirectory=, ConditionPathIsSymbolicLink=,
       ConditionPathIsMountPoint=, ConditionPathIsReadWrite=, ConditionDirectoryNotEmpty=, ConditionFileNotEmpty=, ConditionFileIsExecutable=, ConditionNull=
           Before starting a unit verify that the specified condition is true. If it is not true, the starting of the unit will be skipped, however all ordering
           dependencies of it are still respected. A failing condition will not result in the unit being moved into a failure state. The condition is checked at the
           time the queued start job is to be executed.
Condition проверяется до постановки сервиса в очередь и построения графа его зависомостей. печаль.

dhampire ★★★ ()
Ответ на: комментарий от gentoo_root

Насколько мне известно, Condition* проверяются непосредственно перед активацией юнита, так что может и сработать. Главное — написать Type=oneshot и RemainAfterExit=false у вспомогательного юнита. Но костыль лютый, да.

intelfx ★★★★★ ()
Ответ на: комментарий от dhampire

Почему «до построения графа его зависимостей»? Вроде ведь как раз наоборот.

intelfx ★★★★★ ()
Ответ на: комментарий от intelfx

main.service

cat /etc/systemd/system/main.service 
[Unit]
Description=Test main Service
After=sub.service
ConditionPathExists=/tmp/test/file

[Service]
Type=oneshot
ExecStart=/tmp/main.sh

[Install]
WantedBy=multi-user.target
cat /tmp/main.sh 
#!/bin/sh

[[ -e /tmp/test/file ]] && echo true
exit 0;
sub.service
cat /etc/systemd/system/sub.service 
[Unit]
Description=Test sub Service

[Service]
Type=oneshot
ExecStart=/tmp/sub.sh
RemainAfterExit=false

[Install]
WantedBy=multi-user.target
sub.sh
cat /tmp/sub.sh 
#!/bin/sh
echo 1
[[ -e /tmp/test/file ]] && rm -f /tmp/test/file
echo 2
mkdir -p /tmp/test/
echo 3
touch /tmp/test/file
echo 4
exit 0;

а теперь пробуем взлететь и видим что без существования Condition main сервис не запускается и в том числе не запускает sub, но если зупастить sub руками и после main, то main стартанёт.

~|⇒ date
Вс авг 31 10:32:27 MSK 2014
~|⇒ s systemctl start main
~|⇒ s systemctl status main
● main.service - Test main Service
   Loaded: loaded (/etc/systemd/system/main.service; disabled)
   Active: inactive (dead)

авг 31 00:17:52 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 00:20:21 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:29:34 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:29:39 ideapadz400-arch systemd[1]: Starting Test main Service...
авг 31 10:29:39 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:29:39 ideapadz400-arch main.sh[21307]: true
авг 31 10:30:00 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:30:21 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:32:24 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:32:40 ideapadz400-arch systemd[1]: Started Test main Service.
~|⇒ s systemctl start sub  
~|⇒ s systemctl start main
~|⇒ s systemctl status main sub
● main.service - Test main Service
   Loaded: loaded (/etc/systemd/system/main.service; disabled)
   Active: inactive (dead)

авг 31 10:29:39 ideapadz400-arch systemd[1]: Starting Test main Service...
авг 31 10:29:39 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:29:39 ideapadz400-arch main.sh[21307]: true
авг 31 10:30:00 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:30:21 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:32:24 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:32:40 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:32:59 ideapadz400-arch systemd[1]: Starting Test main Service...
авг 31 10:32:59 ideapadz400-arch main.sh[21406]: true
авг 31 10:32:59 ideapadz400-arch systemd[1]: Started Test main Service.

● sub.service - Test sub Service
   Loaded: loaded (/etc/systemd/system/sub.service; disabled)
   Active: inactive (dead)

авг 31 10:24:22 ideapadz400-arch sub.sh[21181]: 4
авг 31 10:24:22 ideapadz400-arch systemd[1]: Started Test sub Service.
авг 31 10:29:34 ideapadz400-arch systemd[1]: Stopped Test sub Service.
авг 31 10:32:24 ideapadz400-arch systemd[1]: Stopped Test sub Service.
авг 31 10:32:57 ideapadz400-arch systemd[1]: Starting Test sub Service...
авг 31 10:32:57 ideapadz400-arch sub.sh[21395]: 1
авг 31 10:32:57 ideapadz400-arch sub.sh[21395]: 2
авг 31 10:32:57 ideapadz400-arch sub.sh[21395]: 3
авг 31 10:32:57 ideapadz400-arch sub.sh[21395]: 4
авг 31 10:32:57 ideapadz400-arch systemd[1]: Started Test sub Service.
меняем дописываем RemainAfterExit=true в оба юнита
~|⇒ date 
Вс авг 31 10:41:59 MSK 2014
~|⇒ s systemctl start main
~|⇒ s systemctl status main
● main.service - Test main Service
   Loaded: loaded (/etc/systemd/system/main.service; disabled)
   Active: inactive (dead)

авг 31 10:39:39 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:39:42 ideapadz400-arch systemd[1]: Stopping Test main Service...
авг 31 10:39:42 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:39:55 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:40:26 ideapadz400-arch systemd[1]: Starting Test main Service...
авг 31 10:40:26 ideapadz400-arch main.sh[21580]: true
авг 31 10:40:26 ideapadz400-arch systemd[1]: Started Test main Service.
авг 31 10:41:43 ideapadz400-arch systemd[1]: Stopping Test main Service...
авг 31 10:41:43 ideapadz400-arch systemd[1]: Stopped Test main Service.
авг 31 10:42:09 ideapadz400-arch systemd[1]: Started Test main Service.
~|⇒ s systemctl start sub  
~|⇒ s systemctl status sub
● sub.service - Test sub Service
   Loaded: loaded (/etc/systemd/system/sub.service; disabled)
   Active: active (exited) since Вс 2014-08-31 10:42:25 MSK; 4s ago
  Process: 21649 ExecStart=/tmp/sub.sh (code=exited, status=0/SUCCESS)
 Main PID: 21649 (code=exited, status=0/SUCCESS)

авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 1
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 2
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 3
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 4
авг 31 10:42:25 ideapadz400-arch systemd[1]: Started Test sub Service.
~|⇒ s systemctl start main
~|⇒ s systemctl status main sub
● main.service - Test main Service
   Loaded: loaded (/etc/systemd/system/main.service; disabled)
   Active: active (exited) since Вс 2014-08-31 10:42:40 MSK; 5s ago
  Process: 21666 ExecStart=/tmp/main.sh (code=exited, status=0/SUCCESS)
 Main PID: 21666 (code=exited, status=0/SUCCESS)

авг 31 10:42:40 ideapadz400-arch main.sh[21666]: true
авг 31 10:42:40 ideapadz400-arch systemd[1]: Started Test main Service.

● sub.service - Test sub Service
   Loaded: loaded (/etc/systemd/system/sub.service; disabled)
   Active: active (exited) since Вс 2014-08-31 10:42:25 MSK; 20s ago
  Process: 21649 ExecStart=/tmp/sub.sh (code=exited, status=0/SUCCESS)
 Main PID: 21649 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/sub.service

авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 1
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 2
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 3
авг 31 10:42:25 ideapadz400-arch sub.sh[21649]: 4
авг 31 10:42:25 ideapadz400-arch systemd[1]: Started Test sub Service.
стало понятнее что происходит, но суть не поменялась.

dhampire ★★★ ()
Ответ на: комментарий от dhampire

так у тебя в main.service нет Wants=sub.service, только After=... или я чего-то недопонял?

intelfx ★★★★★ ()
Ответ на: комментарий от dhampire

Действительно, проверил сейчас, это работает. Хорошо, что условия проверяются уже непосредственно перед самим запуском.

Я немного изменил файлы юнитов:

telnetd.service:

[Unit]
Description=Emergency telnetd
After=telnetd-check.service
ConditionPathExists=/run/start-telnetd

[Service]
ExecStart=/usr/sbin/telnetd -F -l /bin/sh

[Install]
WantedBy=multi-user.target
Also=telnetd-check.service

telnetd-check.service:

[Unit]
Description=Emergency telnetd check
PartOf=telnetd.service

[Service]
Type=oneshot
ExecStart=/usr/bin/telnetd-check
RemainAfterExit=true

[Install]
WantedBy=telnetd.service

/usr/bin/telnetd-check:

#!/bin/sh

rm -f /run/start-telnetd
touch /run/start-telnetd

Таким образом, получается, что при перезапуске telnetd.service проверка будет выполнена ещё раз.

intelfx, я надеюсь, я не ерунду тут написал?

gentoo_root ★★★★★ ()
Ответ на: комментарий от intelfx

так у тебя в main.service нет Wants=sub.service, только After=... или я чего-то недопонял?

нет, я привёл юниты как есть, если Wants=sub.service заставит это работать то прекрасно.

dhampire ★★★ ()
Ответ на: комментарий от dhampire

заставит это работать

Даже в первоначальном варианте это почти работало, если сделать enable обоим сервисам и просто загрузиться.

В моём варианте я делаю enable для telnetd.service, автоматически enable'ится и telnetd-check.service (при этом и появляется зависимость telnetd.service wants telnetd-check.service), тогда всё работает правильно и при загрузке, и при stop/start telnetd.service во время работы (при каждом start условие проверяется заново).

gentoo_root ★★★★★ ()
Ответ на: комментарий от gentoo_root

Пардон, у меня тут не было особой возможности ответить. Но можно же легче...

test.service:

[Unit]
Description=Test service
Wants=test-sub.service
After=test-sub.service
ConditionPathExists=/tmp/trigger

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/tmp/test.sh

test-sub.service:

[Unit]
Description=Condition service for testing

[Service]
Type=oneshot
RemainAfterExit=false
ExecStart=/bin/rm -f /tmp/trigger
ExecStart=/tmp/trigger.sh

  • /tmp/test.sh — полезная нагрузка
  • /tmp/trigger.sh — скрипт, который создаёт /tmp/trigger при необходимости
intelfx ★★★★★ ()
Ответ на: комментарий от intelfx

Но так или иначе это костылинг. В рассылку написать стоит...

Хотя я предполагаю реакцию — никто, мол, не знает, какая должна быть семантика у этого вспомогательного процесса (его ждать? или не ждать? или убивать через N секунд? он форкается? или не форкается? как рассматривать факт его падения? кто-нибудь ещё попросит аналог SuccessExitStatus=) и в итоге окажется, что костыль, который мы с тобой придумали, объявят окончательным решением.

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от intelfx

Точно, спасибо, так тоже работает.

его ждать? или не ждать? или убивать через N секунд? он форкается? или не форкается? как рассматривать факт его падения?

Ну тут, мне кажется, вариант один — запустить и ждать, пока он не завершится с кодом 0 или не 0. Падение — это завершение с ненулевым кодом. Но проблем тут действительно целая куча:

  • Что делать, если он насоздаёт кучу дочерних процессов, которые не завершатся к моменту завершения нашего процесса? За ними надо следить так же, как systemd следит за сервисами.
  • Насколько долго он может работать?
  • Ещё systemd активно распихивает процессы по cgroups и, наверное, ещё и рулит неймспейсами, я не шарю в этом всём, но думаю, что будут какие-то проблемы, связанные с тем, в каком контексте запускать вспомогательный процесс (хотя в этом я не уверен).

С другой стороны, есть же udev, который тоже может порождать всякие левые процессы, там эти проблемы как-то решили (например, есть таймауты, после которых процесс убивается).

В общем, если найду время, то в рассылку напишу, но пока что работающее решение есть.

gentoo_root ★★★★★ ()
Ответ на: комментарий от gentoo_root

В удаве, действительно, цгруппы нерелевантны, а остальные проблемы были решены очень просто: запуск дочерних процессов объявили синхронным и поставили короткий таймаут с SIGKILL'ом (по всей иерархии или нет — я уже не помню).

Здесь, надо полагать, так не пройдёт. Вполне вероятно, что скажут, мол, запуск дочернего процесса — задача сложная, решить её можно потенциально сорок одним способом, так что юзайте уже существующий механизм в лице вспомогательного юнита и временного файла.

Хотя синдромом -ENOPATCH они не страдают, если напишешь что-нибудь приемлемое — наверняка вмерджат :)

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от intelfx

если напишешь что-нибудь приемлемое

Вот в том-то и дело, что я не уверен, что напишу что-то хорошее, нужно много всяких случаев предусмотреть.

gentoo_root ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.