From 72a91d74cec01bfcfcef2b62c5b327fab82950b6 Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Fri, 3 Jul 2020 00:28:57 +0200 Subject: 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. --- gnu/local.mk | 8 + .../patches/ganeti-deterministic-manual.patch | 16 + .../patches/ganeti-disable-version-symlinks.patch | 136 +++++++ gnu/packages/patches/ganeti-drbd-compat.patch | 166 +++++++++ .../patches/ganeti-haskell-pythondir.patch | 66 ++++ gnu/packages/patches/ganeti-os-disk-size.patch | 17 + .../patches/ganeti-preserve-PYTHONPATH.patch | 21 ++ .../patches/ganeti-shepherd-master-failover.patch | 18 + gnu/packages/patches/ganeti-shepherd-support.patch | 87 +++++ gnu/packages/virtualization.scm | 395 +++++++++++++++++++++ 10 files changed, 930 insertions(+) create mode 100644 gnu/packages/patches/ganeti-deterministic-manual.patch create mode 100644 gnu/packages/patches/ganeti-disable-version-symlinks.patch create mode 100644 gnu/packages/patches/ganeti-drbd-compat.patch create mode 100644 gnu/packages/patches/ganeti-haskell-pythondir.patch create mode 100644 gnu/packages/patches/ganeti-os-disk-size.patch create mode 100644 gnu/packages/patches/ganeti-preserve-PYTHONPATH.patch create mode 100644 gnu/packages/patches/ganeti-shepherd-master-failover.patch create mode 100644 gnu/packages/patches/ganeti-shepherd-support.patch (limited to 'gnu') diff --git a/gnu/local.mk b/gnu/local.mk index f7c6036ee7..c36fa1ea5e 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -947,6 +947,14 @@ dist_patch_DATA = \ %D%/packages/patches/fontconfig-hurd-path-max.patch \ %D%/packages/patches/freeimage-unbundle.patch \ %D%/packages/patches/fuse-overlapping-headers.patch \ + %D%/packages/patches/ganeti-deterministic-manual.patch \ + %D%/packages/patches/ganeti-disable-version-symlinks.patch \ + %D%/packages/patches/ganeti-drbd-compat.patch \ + %D%/packages/patches/ganeti-haskell-pythondir.patch \ + %D%/packages/patches/ganeti-os-disk-size.patch \ + %D%/packages/patches/ganeti-preserve-PYTHONPATH.patch \ + %D%/packages/patches/ganeti-shepherd-master-failover.patch \ + %D%/packages/patches/ganeti-shepherd-support.patch \ %D%/packages/patches/gash-utils-ls-test.patch \ %D%/packages/patches/gawk-shell.patch \ %D%/packages/patches/gcc-arm-bug-71399.patch \ 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: . + +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: . + +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 $_ARGS and $EXTRA__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 diff --git a/gnu/packages/virtualization.scm b/gnu/packages/virtualization.scm index f4df76fec9..d4ef9cc3fd 100644 --- a/gnu/packages/virtualization.scm +++ b/gnu/packages/virtualization.scm @@ -38,6 +38,7 @@ #:use-module (gnu packages attr) #:use-module (gnu packages autotools) #:use-module (gnu packages backup) + #:use-module (gnu packages base) #:use-module (gnu packages bison) #:use-module (gnu packages check) #:use-module (gnu packages cmake) @@ -60,11 +61,19 @@ #:use-module (gnu packages gnome) #:use-module (gnu packages gnupg) #:use-module (gnu packages golang) + #:use-module (gnu packages graphviz) #:use-module (gnu packages gtk) + #:use-module (gnu packages haskell) + #:use-module (gnu packages haskell-apps) + #:use-module (gnu packages haskell-check) + #:use-module (gnu packages haskell-crypto) + #:use-module (gnu packages haskell-web) + #:use-module (gnu packages haskell-xyz) #:use-module (gnu packages image) #:use-module (gnu packages libbsd) #:use-module (gnu packages libusb) #:use-module (gnu packages linux) + #:use-module (gnu packages m4) #:use-module (gnu packages ncurses) #:use-module (gnu packages nettle) #:use-module (gnu packages networking) @@ -75,6 +84,7 @@ #:use-module (gnu packages polkit) #:use-module (gnu packages protobuf) #:use-module (gnu packages python) + #:use-module (gnu packages python-crypto) #:use-module (gnu packages python-web) #:use-module (gnu packages python-xyz) #:use-module (gnu packages pulseaudio) @@ -82,6 +92,7 @@ #:use-module (gnu packages sdl) #:use-module (gnu packages sphinx) #:use-module (gnu packages spice) + #:use-module (gnu packages ssh) #:use-module (gnu packages texinfo) #:use-module (gnu packages textutils) #:use-module (gnu packages tls) @@ -349,6 +360,390 @@ server and embedded PowerPC, and S390 guests.") "usbredir" "libdrm" "libepoxy" "pulseaudio" "vde2" "libcacard"))))) +(define (system->qemu-target system) + (cond + ((string-prefix? "i686" system) + "qemu-system-i386") + ((string-prefix? "arm" system) + "qemu-system-arm") + (else + (string-append "qemu-system-" (match (string-split system #\-) + ((arch kernel) arch) + (_ system)))))) + +(define-public ganeti + (package + (name "ganeti") + ;; Note: we use a pre-release for Python 3 compatibility as well as many + ;; other fixes. + (version "3.0.0beta1-24-g024cc9fa2") + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/ganeti/ganeti") + (commit (string-append "v" version)))) + (sha256 + (base32 "1ll34qd2mifni3bhg7cnir3xfnkafig8ch33qndqwrsby0y5ssia")) + (file-name (git-file-name name version)) + (patches (search-patches "ganeti-shepherd-support.patch" + "ganeti-shepherd-master-failover.patch" + "ganeti-deterministic-manual.patch" + "ganeti-drbd-compat.patch" + "ganeti-os-disk-size.patch" + "ganeti-haskell-pythondir.patch" + "ganeti-disable-version-symlinks.patch" + "ganeti-preserve-PYTHONPATH.patch")))) + (build-system gnu-build-system) + (arguments + `(#:imported-modules (,@%gnu-build-system-modules + (guix build haskell-build-system) + (guix build python-build-system)) + #:modules (,@%gnu-build-system-modules + ((guix build haskell-build-system) #:prefix haskell:) + ((guix build python-build-system) #:select (python-version)) + (ice-9 rdelim)) + + ;; The default test target includes a lot of checks that are only really + ;; relevant for developers such as NEWS file checking, line lengths, etc. + ;; We are only interested in the "py-tests" and "hs-tests" targets: this + ;; is the closest we've got even though it includes a little more. + #:test-target "check-TESTS" + + #:configure-flags + (list "--localstatedir=/var" + "--sharedstatedir=/var" + "--sysconfdir=/etc" + "--enable-haskell-tests" + + ;; By default, the build system installs everything to versioned + ;; directories such as $libdir/3.0 and relies on a $libdir/default + ;; symlink pointed from /etc/ganeti/{lib,share} to actually function. + ;; This is done to accommodate installing multiple versions in + ;; parallel, but is of little use to us as Guix users can just + ;; roll back and forth. Thus, disable it for simplicity. + "--disable-version-links" + + ;; Ganeti can optionally take control over SSH host keys and + ;; distribute them to nodes as they are added, and also rotate keys + ;; with 'gnt-cluster renew-crypto --new-ssh-keys'. Thus it needs to + ;; know how to restart the SSH daemon. + "--with-sshd-restart-command='herd restart ssh-daemon'" + + ;; Look for OS definitions in this directory by default. It can + ;; be changed in the cluster configuration. + "--with-os-search-path=/run/current-system/profile/share/ganeti/os" + + ;; The default QEMU executable to use. We don't use the package + ;; here because this entry is stored in the cluster configuration. + (string-append "--with-kvm-path=/run/current-system/profile/bin/" + ,(system->qemu-target (%current-system)))) + #:phases + (modify-phases %standard-phases + (add-after 'unpack 'create-vcs-version + (lambda _ + ;; If we are building from a git checkout, we need to create a + ;; 'vcs-version' file manually because the build system does + ;; not have access to the git repository information. + (unless (file-exists? "vcs-version") + (call-with-output-file "vcs-version" + (lambda (port) + (format port "v~a~%" ,version)))) + #t)) + (add-after 'unpack 'patch-absolute-file-names + (lambda _ + (substitute* '("lib/utils/process.py" + "lib/utils/text.py" + "src/Ganeti/Constants.hs" + "src/Ganeti/HTools/CLI.hs" + "test/py/ganeti.config_unittest.py" + "test/py/ganeti.hooks_unittest.py" + "test/py/ganeti.utils.process_unittest.py" + "test/py/ganeti.utils.text_unittest.py" + "test/py/ganeti.utils.wrapper_unittest.py") + (("/bin/sh") (which "sh")) + (("/bin/bash") (which "bash")) + (("/usr/bin/env") (which "env")) + (("/bin/true") (which "true"))) + + ;; This script is called by the node daemon at startup to perform + ;; sanity checks on the cluster IP addresses, and it is also used + ;; in a master-failover scenario. Add absolute references to + ;; avoid propagating these executables. + (substitute* "tools/master-ip-setup" + (("arping") (which "arping")) + (("ndisc6") (which "ndisc6")) + (("fping") (which "fping")) + (("grep") (which "grep")) + (("ip addr") (string-append (which "ip") " addr"))) + #t)) + (add-after 'unpack 'override-builtin-PATH + (lambda _ + ;; Ganeti runs OS install scripts and similar with a built-in + ;; hard coded PATH. Patch so it works on Guix System. + (substitute* "src/Ganeti/Constants.hs" + (("/sbin:/bin:/usr/sbin:/usr/bin") + "/run/setuid-programs:/run/current-system/profile/sbin:\ +/run/current-system/profile/bin")) + #t)) + (add-after 'bootstrap 'patch-sphinx-version-detection + (lambda _ + ;; The build system runs 'sphinx-build --version' to verify that + ;; the Sphinx is recent enough, but does not expect the + ;; .sphinx-build-real executable name created by the Sphinx wrapper. + (substitute* "configure" + (("\\$SPHINX --version 2>&1") + "$SPHINX --version 2>&1 | sed 's/.sphinx-build-real/sphinx-build/g'")) + #t)) + + ;; The build system invokes Cabal and GHC, which do not work with + ;; GHC_PACKAGE_PATH: . + ;; Tweak the build system to do roughly what haskell-build-system does. + (add-before 'configure 'configure-haskell + (assoc-ref haskell:%standard-phases 'setup-compiler)) + (add-after 'configure 'do-not-use-GHC_PACKAGE_PATH + (lambda _ + (unsetenv "GHC_PACKAGE_PATH") + (substitute* "Makefile" + (("\\$\\(CABAL\\)") + "$(CABAL) --package-db=../package.conf.d") + (("\\$\\(GHC\\)") + "$(GHC) -package-db=../package.conf.d")) + #t)) + + (add-after 'configure 'fix-installation-directories + (lambda _ + (substitute* "Makefile" + ;; Do not attempt to create /var during install. + (("\\$\\(DESTDIR\\)\\$\\{localstatedir\\}") + "$(DESTDIR)${prefix}${localstatedir}") + ;; Similarly, do not attempt to install the sample ifup scripts + ;; to /etc/ganeti. + (("\\$\\(DESTDIR\\)\\$\\(ifupdir\\)") + "$(DESTDIR)${prefix}$(ifupdir)")) + #t)) + (add-before 'build 'adjust-tests + (lambda _ + ;; Disable tests that can not run. Do it early to prevent + ;; touching the Makefile later and triggering a needless rebuild. + (substitute* "Makefile" + ;; These tests expect the presence of a 'root' user (via + ;; ganeti/runtime.py), which fails in the build environment. + (("test/py/ganeti\\.asyncnotifier_unittest\\.py") "") + (("test/py/ganeti\\.backend_unittest\\.py") "") + (("test/py/ganeti\\.daemon_unittest\\.py") "") + (("test/py/ganeti\\.tools\\.ensure_dirs_unittest\\.py") "") + (("test/py/ganeti\\.utils\\.io_unittest-runasroot\\.py") "") + ;; Disable the bash_completion test, as it requires the full + ;; bash instead of bash-minimal. + (("test/py/bash_completion\\.bash") + "") + ;; This test requires networking. + (("test/py/import-export_unittest\\.bash") + "")) + + ;; Many of the Makefile targets reset PYTHONPATH before running + ;; the Python interpreter, which does not work very well for us. + (substitute* "Makefile" + (("PYTHONPATH=") + (string-append "PYTHONPATH=" (getenv "PYTHONPATH") ":"))) + #t)) + (add-after 'build 'build-bash-completions + (lambda _ + (let ((orig-pythonpath (getenv "PYTHONPATH"))) + (setenv "PYTHONPATH" (string-append ".:" orig-pythonpath)) + (invoke "./autotools/build-bash-completion") + (setenv "PYTHONPATH" orig-pythonpath) + #t))) + (add-before 'check 'pre-check + (lambda* (#:key inputs #:allow-other-keys) + ;; Set TZDIR so that time zones are found. + (setenv "TZDIR" (string-append (assoc-ref inputs "tzdata") + "/share/zoneinfo")) + + ;; This test checks whether PYTHONPATH is untouched, and extends + ;; it to include test directories if so. Add an else branch for + ;; our modified PYTHONPATH, in order to prevent a confusing test + ;; failure where expired certificates are not cleaned because + ;; check-cert-expired is silently crashing. + (substitute* "test/py/ganeti-cleaner_unittest.bash" + (("then export PYTHONPATH=(.*)" all testpath) + (string-append all "else export PYTHONPATH=" + (getenv "PYTHONPATH") ":" testpath "\n"))) + + (substitute* "test/py/ganeti.utils.process_unittest.py" + ;; This test attempts to run an executable with + ;; RunCmd(..., reset_env=True), which fails because the default + ;; PATH from Constants.hs does not exist in the build container. + ((".*def testResetEnv.*" all) + (string-append " @unittest.skipIf(True, " + "\"cannot reset env in the build container\")\n" + all)) + + ;; XXX: Somehow this test fails in the build container, but + ;; works in 'guix environment -C', even without /bin/sh? + ((".*def testPidFile.*" all) + (string-append " @unittest.skipIf(True, " + "\"testPidFile fails in the build container\")\n" + all))) + + ;; XXX: Why are these links not added automatically. + (with-directory-excursion "test/hs" + (for-each (lambda (file) + (symlink "../../src/htools" file)) + '("hspace" "hscan" "hinfo" "hbal" "hroller" + "hcheck" "hail" "hsqueeze"))) + #t)) + (add-after 'install 'install-bash-completions + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (compdir (string-append out "/etc/bash_completion.d"))) + (mkdir-p compdir) + (copy-file "doc/examples/bash_completion" + (string-append compdir "/ganeti")) + ;; The one file contains completions for many different + ;; executables. Create symlinks for found completions. + (with-directory-excursion compdir + (for-each + (lambda (prog) (symlink "ganeti" prog)) + (call-with-input-file "ganeti" + (lambda (port) + (let loop ((line (read-line port)) + (progs '())) + (if (eof-object? line) + progs + (if (string-prefix? "complete" line) + (loop (read-line port) + ;; Extract "prog" from lines of the form: + ;; "complete -F _prog -o filenames prog". + ;; Note that 'burnin' is listed with the + ;; absolute file name, which is why we + ;; run everything through 'basename'. + (cons (basename (car (reverse (string-split + line #\ )))) + progs)) + (loop (read-line port) progs)))))))) + #t))) + ;; Wrap all executables with PYTHONPATH. We can't borrow the phase + ;; from python-build-system because we also need to wrap the scripts + ;; in $out/lib/ganeti such as "node-daemon-setup". + (add-after 'install 'wrap + (lambda* (#:key inputs outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (sbin (string-append out "/sbin")) + (lib (string-append out "/lib")) + (python (assoc-ref inputs "python")) + (major+minor (python-version python)) + (PYTHONPATH (string-append lib "/python" major+minor + "/site-packages:" + (getenv "PYTHONPATH")))) + (define (shell-script? file) + (call-with-ascii-input-file file + (lambda (port) + (let ((shebang (false-if-exception (read-line port)))) + (and shebang + (string-prefix? "#!" shebang) + (or (string-contains shebang "/bin/bash") + (string-contains shebang "/bin/sh"))))))) + + (define (wrap? file) + ;; Do not wrap shell scripts because some are meant to be + ;; sourced, which breaks if they are wrapped. We do wrap + ;; the Haskell executables because some call out to Python + ;; directly. + (and (executable-file? file) + (not (symbolic-link? file)) + (not (shell-script? file)))) + + (for-each (lambda (file) + (wrap-program file + `("PYTHONPATH" ":" prefix (,PYTHONPATH)))) + (filter wrap? + (append (find-files (string-append lib "/ganeti")) + (find-files sbin)))) + #t)))))) + (native-inputs + `(("haskell" ,ghc) + ("cabal" ,cabal-install) + ("m4" ,m4) + + ;; These inputs are necessary to bootstrap the package, because we + ;; have patched the build system. + ("autoconf" ,autoconf) + ("automake" ,automake) + + ;; For the documentation. + ("python-docutils" ,python-docutils) + ("sphinx" ,python-sphinx) + ("pandoc" ,ghc-pandoc) + ("dot" ,graphviz) + + ;; Test dependencies. + ("fakeroot" ,fakeroot) + ("ghc-temporary" ,ghc-temporary) + ("ghc-test-framework" ,ghc-test-framework) + ("ghc-test-framework-hunit" ,ghc-test-framework-hunit) + ("ghc-test-framework-quickcheck2" ,ghc-test-framework-quickcheck2) + ("python-mock" ,python-mock) + ("python-pyyaml" ,python-pyyaml) + ("openssh" ,openssh) + ("procps" ,procps) + ("shelltestrunner" ,shelltestrunner) + ("tzdata" ,tzdata-for-tests))) + (inputs + `(("arping" ,iputils) ;must be the iputils version + ("curl" ,curl) + ("fping" ,fping) + ("iproute2" ,iproute) + ("ndisc6" ,ndisc6) + ("socat" ,socat) + ("qemu" ,qemu-minimal) ;for qemu-img + ("ghc-attoparsec" ,ghc-attoparsec) + ("ghc-base64-bytestring" ,ghc-base64-bytestring) + ("ghc-cryptonite" ,ghc-cryptonite) + ("ghc-curl" ,ghc-curl) + ("ghc-hinotify" ,ghc-hinotify) + ("ghc-hslogger" ,ghc-hslogger) + ("ghc-json" ,ghc-json) + ("ghc-lens" ,ghc-lens) + ("ghc-lifted-base" ,ghc-lifted-base) + ("ghc-network" ,ghc-network) + ("ghc-old-time" ,ghc-old-time) + ("ghc-psqueue" ,ghc-psqueue) + ("ghc-regex-pcre" ,ghc-regex-pcre) + ("ghc-utf8-string" ,ghc-utf8-string) + ("ghc-zlib" ,ghc-zlib) + + ;; For the optional metadata daemon. + ("ghc-snap-core" ,ghc-snap-core) + ("ghc-snap-server" ,ghc-snap-server) + + ("python" ,python) + ("python-pyopenssl" ,python-pyopenssl) + ("python-simplejson" ,python-simplejson) + ("python-pyparsing" ,python-pyparsing) + ("python-pyinotify" ,python-pyinotify) + ("python-pycurl" ,python-pycurl) + ("python-bitarray" ,python-bitarray) + ("python-paramiko" ,python-paramiko) + ("python-psutil" ,python-psutil))) + (home-page "http://www.ganeti.org/") + (synopsis "Cluster-based virtual machine management system") + (description + "Ganeti is a virtual machine management tool built on top of existing +virtualization technologies such as Xen or KVM. Ganeti controls: + +@itemize @bullet +@item Disk creation management; +@item Operating system installation for instances (in co-operation with +OS-specific install scripts); and +@item Startup, shutdown, and failover between physical systems. +@end itemize + +Ganeti is designed to facilitate cluster management of virtual servers and +to provide fast and simple recovery after physical failures, using +commodity hardware.") + (license license:bsd-2))) + (define-public libosinfo (package (name "libosinfo") -- cgit v1.2.3