[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
01/04: gnu: Add ganeti.
From: |
guix-commits |
Subject: |
01/04: gnu: Add ganeti. |
Date: |
Thu, 16 Jul 2020 16:28:31 -0400 (EDT) |
mbakke pushed a commit to branch master
in repository guix.
commit 72a91d74cec01bfcfcef2b62c5b327fab82950b6
Author: Marius Bakke <marius@gnu.org>
AuthorDate: Fri Jul 3 00:28:57 2020 +0200
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(+)
diff --git a/gnu/local.mk b/gnu/local.mk
index f7c6036..c36fa1e 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 0000000..2d90aa7
--- /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 0000000..a5f347c
--- /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 0000000..32f46bc
--- /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 0000000..fa77771
--- /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 0000000..16b1d76
--- /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 0000000..1358e30
--- /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 0000000..36a7918
--- /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 0000000..f750604
--- /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
diff --git a/gnu/packages/virtualization.scm b/gnu/packages/virtualization.scm
index f4df76f..d4ef9cc 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: <https://github.com/haskell/cabal/issues/3728>.
+ ;; 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")