From e03290041c91813f1a301c7e9c4dbb9ee768b400 Mon Sep 17 00:00:00 2001
From: Carlo Zancanaro
Date: Thu, 9 Aug 2018 22:30:38 +1000
Subject: [PATCH] service: Add a replacement slot for delayed service
replacement.
* modules/shepherd/service.scm (): Add replacement slot
(replace-service): New procedure.
(stop): Call replace-service after stopping a service.
* tests/replacement.sh: Add a test for it.
* Makefile.am (TESTS): Add the new test.
* doc/shepherd.texi (Slots of services): Document it.
---
Makefile.am | 1 +
doc/shepherd.texi | 9 +++
modules/shepherd/service.scm | 23 +++++++-
tests/replacement.sh | 106 +++++++++++++++++++++++++++++++++++
4 files changed, 138 insertions(+), 1 deletion(-)
create mode 100644 tests/replacement.sh
diff --git a/Makefile.am b/Makefile.am
index 8dad006..4322d7f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -184,6 +184,7 @@ SUFFIXES = .go
TESTS = \
tests/basic.sh \
+ tests/replacement.sh \
tests/respawn.sh \
tests/respawn-throttling.sh \
tests/misbehaved-client.sh \
diff --git a/doc/shepherd.texi b/doc/shepherd.texi
index 7946f8b..1de6d80 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -708,6 +708,15 @@ handler will not start it again.
otherwise @code{#f}.
address@hidden
address@hidden replacement (slot of )
address@hidden specifies a service to be used to replace this one
+when it is stopped. This service will continue to function normally
+until the @code{stop} action is invoked. After the service has been
+successfully stopped, its definition will be replaced by the value of
+this slot, which must itself be a service. This slot is ignored if
+its value is @code{#f}.
+
@end itemize
@c @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
diff --git a/modules/shepherd/service.scm b/modules/shepherd/service.scm
index 5653388..4f62dc1 100644
--- a/modules/shepherd/service.scm
+++ b/modules/shepherd/service.scm
@@ -205,7 +205,10 @@ respawned, shows that it has been respawned more than TIMES in SECONDS."
(stop-delay? #:init-keyword #:stop-delay?
#:init-value #f)
;; The times of the last respawns, most recent first.
- (last-respawns #:init-form '()))
+ (last-respawns #:init-form '())
+ ;; A replacement for when this service is stopped.
+ (replacement #:init-keyword #:replacement
+ #:init-value #f))
(define (service? obj)
"Return true if OBJ is a service."
@@ -341,6 +344,21 @@ wire."
(canonical-name obj)))))
(slot-ref obj 'running))
+(define (replace-service service)
+ (let ((replacement (slot-ref service 'replacement)))
+ (define (copy-slot! slot)
+ (slot-set! service slot (slot-ref replacement slot)))
+ (when replacement
+ (copy-slot! 'provides)
+ (copy-slot! 'requires)
+ (copy-slot! 'respawn?)
+ (copy-slot! 'start)
+ (copy-slot! 'stop)
+ (copy-slot! 'actions)
+ (copy-slot! 'running)
+ (copy-slot! 'docstring))
+ service))
+
;; Stop the service, including services that depend on it. If the
;; latter fails, continue anyway. Return `#f' if it could be stopped.
(define-method (stop (obj ) . args)
@@ -385,6 +403,9 @@ wire."
;; Reset the list of respawns.
(slot-set! obj 'last-respawns '())
+ ;; Replace the service with its replacement, if it has one
+ (replace-service obj)
+
;; Status message.
(let ((name (canonical-name obj)))
(if (running? obj)
diff --git a/tests/replacement.sh b/tests/replacement.sh
new file mode 100644
index 0000000..585ab5a
--- /dev/null
+++ b/tests/replacement.sh
@@ -0,0 +1,106 @@
+# GNU Shepherd --- Ensure replacing services works properly
+# Copyright © 2014, 2016 Ludovic Courtès
+# Copyright © 2018 Carlo Zancanaro
+#
+# This file is part of the GNU Shepherd.
+#
+# The GNU Shepherd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or (at
+# your option) any later version.
+#
+# The GNU Shepherd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the GNU Shepherd. If not, see .
+
+shepherd --version
+herd --version
+
+socket="t-socket-$$"
+conf="t-conf-$$"
+rconf="t-rconf-$$"
+log="t-log-$$"
+stamp="t-stamp-$$"
+pid="t-pid-$$"
+
+herd="herd -s $socket"
+
+trap "rm -f $socket $conf $rconf $stamp $log;
+ test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT
+
+cat > "$conf"<
+ #:provides '(test)
+ #:start (const #t)
+ #:actions (make-actions
+ (say-hello (lambda _
+ (call-with-output-file "$stamp"
+ (lambda (port)
+ (display "Hello" port))))))
+ #:respawn? #f))
+EOF
+
+rm -f "$pid" "$stamp" "$socket"
+shepherd -I -s "$socket" -c "$conf" --pid="$pid" --log="$log" &
+
+while ! test -f "$pid"; do sleep 0.5 ; done
+
+$herd start test
+
+if ! $herd say-hello test; then
+ echo "say-hello failed"
+ exit 1
+fi
+
+cat - > "$rconf"<
+ #:provides '(test)
+ #:start (const #t)
+ #:actions (make-actions
+ (say-goodbye (lambda _
+ (call-with-output-file "$stamp"
+ (lambda (port)
+ (display "Goodbye" port))))))
+ #:respawn? #f)))
+EOF
+
+$herd load root "$rconf"
+
+if ! $herd say-hello test; then
+ echo "say-hello failed after setting replacement"
+ exit 1
+fi
+
+if test `cat $stamp` != "Hello"; then
+ echo "Output file had the wrong contents! Was:"
+ cat $stamp
+ exit 1
+fi
+
+$herd stop test
+
+$herd start test
+
+if $herd say-hello test; then
+ echo "say-hello should have failed after stop/start"
+ exit 1
+fi
+
+if ! $herd say-goodbye test; then
+ echo "say-goodbye should have failed"
+ exit 1
+fi
+
+if test `cat $stamp` != "Goodbye"; then
+ echo "Output file had the wrong contents! Was:"
+ cat $stamp
+ exit 1
+fi
--
2.18.0