summary refs log tree commit diff
path: root/gnu/packages/patches
diff options
context:
space:
mode:
authorMarius Bakke <marius@gnu.org>2020-07-03 00:28:57 +0200
committerMarius Bakke <marius@gnu.org>2020-07-16 21:51:43 +0200
commit72a91d74cec01bfcfcef2b62c5b327fab82950b6 (patch)
treed93d3d99a5b2fe2f06765286e72cc89e17195b98 /gnu/packages/patches
parent851cc11cd1c28178110ea7db5d528b342e9bc5be (diff)
downloadguix-72a91d74cec01bfcfcef2b62c5b327fab82950b6.tar.gz
gnu: Add ganeti.
* gnu/packages/virtualization.scm (system->qemu-target, ganeti): New variables.
* gnu/packages/patches/ganeti-deterministic-manual.patch,
gnu/packages/patches/ganeti-disable-version-symlinks.patch,
gnu/packages/patches/ganeti-drbd-compat.patch,
gnu/packages/patches/ganeti-haskell-pythondir.patch,
gnu/packages/patches/ganeti-os-disk-size.patch,
gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch,
gnu/packages/patches/ganeti-shepherd-master-failover.patch,
gnu/packages/patches/ganeti-shepherd-support.patch: New files.
* gnu/local.mk (dist_patch_DATA): Adjust accordingly.
Diffstat (limited to 'gnu/packages/patches')
-rw-r--r--gnu/packages/patches/ganeti-deterministic-manual.patch16
-rw-r--r--gnu/packages/patches/ganeti-disable-version-symlinks.patch136
-rw-r--r--gnu/packages/patches/ganeti-drbd-compat.patch166
-rw-r--r--gnu/packages/patches/ganeti-haskell-pythondir.patch66
-rw-r--r--gnu/packages/patches/ganeti-os-disk-size.patch17
-rw-r--r--gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch21
-rw-r--r--gnu/packages/patches/ganeti-shepherd-master-failover.patch18
-rw-r--r--gnu/packages/patches/ganeti-shepherd-support.patch87
8 files changed, 527 insertions, 0 deletions
diff --git a/gnu/packages/patches/ganeti-deterministic-manual.patch b/gnu/packages/patches/ganeti-deterministic-manual.patch
new file mode 100644
index 0000000000..2d90aa740e
--- /dev/null
+++ b/gnu/packages/patches/ganeti-deterministic-manual.patch
@@ -0,0 +1,16 @@
+Sort the ecode list in the gnt-cluster manual for deterministic results.
+
+Submitted upstream: <https://github.com/ganeti/ganeti/pull/1504>.
+
+diff --git a/lib/build/sphinx_ext.py b/lib/build/sphinx_ext.py
+--- a/lib/build/sphinx_ext.py
++++ b/lib/build/sphinx_ext.py
+@@ -108,7 +108,7 @@ CV_ECODES_DOC = "ecodes"
+ # pylint: disable=W0621
+ CV_ECODES_DOC_LIST = [(name, doc) for (_, name, doc) in constants.CV_ALL_ECODES]
+ DOCUMENTED_CONSTANTS = {
+-  CV_ECODES_DOC: CV_ECODES_DOC_LIST,
++  CV_ECODES_DOC: sorted(CV_ECODES_DOC_LIST, key=lambda tup: tup[0]),
+   }
+ 
+ 
diff --git a/gnu/packages/patches/ganeti-disable-version-symlinks.patch b/gnu/packages/patches/ganeti-disable-version-symlinks.patch
new file mode 100644
index 0000000000..a5f347cfc6
--- /dev/null
+++ b/gnu/packages/patches/ganeti-disable-version-symlinks.patch
@@ -0,0 +1,136 @@
+This patch adds a new "--disable-version-links" configuration option
+that allows installing to the standard GNU installation directories
+instead of having to add symlinks in /etc/ganeti/{lib,share} that
+points to the right $ganeti/{lib,share}/$version.  Mainly to reduce
+service complexity, and because Guix users can install as many versions
+of Ganeti they can muster without resorting to such hacks.
+
+diff --git a/Makefile.am b/Makefile.am
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -66,11 +66,16 @@ SHELL_ENV_INIT = autotools/shell-env-init
+ # so, if some currently architecture-independent executable is replaced by an
+ # architecture-dependent one (and hence has to go under $(versiondir)), add a link
+ # under $(versionedsharedir) but do not change the external links.
++#
++# As of Ganeti 3.0, it is possible to disable this behavior by passing
++# --disable-version-links, in which case the standard GNU installation
++# directories are used.
+ if USE_VERSION_FULL
+ DIRVERSION=$(VERSION_FULL)
+ else
+ DIRVERSION=$(VERSION_MAJOR).$(VERSION_MINOR)
+ endif
++if USE_VERSION_LINKS
+ versiondir = $(libdir)/ganeti/$(DIRVERSION)
+ defaultversiondir = $(libdir)/ganeti/default
+ versionedsharedir = $(prefix)/share/ganeti/$(DIRVERSION)
+@@ -90,6 +95,18 @@ gntpythondir = $(versionedsharedir)
+ pkgpython_bindir = $(versionedsharedir)
+ gnt_python_sbindir = $(versionedsharedir)
+ tools_pythondir = $(versionedsharedir)
++else
++myexeclibdir = $(pkglibdir)
++pkgpython_rpc_stubdir = $(pkgpythondir)/rpc/stub
++gntpythondir = $(sbindir)
++pkgpython_bindir = $(pkglibdir)
++gnt_python_sbindir = $(sbindir)
++tools_pythondir = $(pkglibdir)
++versionedsharedir = $(pkglibdir)
++# This is a hack but works because the only user does $(versiondir)$(datadir).
++versiondir =
++endif !USE_VERSION_LINKS
++
+ 
+ clientdir = $(pkgpythondir)/client
+ cmdlibdir = $(pkgpythondir)/cmdlib
+@@ -2356,6 +2373,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
+ 	    -DVERSION_SUFFIX="$(VERSION_SUFFIX)" \
+ 	    -DVERSION_FULL="$(VERSION_FULL)" \
+ 	    -DDIRVERSION="$(DIRVERSION)" \
++	    -DUSE_VERSION_LINKS="$(USE_VERSION_LINKS)" \
+ 	    -DLOCALSTATEDIR="$(localstatedir)" \
+ 	    -DSYSCONFDIR="$(sysconfdir)" \
+ 	    -DSSH_CONFIG_DIR="$(SSH_CONFIG_DIR)" \
+@@ -2857,6 +2875,7 @@ install-exec-local:
+ 	@mkdir_p@ "$(DESTDIR)${localstatedir}/lib/ganeti" \
+ 	  "$(DESTDIR)${localstatedir}/log/ganeti" \
+ 	  "$(DESTDIR)${localstatedir}/run/ganeti"
++if USE_VERSION_LINKS
+ 	for dir in $(SYMLINK_TARGET_DIRS); do \
+ 	  @mkdir_p@  $(DESTDIR)$$dir; \
+ 	done
+@@ -2892,7 +2911,8 @@ install-exec-local:
+ if INSTALL_SYMLINKS
+ 	$(LN_S) -f $(versionedsharedir) $(DESTDIR)$(sysconfdir)/ganeti/share
+ 	$(LN_S) -f $(versiondir) $(DESTDIR)$(sysconfdir)/ganeti/lib
+-endif
++endif INSTALL_SYMLINKS
++endif USE_VERSION_LINKS
+ 
+ .PHONY: apidoc
+ if WANT_HSAPIDOC
+diff --git a/configure.ac b/configure.ac
+--- a/configure.ac
++++ b/configure.ac
+@@ -29,6 +29,23 @@ AC_SUBST([BINDIR], $bindir)
+ AC_SUBST([SBINDIR], $sbindir)
+ AC_SUBST([MANDIR], $mandir)
+ 
++# --enable-version-links
++AC_ARG_ENABLE([version-links],
++  [AS_HELP_STRING([--enable-version-links],
++                  m4_normalize([install ganeti to version-specific
++                  subdirectories to allow installing multiple versions
++                  in parallel (default: enabled)]))],
++  [[if test "$enableval" != no; then
++      USE_VERSION_LINKS=True
++    else
++      USE_VERSION_LINKS=False
++    fi
++  ]],
++  [USE_VERSION_LINKS=True
++  ])
++AC_SUBST(USE_VERSION_LINKS, $USE_VERSION_LINKS)
++AM_CONDITIONAL([USE_VERSION_LINKS], [test "$USE_VERSION_LINKS" = True])
++
+ # --enable-versionfull
+ AC_ARG_ENABLE([versionfull],
+   [AS_HELP_STRING([--enable-versionfull],
+diff --git a/lib/bootstrap.py b/lib/bootstrap.py
+--- a/lib/bootstrap.py
++++ b/lib/bootstrap.py
+@@ -944,7 +944,7 @@ def SetupNodeDaemon(opts, cluster_name, node, ssh_port):
+                          debug=opts.debug, verbose=opts.verbose,
+                          use_cluster_key=True, ask_key=opts.ssh_key_check,
+                          strict_host_check=opts.ssh_key_check,
+-                         ensure_version=True)
++                         ensure_version=constants.USE_VERSION_LINKS)
+ 
+   _WaitForSshDaemon(node, ssh_port)
+   _WaitForNodeDaemon(node)
+diff --git a/src/AutoConf.hs.in b/src/AutoConf.hs.in
+--- a/src/AutoConf.hs.in
++++ b/src/AutoConf.hs.in
+@@ -64,6 +64,9 @@ versionFull = "VERSION_FULL"
+ dirVersion :: String
+ dirVersion = "DIRVERSION"
+ 
++useVersionLinks :: Bool
++useVersionLinks = USE_VERSION_LINKS
++
+ localstatedir :: String
+ localstatedir = "LOCALSTATEDIR"
+ 
+diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs
+--- a/src/Ganeti/Constants.hs
++++ b/src/Ganeti/Constants.hs
+@@ -164,5 +164,8 @@ versionRevision = AutoConf.versionRevision
+ dirVersion :: String
+ dirVersion = AutoConf.dirVersion
+ 
++useVersionLinks :: Bool
++useVersionLinks = AutoConf.useVersionLinks
++
+ osApiV10 :: Int
+ osApiV10 = 10
diff --git a/gnu/packages/patches/ganeti-drbd-compat.patch b/gnu/packages/patches/ganeti-drbd-compat.patch
new file mode 100644
index 0000000000..32f46bc7ed
--- /dev/null
+++ b/gnu/packages/patches/ganeti-drbd-compat.patch
@@ -0,0 +1,166 @@
+This patch adds support for newer versions of DRBD.
+
+Submitted upstream: <https://github.com/ganeti/ganeti/pull/1496>.
+
+diff --git a/lib/storage/drbd.py b/lib/storage/drbd.py
+--- a/lib/storage/drbd.py
++++ b/lib/storage/drbd.py
+@@ -315,6 +315,13 @@ class DRBD8Dev(base.BlockDev):
+     """
+     return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
+ 
++  @staticmethod
++  def _NeedsLocalSyncerParams():
++    # For DRBD >= 8.4, syncer init must be done after local, not in net.
++    info = DRBD8.GetProcInfo()
++    version = info.GetVersion()
++    return version["k_minor"] >= 4
++
+   def _MatchesLocal(self, info):
+     """Test if our local config matches with an existing device.
+ 
+@@ -397,6 +404,20 @@ class DRBD8Dev(base.BlockDev):
+         base.ThrowError("drbd%d: can't attach local disk: %s",
+                         minor, result.output)
+ 
++    def _WaitForMinorSyncParams():
++      """Call _SetMinorSyncParams and raise RetryAgain on errors.
++      """
++      if self._SetMinorSyncParams(minor, self.params):
++        raise utils.RetryAgain()
++
++    if self._NeedsLocalSyncerParams():
++      # Retry because disk config for DRBD resource may be still uninitialized.
++      try:
++        utils.Retry(_WaitForMinorSyncParams, 1.0, 5.0)
++      except utils.RetryTimeout as e:
++        base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
++                        (minor, utils.CommaJoin(e.args[0])))
++
+   def _AssembleNet(self, minor, net_info, dual_pri=False, hmac=None,
+                    secret=None):
+     """Configure the network part of the device.
+@@ -432,21 +453,24 @@ class DRBD8Dev(base.BlockDev):
+     # sync speed only after setting up both sides can race with DRBD
+     # connecting, hence we set it here before telling DRBD anything
+     # about its peer.
+-    sync_errors = self._SetMinorSyncParams(minor, self.params)
+-    if sync_errors:
+-      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
+-                      (minor, utils.CommaJoin(sync_errors)))
++
++    if not self._NeedsLocalSyncerParams():
++      sync_errors = self._SetMinorSyncParams(minor, self.params)
++      if sync_errors:
++        base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
++                        (minor, utils.CommaJoin(sync_errors)))
+ 
+     family = self._GetNetFamily(minor, lhost, rhost)
+ 
+-    cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
++    cmds = self._cmd_gen.GenNetInitCmds(minor, family, lhost, lport,
+                                       rhost, rport, protocol,
+                                       dual_pri, hmac, secret, self.params)
+ 
+-    result = utils.RunCmd(cmd)
+-    if result.failed:
+-      base.ThrowError("drbd%d: can't setup network: %s - %s",
+-                      minor, result.fail_reason, result.output)
++    for cmd in cmds:
++      result = utils.RunCmd(cmd)
++      if result.failed:
++        base.ThrowError("drbd%d: can't setup network: %s - %s",
++                         minor, result.fail_reason, result.output)
+ 
+     def _CheckNetworkConfig():
+       info = self._GetShowInfo(minor)
+@@ -463,19 +487,20 @@ class DRBD8Dev(base.BlockDev):
+       base.ThrowError("drbd%d: timeout while configuring network", minor)
+ 
+     # Once the assembly is over, try to set the synchronization parameters
+-    try:
+-      # The minor may not have been set yet, requiring us to set it at least
+-      # temporarily
+-      old_minor = self.minor
+-      self._SetFromMinor(minor)
+-      sync_errors = self.SetSyncParams(self.params)
+-      if sync_errors:
+-        base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
+-                        (self.minor, utils.CommaJoin(sync_errors)))
+-    finally:
+-      # Undo the change, regardless of whether it will have to be done again
+-      # soon
+-      self._SetFromMinor(old_minor)
++    if not self._NeedsLocalSyncerParams():
++      try:
++        # The minor may not have been set yet, requiring us to set it at least
++        # temporarily
++        old_minor = self.minor
++        self._SetFromMinor(minor)
++        sync_errors = self.SetSyncParams(self.params)
++        if sync_errors:
++          base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
++                          (self.minor, utils.CommaJoin(sync_errors)))
++      finally:
++        # Undo the change, regardless of whether it will have to be done again
++        # soon
++        self._SetFromMinor(old_minor)
+ 
+   @staticmethod
+   def _GetNetFamily(minor, lhost, rhost):
+diff --git a/lib/storage/drbd_cmdgen.py b/lib/storage/drbd_cmdgen.py
+--- a/lib/storage/drbd_cmdgen.py
++++ b/lib/storage/drbd_cmdgen.py
+@@ -56,7 +56,7 @@ class BaseDRBDCmdGenerator(object):
+   def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
+     raise NotImplementedError
+ 
+-  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
++  def GenNetInitCmds(self, minor, family, lhost, lport, rhost, rport, protocol,
+                     dual_pri, hmac, secret, params):
+     raise NotImplementedError
+ 
+@@ -138,7 +138,7 @@ class DRBD83CmdGenerator(BaseDRBDCmdGenerator):
+ 
+     return [args]
+ 
+-  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
++  def GenNetInitCmds(self, minor, family, lhost, lport, rhost, rport, protocol,
+                     dual_pri, hmac, secret, params):
+     args = ["drbdsetup", self._DevPath(minor), "net",
+             "%s:%s:%s" % (family, lhost, lport),
+@@ -155,7 +155,7 @@ class DRBD83CmdGenerator(BaseDRBDCmdGenerator):
+     if params[constants.LDP_NET_CUSTOM]:
+       args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
+ 
+-    return args
++    return [args]
+ 
+   def GenSyncParamsCmd(self, minor, params):
+     args = ["drbdsetup", self._DevPath(minor), "syncer"]
+@@ -345,8 +345,14 @@ class DRBD84CmdGenerator(BaseDRBDCmdGenerator):
+ 
+     return cmds
+ 
+-  def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
++  def GenNetInitCmds(self, minor, family, lhost, lport, rhost, rport, protocol,
+                     dual_pri, hmac, secret, params):
++    cmds = []
++
++    cmds.append(["drbdsetup", "new-resource", self._GetResource(minor)])
++    cmds.append(["drbdsetup", "new-minor", self._GetResource(minor),
++                 str(minor), "0"])
++
+     args = ["drbdsetup", "connect", self._GetResource(minor),
+             "%s:%s:%s" % (family, lhost, lport),
+             "%s:%s:%s" % (family, rhost, rport),
+@@ -362,7 +368,8 @@ class DRBD84CmdGenerator(BaseDRBDCmdGenerator):
+     if params[constants.LDP_NET_CUSTOM]:
+       args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
+ 
+-    return args
++    cmds.append(args)
++    return cmds
+ 
+   def GenSyncParamsCmd(self, minor, params):
+     args = ["drbdsetup", "disk-options", minor]
diff --git a/gnu/packages/patches/ganeti-haskell-pythondir.patch b/gnu/packages/patches/ganeti-haskell-pythondir.patch
new file mode 100644
index 0000000000..fa77771839
--- /dev/null
+++ b/gnu/packages/patches/ganeti-haskell-pythondir.patch
@@ -0,0 +1,66 @@
+This patch allows the Haskell daemons to locate Python libraries
+installed to a non-standard pythondir.  It is necessary because Guix
+does not use versionedsharedir (see related patch that disables it).
+
+diff --git a/Makefile.am b/Makefile.am
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -83,6 +83,7 @@ myexeclibdir = $(pkglibdir)
+ bindir = $(versiondir)/$(BINDIR)
+ sbindir = $(versiondir)$(SBINDIR)
+ mandir = $(versionedsharedir)/root$(MANDIR)
++pythondir = $(versionedsharedir)
+ pkgpythondir = $(versionedsharedir)/ganeti
+ pkgpython_rpc_stubdir = $(versionedsharedir)/ganeti/rpc/stub
+ gntpythondir = $(versionedsharedir)
+@@ -2386,6 +2387,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \
+ 	    -DPKGLIBDIR="$(libdir)/ganeti" \
+ 	    -DSHAREDIR="$(prefix)/share/ganeti" \
+ 	    -DVERSIONEDSHAREDIR="$(versionedsharedir)" \
++	    -DPYTHONDIR="$(pythondir)" \
+ 	    -DDRBD_BARRIERS="$(DRBD_BARRIERS)" \
+ 	    -DDRBD_NO_META_FLUSH="$(DRBD_NO_META_FLUSH)" \
+ 	    -DSYSLOG_USAGE="$(SYSLOG_USAGE)" \
+diff --git a/src/AutoConf.hs.in b/src/AutoConf.hs.in
+--- a/src/AutoConf.hs.in
++++ b/src/AutoConf.hs.in
+@@ -157,6 +157,9 @@ sharedir = "SHAREDIR"
+ versionedsharedir :: String
+ versionedsharedir = "VERSIONEDSHAREDIR"
+ 
++pythondir :: String
++pythondir = "PYTHONDIR"
++
+ drbdBarriers :: String
+ drbdBarriers = "DRBD_BARRIERS"
+ 
+diff --git a/src/Ganeti/Path.hs b/src/Ganeti/Path.hs
+--- a/src/Ganeti/Path.hs
++++ b/src/Ganeti/Path.hs
+@@ -188,5 +188,5 @@ getInstReasonFilename instName = instanceReasonDir `pjoin` instName
+ 
+ -- | The path to the Python executable for starting jobs.
+ jqueueExecutorPy :: IO FilePath
+-jqueueExecutorPy = return $ versionedsharedir
+-                            </> "ganeti" </> "jqueue" </> "exec.py"
++jqueueExecutorPy = return $ pythondir
++                           </> "ganeti" </> "jqueue" </> "exec.py"
+diff --git a/src/Ganeti/Query/Exec.hs b/src/Ganeti/Query/Exec.hs
+--- a/src/Ganeti/Query/Exec.hs
++++ b/src/Ganeti/Query/Exec.hs
+@@ -99,12 +99,12 @@ spawnJobProcess jid = withErrorLogAt CRITICAL (show jid) $
+   do
+     use_debug <- isDebugMode
+     env_ <- (M.toList . M.insert "GNT_DEBUG" (if use_debug then "1" else "0")
+-            . M.insert "PYTHONPATH" AC.versionedsharedir
++            . M.insert "PYTHONPATH" AC.pythondir
+             . M.fromList)
+            `liftM` getEnvironment
+     execPy <- P.jqueueExecutorPy
+     logDebug $ "Executing " ++ AC.pythonPath ++ " " ++ execPy
+-               ++ " with PYTHONPATH=" ++ AC.versionedsharedir
++               ++ " with PYTHONPATH=" ++ AC.pythondir
+ 
+     (master, child) <- pipeClient connectConfig
+     let (rh, wh) = clientToHandle child
+
diff --git a/gnu/packages/patches/ganeti-os-disk-size.patch b/gnu/packages/patches/ganeti-os-disk-size.patch
new file mode 100644
index 0000000000..16b1d7615c
--- /dev/null
+++ b/gnu/packages/patches/ganeti-os-disk-size.patch
@@ -0,0 +1,17 @@
+This exposes information about disk sizes to OS install scripts.  instance-guix
+uses this if available to determine the size of the VM image.
+
+Submitted upstream:
+https://github.com/ganeti/ganeti/pull/1503
+
+diff --git a/lib/backend.py b/lib/backend.py
+--- a/lib/backend.py
++++ b/lib/backend.py
+@@ -4305,6 +4305,7 @@ def OSEnvironment(instance, inst_os, debug=0):
+     uri = _CalculateDeviceURI(instance, disk, real_disk)
+     result["DISK_%d_ACCESS" % idx] = disk.mode
+     result["DISK_%d_UUID" % idx] = disk.uuid
++    result["DISK_%d_SIZE" % idx] = str(disk.size)
+     if real_disk.dev_path:
+       result["DISK_%d_PATH" % idx] = real_disk.dev_path
+     if uri:
diff --git a/gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch b/gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch
new file mode 100644
index 0000000000..1358e30633
--- /dev/null
+++ b/gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch
@@ -0,0 +1,21 @@
+Do not override PYTHONPATH when calling Python code from the Haskell
+daemons.  This is necessary because the Python library dependencies are
+only available through PYTHONPATH.
+
+diff --git a/src/Ganeti/Query/Exec.hs b/src/Ganeti/Query/Exec.hs
+--- a/src/Ganeti/Query/Exec.hs
++++ b/src/Ganeti/Query/Exec.hs
+@@ -99,12 +99,10 @@ spawnJobProcess jid = withErrorLogAt CRITICAL (show jid) $
+   do
+     use_debug <- isDebugMode
+     env_ <- (M.toList . M.insert "GNT_DEBUG" (if use_debug then "1" else "0")
+-            . M.insert "PYTHONPATH" AC.pythondir
+             . M.fromList)
+            `liftM` getEnvironment
+     execPy <- P.jqueueExecutorPy
+     logDebug $ "Executing " ++ AC.pythonPath ++ " " ++ execPy
+-               ++ " with PYTHONPATH=" ++ AC.pythondir
+ 
+     (master, child) <- pipeClient connectConfig
+     let (rh, wh) = clientToHandle child
+
diff --git a/gnu/packages/patches/ganeti-shepherd-master-failover.patch b/gnu/packages/patches/ganeti-shepherd-master-failover.patch
new file mode 100644
index 0000000000..36a7918998
--- /dev/null
+++ b/gnu/packages/patches/ganeti-shepherd-master-failover.patch
@@ -0,0 +1,18 @@
+By default, master-failover will call "herd start ganeti-wconfd" with
+extra arguments such as --force-node.  That does not work with the
+Shepherd, so the Guix service has a "force-start" action for this purpose.
+
+diff --git a/lib/bootstrap.py b/lib/bootstrap.py
+--- a/lib/bootstrap.py
++++ b/lib/bootstrap.py
+@@ -1011,9 +1011,7 @@ def MasterFailover(no_voting=False):
+ 
+   try:
+     # Forcefully start WConfd so that we can access the configuration
+-    result = utils.RunCmd([pathutils.DAEMON_UTIL,
+-                           "start", constants.WCONFD, "--force-node",
+-                           "--no-voting", "--yes-do-it"])
++    result = utils.RunCmd(["herd", "force-start", constants.WCONFD])
+     if result.failed:
+       raise errors.OpPrereqError("Could not start the configuration daemon,"
+                                  " command %s had exitcode %s and error %s" %
diff --git a/gnu/packages/patches/ganeti-shepherd-support.patch b/gnu/packages/patches/ganeti-shepherd-support.patch
new file mode 100644
index 0000000000..f750604344
--- /dev/null
+++ b/gnu/packages/patches/ganeti-shepherd-support.patch
@@ -0,0 +1,87 @@
+Ganeti uses an internal tool to start/stop daemons during init and
+upgrade.  This patch makes the tool use native Shepherd facilities.
+
+diff --git a/daemons/daemon-util.in b/daemons/daemon-util.in
+--- a/daemons/daemon-util.in
++++ b/daemons/daemon-util.in
+@@ -184,6 +184,21 @@ use_systemctl() {
+   return 1
+ }
+ 
++# Checks if we should use the Shepherd to start/stop daemons
++use_shepherd() {
++  # Is Shepherd running as PID 1?
++  ps --no-headers -p 1 -o cmd | grep -q shepherd || return 1
++
++  type -p herd >/dev/null || return 1
++
++  # Does Shepherd know about Ganeti at all?
++  if herd status | grep -q ganeti; then
++    return 0
++  fi
++
++  return 1
++}
++
+ # Prints path to PID file for a daemon.
+ daemon_pidfile() {
+   if [[ "$#" -lt 1 ]]; then
+@@ -261,6 +276,13 @@ check() {
+     else
+       return 1
+     fi
++  elif use_shepherd; then
++    activestate="$(herd status ${name})"
++    if echo $activestate | grep -q Running ; then
++      return 0
++    else
++      return 1
++    fi
+   elif type -p start-stop-daemon >/dev/null; then
+     start-stop-daemon --stop --signal 0 --quiet \
+       --pidfile $pidfile --name "$name"
+@@ -291,6 +313,20 @@ start() {
+     return $?
+   fi
+ 
++  if use_shepherd; then
++    if herd status "$name" | grep -q "disabled"; then
++      # The Shepherd will disable a service that has stopped, even if it exits
++      # gracefully.  Thus, we must re-enable it in case of a master failover.
++      herd enable "${name}"
++    fi
++    # Note: unlike systemd, which happily starts a service and returns success
++    # even if the daemon immediately exits, the Shepherd actually waits for it
++    # to come up.  Thus, ignore the exit status from 'herd start' in case of
++    # master daemons running on the wrong node, or ganeti-kvmd disabled, etc.
++    herd start "${name}"
++    return 0
++  fi
++
+   # Read $<daemon>_ARGS and $EXTRA_<daemon>_ARGS
+   eval local args="\"\$${ucname}_ARGS \$EXTRA_${ucname}_ARGS\""
+ 
+@@ -336,6 +372,13 @@ stop() {
+ 
+   if use_systemctl; then
+     systemctl stop "${name}.service"
++  elif use_shepherd; then
++    if herd status | grep -q "$name"; then
++      herd stop "$name"
++    else
++      # Do not raise an error if the service has not been enabled.
++      return 0
++    fi
+   elif type -p start-stop-daemon >/dev/null; then
+     start-stop-daemon --stop --quiet --oknodo --retry 30 \
+       --pidfile $pidfile --name "$name"
+@@ -352,6 +395,9 @@ check_and_start() {
+     if use_systemctl; then
+       echo "${name} supervised by systemd but not running, will not restart."
+       return 1
++    elif use_shepherd; then
++      echo "${name} supervised by shepherd but not running, will not restart."
++      return 1
+     fi
+ 
+     start $name