From 1da6914589e06621a78ce0ed13f707cc767c2a3d Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 26 Jun 2020 14:44:12 +0200 Subject: tests: install: Fix marionette race condition. If the marionette shuts down before the script return is received, then status will be . * gnu/tests/install.scm (run-install): Allow status to be the object. --- gnu/tests/install.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gnu/tests') diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm index db355b85ca..3323f8c9d8 100644 --- a/gnu/tests/install.scm +++ b/gnu/tests/install.scm @@ -301,7 +301,8 @@ packages defined in installation-os." ;; Run SCRIPT. It typically invokes 'reboot' as a last step and ;; thus normally gets killed with SIGTERM by PID 1. (let ((status (marionette-eval '(system #$script) marionette))) - (exit (or (equal? (status:term-sig status) SIGTERM) + (exit (or (eof-object? status) + (equal? (status:term-sig status) SIGTERM) (equal? (status:exit-val status) 0))))) (when #$(->bool gui-test) -- cgit v1.2.3 From 544fd8e3b5b661125ff0e2bd27598f9deef11fca Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 26 Jun 2020 15:00:08 +0200 Subject: tests: install: Disable image compression. * gnu/tests/install.scm (run-install): Disable image compression to speed-up the tests. --- gnu/tests/install.scm | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gnu/tests') diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm index 3323f8c9d8..b2edfa5c22 100644 --- a/gnu/tests/install.scm +++ b/gnu/tests/install.scm @@ -247,6 +247,8 @@ packages defined in installation-os." (operating-system (operating-system-with-gc-roots os (list target guile-final))) + ;; Do not compress to speed-up the tests. + (compression? #f) ;; Don't provide substitutes; too big. (substitutable? #f))))) (define install -- cgit v1.2.3 From 5e766fe5a2b19a33e1f2589ede37da674aada4c6 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Tue, 30 Jun 2020 14:31:45 +0200 Subject: tests: install: Increase image size limit. Commits 0eed77127592323d89f56c215a15374a1aaae110 and 614a1e3fa2d731d4719f03912b1b87fb4fd309cb caused a ~150M increase of the image size. Increase the image size limit by 200M until the situation is addressed. * gnu/tests/install.scm (%simple-installation-script, %extlinux-gpt-installation-script, %simple-installation-script-for-/dev/vda, %raid-root-installation-script, %encrypted-root-installation-script): Increase image size limit by 200M. --- gnu/tests/install.scm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm index b2edfa5c22..9656e5f41f 100644 --- a/gnu/tests/install.scm +++ b/gnu/tests/install.scm @@ -163,7 +163,7 @@ export GUIX_BUILD_OPTIONS=--no-grafts guix build isc-dhcp parted --script /dev/vdb mklabel gpt \\ mkpart primary ext2 1M 3M \\ - mkpart primary ext2 3M 1.4G \\ + mkpart primary ext2 3M 1.6G \\ set 1 boot on \\ set 1 bios_grub on mkfs.ext4 -L my-root /dev/vdb2 @@ -188,7 +188,7 @@ guix --version export GUIX_BUILD_OPTIONS=--no-grafts guix build isc-dhcp parted --script /dev/vdb mklabel gpt \\ - mkpart ext2 1M 1.4G \\ + mkpart ext2 1M 1.6G \\ set 1 legacy_boot on mkfs.ext4 -L my-root -O '^64bit' /dev/vdb1 mount /dev/vdb1 /mnt @@ -419,7 +419,7 @@ export GUIX_BUILD_OPTIONS=--no-grafts guix build isc-dhcp parted --script /dev/vda mklabel gpt \\ mkpart primary ext2 1M 3M \\ - mkpart primary ext2 3M 1.4G \\ + mkpart primary ext2 3M 1.6G \\ set 1 boot on \\ set 1 bios_grub on mkfs.ext4 -L my-root /dev/vda2 @@ -631,8 +631,8 @@ guix --version export GUIX_BUILD_OPTIONS=--no-grafts parted --script /dev/vdb mklabel gpt \\ mkpart primary ext2 1M 3M \\ - mkpart primary ext2 3M 1.4G \\ - mkpart primary ext2 1.4G 2.8G \\ + mkpart primary ext2 3M 1.6G \\ + mkpart primary ext2 1.6G 3.2G \\ set 1 boot on \\ set 1 bios_grub on yes | mdadm --create /dev/md0 --verbose --level=mirror --raid-devices=2 \\ @@ -658,7 +658,7 @@ by 'mdadm'.") %raid-root-os-source #:script %raid-root-installation-script - #:target-size (* 2800 MiB))) + #:target-size (* 3200 MiB))) (command (qemu-command/writable-image image))) (run-basic-test %raid-root-os `(,@command) "raid-root-os"))))) @@ -719,7 +719,7 @@ export GUIX_BUILD_OPTIONS=--no-grafts ls -l /run/current-system/gc-roots parted --script /dev/vdb mklabel gpt \\ mkpart primary ext2 1M 3M \\ - mkpart primary ext2 3M 1.4G \\ + mkpart primary ext2 3M 1.6G \\ set 1 boot on \\ set 1 bios_grub on echo -n " %luks-passphrase " | \\ -- cgit v1.2.3 From 54461153dac6096a018e35a1d959e45b2c9dbb82 Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Sat, 11 Jul 2020 23:32:28 +0200 Subject: tests: networking: Use 'net.ifnames=0' for the Open vSwitch test. * gnu/tests/networking.scm (%openvswitch-os): Override KERNEL-ARGUMENTS. --- gnu/tests/networking.scm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/networking.scm b/gnu/tests/networking.scm index ca18b2f452..022663aa67 100644 --- a/gnu/tests/networking.scm +++ b/gnu/tests/networking.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2017 Thomas Danckaert -;;; Copyright © 2017 Marius Bakke +;;; Copyright © 2017, 2020 Marius Bakke ;;; Copyright © 2018 Chris Marusich ;;; Copyright © 2018 Arun Isaac ;;; @@ -174,12 +174,15 @@ port 7, and a dict service on port 2628." (respawn? #f))))) (define %openvswitch-os - (simple-operating-system - (static-networking-service "ovs0" "10.1.1.1" - #:netmask "255.255.255.252" - #:requirement '(openvswitch-configuration)) - (service openvswitch-service-type) - openvswitch-configuration-service)) + (operating-system + (inherit (simple-operating-system + (static-networking-service "ovs0" "10.1.1.1" + #:netmask "255.255.255.252" + #:requirement '(openvswitch-configuration)) + (service openvswitch-service-type) + openvswitch-configuration-service)) + ;; Ensure the interface name does not change depending on the driver. + (kernel-arguments (cons "net.ifnames=0" %default-kernel-arguments)))) (define (run-openvswitch-test) (define os -- cgit v1.2.3 From 5cd9cd644ca493b230cb229e7c46641e94a4e2fa Mon Sep 17 00:00:00 2001 From: Julien Lepiller Date: Mon, 13 Jul 2020 04:20:50 +0200 Subject: gnu: tests: Fix unbound variable. Record type descriptors were made private in a143e92446859bd1edc7a7aea85b2089c82c77da, but a usage of them was forgotten in the tests files. * gnu/tests/web.scm (patchwork-initial-database-setup-service): Use accessors to access field values instead of unexported type descriptor. --- gnu/tests/web.scm | 73 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/web.scm b/gnu/tests/web.scm index 1c984dd6f4..7513eab2e4 100644 --- a/gnu/tests/web.scm +++ b/gnu/tests/web.scm @@ -521,42 +521,43 @@ HTTP-PORT." ;;; Patchwork ;;; -(define patchwork-initial-database-setup-service - (match-lambda - (($ - engine name user password host port) - - (define start-gexp - #~(lambda () - (let ((pid (primitive-fork)) - (postgres (getpwnam "postgres"))) - (if (eq? pid 0) - (dynamic-wind - (const #t) - (lambda () - (setgid (passwd:gid postgres)) - (setuid (passwd:uid postgres)) - (primitive-exit - (if (and - (zero? - (system* #$(file-append postgresql "/bin/createuser") - #$user)) - (zero? - (system* #$(file-append postgresql "/bin/createdb") - "-O" #$user #$name))) - 0 - 1))) - (lambda () - (primitive-exit 1))) - (zero? (cdr (waitpid pid))))))) - - (shepherd-service - (requirement '(postgres)) - (provision '(patchwork-postgresql-user-and-database)) - (start start-gexp) - (stop #~(const #f)) - (respawn? #f) - (documentation "Setup patchwork database."))))) +(define (patchwork-initial-database-setup-service configuration) + (define start-gexp + #~(lambda () + (let ((pid (primitive-fork)) + (postgres (getpwnam "postgres"))) + (if (eq? pid 0) + (dynamic-wind + (const #t) + (lambda () + (setgid (passwd:gid postgres)) + (setuid (passwd:uid postgres)) + (primitive-exit + (if (and + (zero? + (system* #$(file-append postgresql "/bin/createuser") + #$(patchwork-database-configuration-user + configuration))) + (zero? + (system* #$(file-append postgresql "/bin/createdb") + "-O" + #$(patchwork-database-configuration-user + configuration) + #$(patchwork-database-configuration-name + configuration)))) + 0 + 1))) + (lambda () + (primitive-exit 1))) + (zero? (cdr (waitpid pid))))))) + + (shepherd-service + (requirement '(postgres)) + (provision '(patchwork-postgresql-user-and-database)) + (start start-gexp) + (stop #~(const #f)) + (respawn? #f) + (documentation "Setup patchwork database."))) (define (patchwork-os patchwork) (simple-operating-system -- cgit v1.2.3 From 37fea3c30a79d2c401dd64b446a0fbfe4a14b966 Mon Sep 17 00:00:00 2001 From: Oleg Pykhalov Date: Sat, 11 Jul 2020 14:01:44 +0300 Subject: tests: zabbix: Fix typo in comment. * gnu/tests/monitoring.scm (run-zabbix-server-test)[test]: Fix typo in comment. --- gnu/tests/monitoring.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gnu/tests') diff --git a/gnu/tests/monitoring.scm b/gnu/tests/monitoring.scm index 732fbc54d7..d20b8ac59e 100644 --- a/gnu/tests/monitoring.scm +++ b/gnu/tests/monitoring.scm @@ -280,7 +280,7 @@ zabbix||{} '(file-exists? "/var/run/nginx/pid") marionette)) - ;; Make sure we can access pages that correspond to our repository. + ;; Make sure we can access pages that correspond to our Zabbix instance. (letrec-syntax ((test-url (syntax-rules () ((_ path code) -- cgit v1.2.3 From 9a622827559b223cc6b48733a82c74ce14a29bab Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Sat, 4 Jul 2020 00:30:19 +0200 Subject: services: Add ganeti. * gnu/services/ganeti.scm, gnu/tests/ganeti.scm: New files. * doc/guix.texi (Virtualization Services): Document the Ganeti services. --- doc/guix.texi | 646 +++++++++++++++++++++++++++ gnu/local.mk | 2 + gnu/services/ganeti.scm | 1109 +++++++++++++++++++++++++++++++++++++++++++++++ gnu/tests/ganeti.scm | 265 +++++++++++ 4 files changed, 2022 insertions(+) create mode 100644 gnu/services/ganeti.scm create mode 100644 gnu/tests/ganeti.scm (limited to 'gnu/tests') diff --git a/doc/guix.texi b/doc/guix.texi index 17338ed764..a6ee679b11 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -24975,6 +24975,652 @@ the @code{--snapshot} flag using something along these lines: (options '("--hda")))) @end lisp +@subsubheading Ganeti + +@cindex ganeti + +@quotation Note +This service is considered experimental. Configuration options may be changed +in a backwards-incompatible manner, and not all features have been thorougly +tested. Users of this service are encouraged to share their experience at +@email{guix-devel@@gnu.org}. +@end quotation + +Ganeti is a virtual machine management system. It is designed to keep virtual +machines running on a cluster of servers even in the event of hardware failures, +and to make maintenance and recovery tasks easy. It consists of multiple +services which are described later in this section. In addition to the Ganeti +service, you will need the OpenSSH service (@pxref{Networking Services, +@code{openssh-service-type}}), and update the @file{/etc/hosts} file +(@pxref{operating-system Reference, @code{hosts-file}}) with the cluster name +and address (or use a DNS server). + +All nodes participating in a Ganeti cluster should have the same Ganeti and +@file{/etc/hosts} configuration. Here is an example configuration for a Ganeti +cluster node that supports multiple storage backends, and installs the +@code{debootstrap} and @code{guix} @dfn{OS providers}: + +@lisp +(use-package-modules virtualization) +(use-service-modules base ganeti networking ssh) +(operating-system + ;; @dots{} + (host-name "node1") + (hosts-file (plain-file "hosts" (format #f " +127.0.0.1 localhost +::1 localhost + +192.168.1.200 ganeti.example.com +192.168.1.201 node1.example.com node1 +192.168.1.202 node2.example.com node2 +"))) + + ;; Install QEMU so we can use KVM-based instances, and LVM, DRBD and Ceph + ;; in order to use the "plain", "drbd" and "rbd" storage backends. + (packages (append (map specification->package + '("qemu" "lvm2" "drbd-utils" "ceph" + ;; Add the debootstrap and guix OS providers. + "ganeti-instance-guix" "ganeti-instance-debootstrap")) + %base-packages)) + (services + (append (list (static-networking-service "eth0" "192.168.1.201" + #:netmask "255.255.255.0" + #:gateway "192.168.1.254" + #:name-servers '("192.168.1.252" + "192.168.1.253")) + + ;; Ganeti uses SSH to communicate between nodes. + (service openssh-service-type + (openssh-configuration + (permit-root-login 'without-password))) + + (service ganeti-service-type + (ganeti-configuration + ;; This list specifies allowed file system paths + ;; for storing virtual machine images. + (file-storage-paths '("/srv/ganeti/file-storage")) + ;; This variable configures a single "variant" for + ;; both Debootstrap and Guix that works with KVM. + (os %default-ganeti-os)))) + %base-services))) +@end lisp + +Users are advised to read the +@url{http://docs.ganeti.org/ganeti/master/html/admin.html,Ganeti +administrators guide} to learn about the various cluster options and +day-to-day operations. There is also a +@url{https://guix.gnu.org/blog/2020/ganeti-on-guix/,blog post} +describing how to configure a small cluster. + +@defvr {Scheme Variable} ganeti-service-type +This is a service type that includes all the various services that Ganeti +nodes should run. + +Its value is a @code{ganeti-configuration} object that defines the package +to use for CLI operations, as well as configuration for the various daemons. +@end defvr + +@deftp {Data Type} ganeti-configuration +The @code{ganeti} service takes the following configuration options: + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use. It will be installed to the system profile +and make @command{gnt-cluster}, @command{gnt-instance}, etc available. Note +that the value specified here does not affect the other services as each refer +to a specific @code{ganeti} package (see below). + +@item @code{noded-configuration} (default: @code{(ganeti-noded-configuration)}) +@itemx @code{confd-configuration} (default: @code{(ganeti-confd-configuration)}) +@itemx @code{wconfd-configuration} (default: @code{(ganeti-wconfd-configuration)}) +@itemx @code{luxid-configuration} (default: @code{(ganeti-luxid-configuration)}) +@itemx @code{rapi-configuration} (default: @code{(ganeti-rapi-configuration)}) +@itemx @code{kvmd-configuration} (default: @code{(ganeti-kvmd-configuration)}) +@itemx @code{mond-configuration} (default: @code{(ganeti-mond-configuration)}) +@itemx @code{metad-configuration} (default: @code{(ganeti-metad-configuration)}) +@itemx @code{watcher-configuration} (default: @code{(ganeti-watcher-configuration)}) +@itemx @code{cleaner-configuration} (default: @code{(ganeti-cleaner-configuration)}) + +These options control the various daemons and cron jobs that are distributed +with Ganeti. The possible values for these are described in detail below. +To override a setting, you must use the configuration type for that service: + +@lisp +(service ganeti-service-type + (ganeti-configuration + (rapi-configuration + (ganeti-rapi-configuration + (interface "eth1")))) + (watcher-configuration + (ganeti-watcher-configuration + (rapi-ip "10.0.0.1")))) +@end lisp + +@item @code{file-storage-paths} (default: @code{'()}) +List of allowed directories for file storage backend. + +@item @code{os} (default: @code{%default-ganeti-os}) +List of @code{} records. +@end table + +In essence @code{ganeti-service-type} is shorthand for declaring each service +individually: + +@lisp +(service ganeti-noded-service-type) +(service ganeti-confd-service-type) +(service ganeti-wconfd-service-type) +(service ganeti-luxid-service-type) +(service ganeti-kvmd-service-type) +(service ganeti-mond-service-type) +(service ganeti-metad-service-type) +(service ganeti-watcher-service-type) +(service ganeti-cleaner-service-type) +@end lisp + +Plus a service extension for @code{etc-service-type} that configures the file +storage backend and OS variants. + +@end deftp + +@deftp {Data Type} ganeti-os +This data type is suitable for passing to the @code{os} configuration of +Ganeti. It takes the following parameters: + +@table @asis +@item @code{name} +The name for this OS provider. It is only used to specify where the +configuration ends up. Setting it to ``debootstrap'' will create +@file{/etc/ganeti/instance-debootstrap}. + +@item @code{extension} +The file extension for variants of this OS type. For example +@file{.conf} or @file{.scm}. + +@item @code{variants} (default: @code{'()}) +List of @code{ganeti-os-variant} objects for this OS. + +@end table +@end deftp + +@deftp {Data Type} ganeti-os-variant +This is the data type for a Ganeti OS variant. It takes the following +parameters: + +@table @asis +@item @code{name} +The name of this variant. + +@item @code{configuration} +A configuration file for this variant. +@end table +@end deftp + +@defvr {Scheme Variable} %default-debootstrap-hooks +This variable contains hooks to configure networking and the GRUB bootloader. +@end defvr + +@defvr {Scheme Variable} %default-debootstrap-extra-pkgs +This variable contains a list of packages suitable for a fully-virtualized guest. +@end defvr + +@deftp {Data Type} debootstrap-configuration + +This data type creates configuration files suitable for the debootstrap OS provider. + +@table @asis +@item @code{hooks} (default: @code{%default-debootstrap-hooks}) +When not @code{#f}, this must be a G-expression that specifies a directory with +scripts that will run when the OS is installed. It can also be a list of +@code{(name . file-like)} pairs. For example: + +@lisp + +`((99-hello-world . ,(plain-file "#!/bin/sh\necho Hello, World"))) + +@end lisp + +That will create a directory with one executable named @code{99-hello-world} +and run it every time this variant is installed. If set to @code{#f}, hooks +in @file{/etc/ganeti/instance-debootstrap/hooks} will be used, if any. +@item @code{proxy} (default: @code{#f}) +HTTP proxy to use, if any. +@item @code{mirror} (default: @code{#f}) +The Debian mirror. Typically something like @code{http://ftp.no.debian.org/debian}. +The default varies depending on the distribution. +@item @code{arch} (default: @code{#f}) +The dpkg architecture. Set to @code{armhf} to debootstrap an ARMv7 instance +on an AArch64 host. Default is to use the current system architecture. +@item @code{suite} (default: @code{"stable"}) +When set, this must be a Debian distribution ``suite'' such as @code{buster} +or @code{focal}. If set to @code{#f}, the default for the OS provider is used. +@item @code{extra-pkgs} (default: @code{%default-debootstrap-extra-pkgs}) +List of extra packages that will get installed by dpkg in addition +to the minimal system. +@item @code{components} (default: @code{#f}) +When set, must be a list of Debian repository ``components''. For example +@code{'("main" "contrib")}. +@item @code{generate-cache?} (default: @code{#t}) +Whether to automatically cache the generated debootstrap archive. +@item @code{clean-cache} (default: @code{14}) +Discard the cache after this amount of days. Use @code{#f} to never +clear the cache. +@item @code{partition-style} (default: @code{'msdos}) +The type of partition to create. When set, it must be one of +@code{'msdos}, @code{'none} or a string. +@item @code{partition-alignment} (default: @code{2048}) +Alignment of the partition in sectors. +@end table +@end deftp + +@deffn {Scheme Procedure} debootstrap-variant +This is a helper procedure that creates a @code{ganeti-os-variant} record. It +takes two parameters: a name and a @code{debootstrap-configuration} object. +@end deffn + +@deffn {Scheme Procedure} debootstrap-os +This is a helper procedure that creates a @code{ganeti-os} record. It takes +a list of variants created with @code{debootstrap-variant}. +@end deffn + +@deffn {Scheme Procedure} guix-variant +This is a helper procedure that creates a @code{ganeti-os-variant} record for +use with the Guix OS provider. It takes a name and a G-expression that returns +a ``file-like'' (@pxref{G-Expressions, file-like objects}) object containing a +Guix System configuration. +@end deffn + +@deffn {Scheme Procedure} guix-os +This is a helper procedure that creates a @code{ganeti-os} record. It +takes a list of variants produced by @code{guix-variant}. +@end deffn + +@defvr {Scheme Variable} %default-debootstrap-variants +This is a convenience variable to make the debootstrap provider work +``out of the box'' without users having to declare variants manually. It +contains a single debootstrap variant with the default configuration: + +@lisp +(list (debootstrap-variant + "default" + (debootstrap-configuration)))) +@end lisp +@end defvr + +@defvr {Scheme Variable} %default-guix-variants +This is a convenience variable to make the Guix OS provider work without +additional configuration. It creates a virtual machine that has an SSH +server, a serial console, and authorizes the Ganeti hosts SSH keys. + +@lisp +(list (guix-variant + "default" + (file-append ganeti-instance-guix + "/share/doc/ganeti-instance-guix/examples/dynamic.scm")))) +@end lisp +@end defvr + +Users can implement support for OS providers unbeknownst to Guix by extending +the @code{ganeti-os} and @code{ganeti-os-variant} records appropriately. +For example: + +@lisp +(ganeti-os + (name "custom") + (extension ".conf") + (variants + (list (ganeti-os-variant + (name "foo") + (configuration (plain-file "bar" "this is fine")))))) +@end lisp + +That creates @file{/etc/ganeti/instance-custom/variants/foo.conf} which points +to a file in the store with contents @code{this is fine}. It also creates +@file{/etc/ganeti/instance-custom/variants/variants.list} with contents @code{foo}. + +Obviously this may not work for all OS providers out there. If you find the +interface limiting, please reach out to @email{guix-devel@@gnu.org}. + +The rest of this section documents the various services that are included by +@code{ganeti-service-type}. + +@defvr {Scheme Variable} ganeti-noded-service-type +@command{ganeti-noded} is the daemon responsible for node-specific functions +within the Ganeti system. The value of this service must be a +@code{ganeti-noded-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-noded-configuration +This is the configuration for the @code{ganeti-noded} service. + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{port} (default: @code{1811}) +The TCP port on which the node daemon listens for network requests. + +@item @code{address} (default: @code{"0.0.0.0"}) +The network address that the daemon will bind to. The default address means +bind to all available addresses. + +@item @code{interface} (default: @code{#f}) +When this is set, it must be a specific network interface (e.g.@: @code{eth0}) +that the daemon will bind to. + +@item @code{max-clients} (default: @code{20}) +This sets a limit on the maximum number of simultaneous client connections +that the daemon will handle. Connections above this count are accepted, but +no responses will be sent until enough connections have closed. + +@item @code{ssl?} (default: @code{#t}) +Whether to use SSL/TLS to encrypt network communications. The certificate +is automatically provisioned by the cluster and can be rotated with +@command{gnt-cluster renew-crypto}. + +@item @code{ssl-key} (default: @file{"/var/lib/ganeti/server.pem"}) +This can be used to provide a specific encryption key for TLS communications. + +@item @code{ssl-cert} (default: @file{"/var/lib/ganeti/server.pem"}) +This can be used to provide a specific certificate for TLS communications. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. +Note that this will leak encryption details to the log files, use with caution. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-confd-service-type +@command{ganeti-confd} answers queries related to the configuration of a +Ganeti cluster. The purpose of this daemon is to have a highly available +and fast way to query cluster configuration values. It is automatically +active on all @dfn{master candidates}. The value of this service must be a +@code{ganeti-confd-configuration} object. + +@end defvr + +@deftp {Data Type} ganeti-confd-configuration +This is the configuration for the @code{ganeti-confd} service. + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{port} (default: @code{1814}) +The UDP port on which to listen for network requests. + +@item @code{address} (default: @code{"0.0.0.0"}) +Network address that the daemon will bind to. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-wconfd-service-type +@command{ganeti-wconfd} is the daemon that has authoritative knowledge +about the cluster configuration and is the only entity that can accept +changes to it. All jobs that need to modify the configuration will do so +by sending appropriate requests to this daemon. It only runs on the +@dfn{master node} and will automatically disable itself on other nodes. + +The value of this service must be a +@code{ganeti-wconfd-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-wconfd-configuration +This is the configuration for the @code{ganeti-wconfd} service. + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{no-voting?} (default: @code{#f}) +The daemon will refuse to start if the majority of cluster nodes does not +agree that it is running on the master node. Set to @code{#t} to start +even if a quorum can not be reached (dangerous, use with caution). + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-luxid-service-type +@command{ganeti-luxid} is a daemon used to answer queries related to the +configuration and the current live state of a Ganeti cluster. Additionally, +it is the authorative daemon for the Ganeti job queue. Jobs can be +submitted via this daemon and it schedules and starts them. + +It takes a @code{ganeti-luxid-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-luxid-configuration +This is the configuration for the @code{ganeti-wconfd} service. + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{no-voting?} (default: @code{#f}) +The daemon will refuse to start if it cannot verify that the majority of +cluster nodes believes that it is running on the master node. Set to +@code{#t} to ignore such checks and start anyway (this can be dangerous). + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-rapi-service-type +@command{ganeti-rapi} provides a remote API for Ganeti clusters. It runs on +the master node and can be used to perform cluster actions programmatically +via a JSON-based RPC protocol. + +Most query operations are allowed without authentication (unless +@var{require-authentication?} is set), whereas write operations require +explicit authorization via the @file{/var/lib/ganeti/rapi/users} file. See +the @url{http://docs.ganeti.org/ganeti/master/html/rapi.html, Ganeti Remote +API documentation} for more information. + +The value of this service must be a @code{ganeti-rapi-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-rapi-configuration +This is the configuration for the @code{ganeti-rapi} service. + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{require-authentication?} (default: @code{#f}) +Whether to require authentication even for read-only operations. + +@item @code{port} (default: @code{5080}) +The TCP port on which to listen to API requests. + +@item @code{address} (default: @code{"0.0.0.0"}) +The network address that the service will bind to. By default it listens +on all configured addresses. + +@item @code{interface} (default: @code{#f}) +When set, it must specify a specific network interface such as @code{eth0} +that the daemon will bind to. + +@item @code{max-clients} (default: @code{20}) +The maximum number of simultaneous client requests to handle. Further +connections are allowed, but no responses are sent until enough connections +have closed. + +@item @code{ssl?} (default: @code{#f}) +Whether to use SSL/TLS encryption on the RAPI port. + +@item @code{ssl-key} (default: @file{"/var/lib/ganeti/server.pem"}) +This can be used to provide a specific encryption key for TLS communications. + +@item @code{ssl-cert} (default: @file{"/var/lib/ganeti/server.pem"}) +This can be used to provide a specific certificate for TLS communications. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. +Note that this will leak encryption details to the log files, use with caution. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-kvmd-service-type +@command{ganeti-kvmd} is responsible for determining whether a given KVM +instance was shut down by an administrator or a user. Normally Ganeti will +restart an instance that was not stopped through Ganeti itself. If the +cluster option @code{user_shutdown} is true, this daemon monitors the +@code{QMP} socket provided by QEMU and listens for shutdown events, and +marks the instance as @dfn{USER_down} instead of @dfn{ERROR_down} when +it shuts down gracefully by itself. + +It takes a @code{ganeti-kvmd-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-kvmd-configuration + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-mond-service-type +@command{ganeti-mond} is an optional daemon that provides Ganeti monitoring +functionality. It is responsible for running data collectors and publish the +collected information through a HTTP interface. + +It takes a @code{ganeti-mond-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-mond-configuration + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{port} (default: @code{1815}) +The port on which the daemon will listen. + +@item @code{address} (default: @code{"0.0.0.0"}) +The network address that the daemon will bind to. By default it binds to all +available interfaces. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-metad-service-type +@command{ganeti-metad} is an optional daemon that can be used to provide +information about the cluster to instances or OS install scripts. It is +not included in @code{ganeti-service-type} because using it requires +additional configuration and support in OS providers. + +It takes a @code{ganeti-metad-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-metad-configuration + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{port} (default: @code{80}) +The port on which the daemon will listen. + +@item @code{address} (default: @code{#f}) +If set, the daemon will bind to this address only. If left unset, the behavior +depends on the cluster configuration. + +@item @code{debug?} (default: @code{#f}) +When true, the daemon performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-watcher-service-type +@command{ganeti-watcher} is a script designed to run periodically and ensure +the health of a cluster. It will automatically restart instances that have +stopped without Ganetis consent, and repairs DRBD links in case a node has +rebooted. It also archives old cluster jobs and restarts Ganeti daemons +that are not running. If the cluster parameter @code{ensure_node_health} +is set, the watcher will also shutdown instances and DRBD devices if the +node it is running on is declared offline by known master candidates. + +It can be paused on all nodes with @command{gnt-cluster watcher pause}. + +The service takes a @code{ganeti-watcher-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-watcher-configuration + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for this service. + +@item @code{schedule} (default: @code{'(next-second-from (next-minute (range 0 60 5)))}) +How often to run the script. The default is every five minutes. + +@item @code{rapi-ip} (default: @code{#f}) +This option needs to be specified only if the RAPI daemon is configured to use +a particular interface or address. By default the cluster address is used. + +@item @code{job-age} (default: @code{(* 6 3600)}) +Archive cluster jobs older than this age, specified in seconds. The default +is 6 hours. This keeps @command{gnt-job list} manageable. + +@item @code{verify-disks?} (default: @code{#t}) +If this is @code{#f}, the watcher will not try to repair broken DRBD links +automatically. Administrators will need to use @command{gnt-cluster verify-disks} +manually instead. + +@item @code{debug?} (default: @code{#f}) +When @code{#t}, the script performs additional logging for debugging purposes. + +@end table +@end deftp + +@defvr {Scheme Variable} ganeti-cleaner-service-type +@command{ganeti-cleaner} is a script designed to run periodically and remove +old files from the cluster. This service type controls two @dfn{cron jobs}: +one intended for the master node that permanently purges old cluster jobs, +and one intended for every node that removes expired X509 certificates, keys, +and outdated @command{ganeti-watcher} information. Like all Ganeti services, +it is safe to include even on non-master nodes as it will disable itself as +necessary. + +It takes a @code{ganeti-cleaner-configuration} object. +@end defvr + +@deftp {Data Type} ganeti-cleaner-configuration + +@table @asis +@item @code{ganeti} (default: @code{ganeti}) +The @code{ganeti} package to use for the @command{gnt-cleaner} command. + +@item @code{master-schedule} (default: @code{"45 1 * * *"}) +How often to run the master cleaning job. The default is once per day, at +01:45:00. + +@item @code{node-schedule} (default: @code{"45 2 * * *"}) +How often to run the node cleaning job. The default is once per day, at +02:45:00. + +@end table +@end deftp + @node Version Control Services @subsection Version Control Services diff --git a/gnu/local.mk b/gnu/local.mk index c36fa1ea5e..7f4ff1f695 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -586,6 +586,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/docker.scm \ %D%/services/authentication.scm \ %D%/services/games.scm \ + %D%/services/ganeti.scm \ %D%/services/getmail.scm \ %D%/services/guix.scm \ %D%/services/hurd.scm \ @@ -662,6 +663,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/desktop.scm \ %D%/tests/dict.scm \ %D%/tests/docker.scm \ + %D%/tests/ganeti.scm \ %D%/tests/guix.scm \ %D%/tests/monitoring.scm \ %D%/tests/nfs.scm \ diff --git a/gnu/services/ganeti.scm b/gnu/services/ganeti.scm new file mode 100644 index 0000000000..80a61818f7 --- /dev/null +++ b/gnu/services/ganeti.scm @@ -0,0 +1,1109 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2020 Marius Bakke +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu services ganeti) + #:use-module (gnu packages virtualization) + #:use-module (gnu services) + #:use-module (gnu services mcron) + #:use-module (gnu services shepherd) + #:use-module (guix gexp) + #:use-module (guix records) + + #:use-module (srfi srfi-1) + #:use-module (ice-9 match) + + #:export (ganeti-noded-configuration + ganeti-noded-configuration? + ganeti-noded-configuration-ganeti + ganeti-noded-configuration-port + ganeti-noded-configuration-address + ganeti-noded-configuration-interface + ganeti-noded-configuration-max-clients + ganeti-noded-configuration-ssl? + ganeti-noded-configuration-ssl-key + ganeti-noded-configuration-ssl-cert + ganeti-noded-configuration-debug? + ganeti-noded-service-type + + ganeti-confd-configuration + ganeti-confd-configuration? + ganeti-confd-configuration-ganeti + ganeti-confd-configuration-port + ganeti-confd-configuration-address + ganeti-confd-configuration-debug + ganeti-confd-service-type + + ganeti-wconfd-configuration + ganeti-wconfd-configuration? + ganeti-wconfd-configuration-ganeti + ganeti-wconfd-configuration-no-voting? + ganeti-wconfd-configuration-debug? + ganeti-wconfd-service-type + + ganeti-luxid-configuration + ganeti-luxid-configuration? + ganeti-luxid-configuration-ganeti + ganeti-luxid-configuration-no-voting? + ganeti-luxid-configuration-debug? + ganeti-luxid-service-type + + ganeti-rapi-configuration + ganeti-rapi-configuration? + ganeti-rapi-configuration-ganeti + ganeti-rapi-configuration-require-authentication? + ganeti-rapi-configuration-port + ganeti-rapi-configuration-address + ganeti-rapi-configuration-interface + ganeti-rapi-configuration-max-clients + ganeti-rapi-configuration-ssl? + ganeti-rapi-configuration-ssl-key + ganeti-rapi-configuration-ssl-cert + ganeti-rapi-configuration-debug? + ganeti-rapi-service-type + + ganeti-kvmd-configuration + ganeti-kvmd-configuration? + ganeti-kvmd-configuration-ganeti + ganeti-kvmd-configuration-debug? + ganeti-kvmd-service-type + + ganeti-mond-configuration + ganeti-mond-configuration? + ganeti-mond-configuration-ganeti + ganeti-mond-configuration-port + ganeti-mond-configuration-address + ganeti-mond-configuration-debug? + ganeti-mond-service-type + + ganeti-metad-configuration + ganeti-metad-configuration? + ganeti-metad-configuration-ganeti + ganeti-metad-configuration-port + ganeti-metad-configuration-address + ganeti-metad-configuration-debug? + ganeti-metad-service-type + + ganeti-watcher-configuration + ganeti-watcher-configuration? + ganeti-watcher-configuration-ganeti + ganeti-watcher-configuration-schedule + ganeti-watcher-configuration-rapi-ip + ganeti-watcher-configuration-job-age + ganeti-watcher-configuration-verify-disks? + ganeti-watcher-configuration-debug? + ganeti-watcher-service-type + + ganeti-cleaner-configuration + ganeti-cleaner-configuration? + ganeti-cleaner-configuration-ganeti + ganeti-cleaner-configuration-master-schedule + ganeti-cleaner-configuration-node-schedule + ganeti-cleaner-service-type + + ganeti-os + ganeti-os? + ganeti-os-name + ganeti-os-extension + ganeti-os-variants + + ganeti-os-variant + ganeti-os-variant? + ganeti-os-variant-name + ganeti-os-variant-configuration + + %debootstrap-interfaces-hook + %debootstrap-grub-hook + %default-debootstrap-hooks + %default-debootstrap-extra-pkgs + debootstrap-configuration + debootstrap-configuration? + debootstrap-configuration-hooks + debootstrap-configuration-proxy + debootstrap-configuration-mirror + debootstrap-configuration-arch + debootstrap-configuration-suite + debootstrap-configuration-extra-pkgs + debootstrap-configuration-components + debootstrap-configuration-generate-cache? + debootstrap-configuration-clean-cache + debootstrap-configuration-partition-style + debootstrap-configuration-partition-alignment + + debootstrap-variant + debootstrap-os + %default-debootstrap-variants + + guix-variant + guix-os + %default-guix-variants + + %default-ganeti-os + + ganeti-configuration + ganeti-configuration? + ganeti-configuration-noded-configuration + ganeti-configuration-confd-configuration + ganeti-configuration-wconfd-configuration + ganeti-configuration-luxid-configuration + ganeti-configuration-rapi-configuration + ganeti-configuration-kvmd-configuration + ganeti-configuration-mond-configuration + ganeti-configuration-metad-configuration + ganeti-configuration-watcher-configuration + ganeti-configuration-cleaner-configuration + ganeti-configuration-file-storage-paths + ganeti-configuration-os + ganeti-service-type)) + +;;; +;;; Service definitions for running a Ganeti cluster. +;;; +;;; Planned improvements: run daemons (except ganeti-noded) under unprivileged +;;; user accounts and/or containers. The account names must match the ones +;;; given to Ganetis configure script. metad needs "setcap" or root in order +;;; to bind on port 80. + +;; Set PATH so the various daemons are able to find the 'ip' executable, LVM, +;; Ceph, Gluster, etc, without having to add absolute references to everything. +(define %default-ganeti-environment-variables + (list (string-append "PATH=" + (string-join '("/run/setuid-programs" + "/run/current-system/profile/sbin" + "/run/current-system/profile/bin") + ":")))) + +(define-record-type* + ganeti-noded-configuration make-ganeti-noded-configuration + ganeti-noded-configuration? + (ganeti ganeti-noded-configuration-ganeti ; + (default ganeti)) + (port ganeti-noded-configuration-port ;integer + (default 1811)) + (address ganeti-noded-configuration-address ;string + (default "0.0.0.0")) + (interface ganeti-noded-configuration-interface ;string | #f + (default #f)) + (max-clients ganeti-noded-configuration-max-clients ;integer + (default 20)) + (ssl? ganeti-noded-configuration-ssl? ;Boolean + (default #t)) + (ssl-key ganeti-noded-configuration-ssl-key ;string + (default "/var/lib/ganeti/server.pem")) + (ssl-cert ganeti-noded-configuration-ssl-cert ;string + (default "/var/lib/ganeti/server.pem")) + (debug? ganeti-noded-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-noded-service + (match-lambda + (($ ganeti port address interface max-clients + ssl? ssl-key ssl-cert debug?) + (list (shepherd-service + (documentation "Run the Ganeti node daemon.") + (provision '(ganeti-noded)) + (requirement '(user-processes networking)) + + ;; If the daemon stops, it is probably for a good reason; + ;; otherwise ganeti-watcher will restart it for us anyway. + (respawn? #f) + + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-noded") + #$(string-append "--port=" (number->string port)) + #$(string-append "--bind=" address) + #$@(if interface + #~((string-append "--interface=" #$interface)) + #~()) + #$(string-append "--max-clients=" + (number->string max-clients)) + #$@(if ssl? + #~((string-append "--ssl-key=" #$ssl-key) + (string-append "--ssl-cert=" #$ssl-cert)) + #~("--no-ssl")) + #$@(if debug? + #~("--debug") + #~())) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-noded.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-noded-service-type + (service-type (name 'ganeti-noded) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-noded-service))) + (default-value (ganeti-noded-configuration)) + (description + "@command{ganeti-noded} is the daemon which is responsible +for the node functions in the Ganeti system."))) + +(define-record-type* + ganeti-confd-configuration make-ganeti-confd-configuration + ganeti-confd-configuration? + (ganeti ganeti-confd-configuration-ganeti ; + (default ganeti)) + (port ganeti-confd-configuration-port ;integer + (default 1814)) + (address ganeti-confd-configuration-address ;string + (default "0.0.0.0")) + (debug? ganeti-confd-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-confd-service + (match-lambda + (($ ganeti port address debug?) + (list (shepherd-service + (documentation "Run the Ganeti confd daemon.") + (provision '(ganeti-confd)) + (requirement '(user-processes networking)) + (respawn? #f) + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-confd") + #$(string-append "--port=" (number->string port)) + #$(string-append "--bind=" address) + #$@(if debug? + #~("--debug") + #~())) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-confd.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-confd-service-type + (service-type (name 'ganeti-confd) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-confd-service))) + (default-value (ganeti-confd-configuration)) + (description + "@command{ganeti-confd} is a daemon used to answer queries +related to the configuration of a Ganeti cluster."))) + +(define-record-type* + ganeti-wconfd-configuration make-ganeti-wconfd-configuration + ganeti-wconfd-configuration? + (ganeti ganeti-wconfd-configuration-ganeti ; + (default ganeti)) + (no-voting? ganeti-wconfd-configuration-no-voting? ;Boolean + (default #f)) + (debug? ganeti-wconfd-configuration-debug? ;Boolean + (default #f))) + +;; If this file exists, the wconfd daemon will be forcefully started even on +;; non-master nodes. It is used to accommodate a master-failover scenario. +(define %wconfd-force-node-hint + "/var/lib/ganeti/guix_wconfd_force_node_hint") + +(define (wconfd-wrapper ganeti args) + ;; Wrapper for the wconfd daemon that looks for the force-node hint. + (program-file + "wconfd-wrapper" + #~(begin + (let ((wconfd #$(file-append ganeti "/sbin/ganeti-wconfd")) + (force-node? (file-exists? #$%wconfd-force-node-hint))) + (if force-node? + (execl wconfd wconfd "--force-node" "--no-voting" "--yes-do-it" #$@args) + (execl wconfd wconfd #$@args)))))) + +(define shepherd-wconfd-force-start-action + ;; Shepherd action to create the force-node hint and start wconfd. + (shepherd-action + (name 'force-start) + (documentation + "Forcefully start wconfd even on non-master nodes (dangerous!).") + (procedure #~(lambda _ + (format #t "Forcefully starting the wconfd daemon...~%") + (action 'ganeti-wconfd 'enable) + (dynamic-wind + (lambda () + (false-if-exception + (call-with-output-file #$%wconfd-force-node-hint + (lambda (port) + (const #t))))) + (lambda () + (action 'ganeti-wconfd 'restart)) + (lambda () + (delete-file #$%wconfd-force-node-hint))) + #t)))) + +(define ganeti-wconfd-service + (match-lambda + (($ ganeti no-voting? debug?) + (list (shepherd-service + (documentation "Run the Ganeti wconfd daemon.") + (provision '(ganeti-wconfd)) + (requirement '(user-processes)) + + ;; Shepherd action to support a master-failover scenario. It is + ;; automatically invoked during 'gnt-cluster master-failover' (see + ;; related Ganeti patch) and not intended for interactive use. + (actions (list shepherd-wconfd-force-start-action)) + + ;; wconfd will disable itself when not running on the master + ;; node. Don't attempt to restart it. + (respawn? #f) + + (start + #~(make-forkexec-constructor + (list #$(wconfd-wrapper ganeti + (append + (if no-voting? + '("--no-voting" "--yes-do-it") + '()) + (if debug? + '("--debug") + '())))) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-wconfd.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-wconfd-service-type + (service-type (name 'ganeti-wconfd) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-wconfd-service))) + (default-value (ganeti-wconfd-configuration)) + (description + "@command{ganeti-wconfd} is the daemon that has authoritative +knowledge about the configuration and is the only entity that can accept changes +to it. All jobs that need to modify the configuration will do so by sending +appropriate requests to this daemon."))) + +(define-record-type* + ganeti-luxid-configuration make-ganeti-luxid-configuration + ganeti-luxid-configuration? + (ganeti ganeti-luxid-configuration-ganeti ; + (default ganeti)) + (no-voting? ganeti-luxid-configuration-no-voting? ;Boolean + (default #f)) + (debug? ganeti-luxid-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-luxid-service + (match-lambda + (($ ganeti no-voting? debug?) + (list (shepherd-service + (documentation "Run the Ganeti LUXI daemon.") + (provision '(ganeti-luxid)) + (requirement '(user-processes)) + + ;; This service will automatically disable itself when not + ;; running on the master node. Don't attempt to restart it. + (respawn? #f) + + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-luxid") + #$@(if no-voting? + #~("--no-voting" "--yes-do-it") + #~()) + #$@(if debug? + #~("--debug") + #~())) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-luxid.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-luxid-service-type + (service-type (name 'ganeti-luxid) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-luxid-service))) + (default-value (ganeti-luxid-configuration)) + (description + "@command{ganeti-luxid} is a daemon used to answer queries +related to the configuration and the current live state of a Ganeti cluster. +Additionally, it is the autorative daemon for the Ganeti job queue. Jobs can +be submitted via this daemon and it schedules and starts them."))) + +(define-record-type* + ganeti-rapi-configuration make-ganeti-rapi-configuration + ganeti-rapi-configuration? + (ganeti ganeti-rapi-configuration-ganeti ; + (default ganeti)) + (require-authentication? + ganeti-rapi-configuration-require-authentication? ;Boolean + (default #f)) + (port ganeti-rapi-configuration-port ;integer + (default 5080)) + (address ganeti-rapi-configuration-address ;string + (default "0.0.0.0")) + (interface ganeti-rapi-configuration-interface ;string | #f + (default #f)) + (max-clients ganeti-rapi-configuration-max-clients ;integer + (default 20)) + (ssl? ganeti-rapi-configuration-ssl? ;Boolean + (default #f)) + (ssl-key ganeti-rapi-configuration-ssl-key ;string + (default "/var/lib/ganeti/server.pem")) + (ssl-cert ganeti-rapi-configuration-ssl-cert ;string + (default "/var/lib/ganeti/server.pem")) + (debug? ganeti-rapi-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-rapi-service + (match-lambda + (($ ganeti require-authentication? port address + interface max-clients ssl? ssl-key ssl-cert + debug?) + (list (shepherd-service + (documentation "Run the Ganeti RAPI daemon.") + (provision '(ganeti-rapi)) + (requirement '(user-processes networking)) + + ;; This service will automatically disable itself when not + ;; running on the master node. Don't attempt to restart it. + (respawn? #f) + + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-rapi") + #$@(if require-authentication? + #~("--require-authentication") + #~()) + #$(string-append "--port=" (number->string port)) + #$(string-append "--bind=" address) + #$@(if interface + #~((string-append "--interface=" #$interface)) + #~()) + #$(string-append "--max-clients=" + (number->string max-clients)) + #$@(if ssl? + #~((string-append "--ssl-key=" #$ssl-key) + (string-append "--ssl-cert=" #$ssl-cert)) + #~("--no-ssl")) + #$@(if debug? + #~("--debug") + #~())) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-rapi.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-rapi-service-type + (service-type (name 'ganeti-rapi) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-rapi-service))) + (default-value (ganeti-rapi-configuration)) + (description + "@command{ganeti-rapi} is the daemon providing a remote API +for Ganeti clusters."))) + +(define-record-type* + ganeti-kvmd-configuration make-ganeti-kvmd-configuration + ganeti-kvmd-configuration? + (ganeti ganeti-kvmd-configuration-ganeti ; + (default ganeti)) + (debug? ganeti-kvmd-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-kvmd-service + (match-lambda + (($ ganeti debug?) + (list (shepherd-service + (documentation "Run the Ganeti KVM daemon.") + (provision '(ganeti-kvmd)) + (requirement '(user-processes)) + + ;; This service will automatically disable itself when not + ;; needed. Don't attempt to restart it. + (respawn? #f) + + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-kvmd") + #$@(if debug? + #~("--debug") + #~())) + #:environment-variables + '#$%default-ganeti-environment-variables + #:pid-file "/var/run/ganeti/ganeti-kvmd.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-kvmd-service-type + (service-type (name 'ganeti-kvmd) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-kvmd-service))) + (default-value (ganeti-kvmd-configuration)) + (description + "@command{ganeti-kvmd} is responsible for determining whether +a given KVM instance was shutdown by an administrator or a user. + +The KVM daemon monitors, using @code{inotify}, KVM instances through their QMP +sockets, which are provided by KVM. Using the QMP sockets, the KVM daemon +listens for particular shutdown, powerdown, and stop events which will determine +if a given instance was shutdown by the user or Ganeti, and this result is +communicated to Ganeti via a special file in the filesystem."))) + +(define-record-type* + ganeti-mond-configuration make-ganeti-mond-configuration + ganeti-mond-configuration? + (ganeti ganeti-mond-configuration-ganeti ; + (default ganeti)) + (port ganeti-mond-configuration-port ;integer + (default 1815)) + (address ganeti-mond-configuration-address ;string + (default "0.0.0.0")) + (debug? ganeti-mond-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-mond-service + (match-lambda + (($ ganeti port address debug?) + (list (shepherd-service + (documentation "Run the Ganeti monitoring daemon.") + (provision '(ganeti-mond)) + (requirement '(user-processes networking)) + (respawn? #f) + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-mond") + #$(string-append "--port=" (number->string port)) + #$(string-append "--bind=" address) + #$@(if debug? + #~("--debug") + #~())) + #:pid-file "/var/run/ganeti/ganeti-mond.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-mond-service-type + (service-type (name 'ganeti-mond) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-mond-service))) + (default-value (ganeti-mond-configuration)) + (description + "@command{ganeti-mond} is a daemon providing monitoring +functionality. It is responsible for running the data collectors and to +provide the collected information through a HTTP interface."))) + +(define-record-type* + ganeti-metad-configuration make-ganeti-metad-configuration + ganeti-metad-configuration? + (ganeti ganeti-metad-configuration-ganeti ; + (default ganeti)) + (port ganeti-metad-configuration-port ;integer + (default 80)) + (address ganeti-metad-configuration-address ;string | #f + (default #f)) + (debug? ganeti-metad-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-metad-service + (match-lambda + (($ ganeti port address debug?) + (list (shepherd-service + (documentation "Run the Ganeti metadata daemon.") + (provision '(ganeti-metad)) + (requirement '(user-processes networking)) + (respawn? #f) + (start #~(make-forkexec-constructor + (list #$(file-append ganeti "/sbin/ganeti-metad") + #$(string-append "--port=" (number->string port)) + #$@(if address + #~((string-append "--bind=" #$address)) + #~()) + #$@(if debug? + #~("--debug") + #~())) + #:pid-file "/var/run/ganeti/ganeti-metad.pid")) + (stop #~(make-kill-destructor))))))) + +(define ganeti-metad-service-type + (service-type (name 'ganeti-metad) + (extensions + (list (service-extension shepherd-root-service-type + ganeti-metad-service))) + (default-value (ganeti-metad-configuration)) + (description + "@command{ganeti-metad} is a daemon that can be used to pass +information to OS install scripts or instances."))) + +(define-record-type* + ganeti-watcher-configuration make-ganeti-watcher-configuration + ganeti-watcher-configuration? + (ganeti ganeti-watcher-configuration-ganeti ; + (default ganeti)) + (schedule ganeti-watcher-configuration-schedule ;list | string + (default '(next-second-from + ;; Run every five minutes. + (next-minute (range 0 60 5))))) + (rapi-ip ganeti-watcher-configuration-rapi-ip ;#f | string + (default #f)) + (job-age ganeti-watcher-configuration-job-age ;integer + (default (* 6 3600))) + (verify-disks? ganeti-watcher-configuration-verify-disks? ;Boolean + (default #t)) + (debug? ganeti-watcher-configuration-debug? ;Boolean + (default #f))) + +(define ganeti-watcher-command + (match-lambda + (($ ganeti _ rapi-ip job-age verify-disks? + debug?) + #~(lambda () + (system* #$(file-append ganeti "/sbin/ganeti-watcher") + #$@(if rapi-ip + #~(string-append "--rapi-ip=" #$rapi-ip) + #~()) + #$(string-append "--job-age=" (number->string job-age)) + #$@(if verify-disks? + #~() + #~("--no-verify-disks")) + #$@(if debug? + #~("--debug") + #~())))))) + +(define (ganeti-watcher-jobs config) + (match config + (($ _ schedule) + (list + #~(job #$@(match schedule + ((? string?) + #~(#$schedule)) + ((? list?) + #~('#$schedule))) + #$(ganeti-watcher-command config)))))) + +(define ganeti-watcher-service-type + (service-type (name 'ganeti-watcher) + (extensions + (list (service-extension mcron-service-type + ganeti-watcher-jobs))) + (default-value (ganeti-watcher-configuration)) + (description + "@command{ganeti-watcher} is a periodically run script that +performs a number of maintenance actions on the cluster. It will automatically +restart instances that are marked as ERROR_down, i.e., instances that should be +running, but are not; and it will also try to repair DRBD links in case a +secondary node has rebooted. In addition it is responsible for archiving old +cluster jobs, and it will restart any down Ganeti daemons that are appropriate +for the current node. If the cluster parameter @code{maintain_node_health} is +enabled, the watcher will also shutdown instances and DRBD devices if the node +is declared offline by known master candidates."))) + +(define-record-type* + ganeti-cleaner-configuration make-ganeti-cleaner-configuration + ganeti-cleaner-configuration? + (ganeti ganeti-cleaner-configuration-ganeti ; + (default ganeti)) + (master-schedule ganeti-cleaner-configuration-master-schedule ;list | string + ;; Run the master cleaner at 01:45 every day. + (default "45 1 * * *")) + (node-schedule ganeti-cleaner-configuration-node-schedule ;list | string + ;; Run the node cleaner at 02:45 every day. + (default "45 2 * * *"))) + +(define ganeti-cleaner-jobs + (match-lambda + (($ ganeti master-schedule node-schedule) + (list + #~(job #$@(match master-schedule + ((? string?) + #~(#$master-schedule)) + ((? list?) + #~('#$master-schedule))) + (lambda () + (system* #$(file-append ganeti "/sbin/ganeti-cleaner") + "master"))) + #~(job #$@(match node-schedule + ((? string?) + #~(#$node-schedule)) + ((? list?) + #~('#$node-schedule))) + (lambda () + (system* #$(file-append ganeti "/sbin/ganeti-cleaner") + "node"))))))) + +(define ganeti-cleaner-service-type + (service-type (name 'ganeti-cleaner) + (extensions + (list (service-extension mcron-service-type + ganeti-cleaner-jobs))) + (default-value (ganeti-cleaner-configuration)) + (description + "@command{ganeti-cleaner} is a script that removes old files +from the cluster. When called with @code{node} as argument it removes expired +X509 certificates and keys from @file{/var/run/ganeti/crypto}, as well as +outdated @command{ganeti-watcher} information. + +When called with @code{master} as argument, it instead removes files older +than 21 days from @file{/var/lib/ganeti/queue/archive}."))) + +(define-record-type* + ganeti-configuration make-ganeti-configuration + ganeti-configuration? + (ganeti ganeti-configuration-ganeti + (default ganeti)) + (noded-configuration ganeti-configuration-noded-configuration + (default (ganeti-noded-configuration))) + (confd-configuration ganeti-configuration-confd-configuration + (default (ganeti-confd-configuration))) + (wconfd-configuration ganeti-configuration-wconfd-configuration + (default (ganeti-wconfd-configuration))) + (luxid-configuration ganeti-configuration-luxid-configuration + (default (ganeti-luxid-configuration))) + (rapi-configuration ganeti-configuration-rapi-configuration + (default (ganeti-rapi-configuration))) + (kvmd-configuration ganeti-configuration-kvmd-configuration + (default (ganeti-kvmd-configuration))) + (mond-configuration ganeti-configuration-mond-configuration + (default (ganeti-mond-configuration))) + (metad-configuration ganeti-configuration-metad-configuration + (default (ganeti-metad-configuration))) + (watcher-configuration ganeti-configuration-watcher-configuration + (default (ganeti-watcher-configuration))) + (cleaner-configuration ganeti-configuration-cleaner-configuration + (default (ganeti-cleaner-configuration))) + (file-storage-paths ganeti-configuration-file-storage-paths ;list of strings | gexp + (default '())) + (os ganeti-configuration-os ;list of + (default '()))) + +(define (ganeti-activation config) + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (for-each mkdir-p + '("/var/log/ganeti" + "/var/log/ganeti/kvm" + "/var/log/ganeti/os" + "/var/lib/ganeti/rapi" + "/var/lib/ganeti/queue" + "/var/lib/ganeti/queue/archive" + "/var/run/ganeti/bdev-cache" + "/var/run/ganeti/crypto" + "/var/run/ganeti/socket" + "/var/run/ganeti/instance-disks" + "/var/run/ganeti/instance-reason" + "/var/run/ganeti/livelocks"))))) + +(define ganeti-shepherd-services + (match-lambda + (($ _ noded confd wconfd luxid rapi kvmd mond metad) + (append (ganeti-noded-service noded) + (ganeti-confd-service confd) + (ganeti-wconfd-service wconfd) + (ganeti-luxid-service luxid) + (ganeti-rapi-service rapi) + (ganeti-kvmd-service kvmd) + (ganeti-mond-service mond) + (ganeti-metad-service metad))))) + +(define ganeti-mcron-jobs + (match-lambda + (($ _ _ _ _ _ _ _ _ _ watcher cleaner) + (append (ganeti-watcher-jobs watcher) + (ganeti-cleaner-jobs cleaner))))) + +(define-record-type* + ganeti-os make-ganeti-os ganeti-os? + (name ganeti-os-name) ;string + (extension ganeti-os-extension) ;string + (variants ganeti-os-variants ;list of + (default '()))) + +(define-record-type* + ganeti-os-variant make-ganeti-os-variant ganeti-os-variant? + (name ganeti-os-variant-name) ;string + (configuration ganeti-os-variant-configuration)) ; + +(define %debootstrap-interfaces-hook + (file-append ganeti-instance-debootstrap + "/share/doc/ganeti-instance-debootstrap/examples/interfaces")) + +;; The GRUB hook shipped with instance-debootstrap does not work with GRUB2. +;; For convenience, provide one that work with modern Debians here. +;; Note: it would be neat to reuse Guix' bootloader infrastructure instead. +(define %debootstrap-grub-hook + (plain-file "grub" + "#!/usr/bin/env bash +CLEANUP=( ) +cleanup() { + if [ ${#CLEANUP[*]} -gt 0 ]; then + LAST_ELEMENT=$((${#CLEANUP[*]}-1)) + REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0) + for i in $REVERSE_INDEXES; do + ${CLEANUP[$i]} + done + fi +} + +trap cleanup EXIT + +mount -t proc proc $TARGET/proc +CLEANUP+=(\"umount $TARGET/proc\") +mount -t sysfs sysfs $TARGET/sys +CLEANUP+=(\"umount $TARGET/sys\") +mount -o bind /dev $TARGET/dev +CLEANUP+=(\"umount $TARGET/dev\") + +echo ' +GRUB_TIMEOUT_STYLE=menu +GRUB_CMDLINE_LINUX_DEFAULT=\"console=ttyS0,115200 net.ifnames=0\" +GRUB_TERMINAL=\"serial\" +GRUB_SERIAL_COMMAND=\"serial --unit=0 --speed=115200\" +' >> $TARGET/etc/default/grub + +# This PATH is propagated into the chroot and necessary to make grub-install +# and related commands visible. +export PATH=\"/usr/sbin:/usr/bin:/sbin:/bin:$PATH\" + +chroot \"$TARGET\" grub-install $BLOCKDEV +chroot \"$TARGET\" update-grub + +cleanup +trap - EXIT +")) + +(define %default-debootstrap-hooks + `((10-interfaces . ,%debootstrap-interfaces-hook) + (90-grub . ,%debootstrap-grub-hook))) + +(define %default-debootstrap-extra-pkgs + ;; Packages suitable for a fully virtualized KVM guest. + '("acpi-support-base" "udev" "linux-image-amd64" "openssh-server" + "locales-all" "grub-pc")) + +(define-record-type* + debootstrap-configuration make-debootstrap-configuration + debootstrap-configuration? + (hooks debootstrap-configuration-hooks ;#f | gexp | '((name . gexp)) + (default %default-debootstrap-hooks)) + (proxy debootstrap-configuration-proxy (default #f)) ;#f | string + (mirror debootstrap-configuration-mirror ;#f | string + (default #f)) + (arch debootstrap-configuration-arch (default #f)) ;#f | string + (suite debootstrap-configuration-suite ;#f | string + (default "stable")) + (extra-pkgs debootstrap-configuration-extra-pkgs ;list of strings + (default %default-debootstrap-extra-pkgs)) + (components debootstrap-configuration-components ;list of strings + (default '())) + (generate-cache? debootstrap-configuration-generate-cache? ;Boolean + (default #t)) + (clean-cache debootstrap-configuration-clean-cache ;#f | integer + (default 14)) + (partition-style debootstrap-configuration-partition-style ;#f | symbol | string + (default 'msdos)) + (partition-alignment debootstrap-configuration-partition-alignment ;#f | integer + (default 2048))) + +(define (hooks->directory hooks) + (match hooks + ((? file-like?) + hooks) + ((? list?) + (let ((names (map car hooks)) + (files (map cdr hooks))) + (with-imported-modules '((guix build utils)) + (computed-file "hooks-union" + #~(begin + (use-modules (guix build utils) + (ice-9 match)) + (mkdir-p #$output) + (with-directory-excursion #$output + (for-each (match-lambda + ((name hook) + (let ((file-name (string-append + #$output "/" + (symbol->string name)))) + ;; Copy to the destination to ensure + ;; the file is executable. + (copy-file hook file-name) + (chmod file-name #o555)))) + '#$(zip names files)))))))) + (_ #f))) + +(define-gexp-compiler (debootstrap-configuration-compiler + (file ) system target) + (match file + (($ hooks proxy mirror arch suite extra-pkgs + components generate-cache? clean-cache + partition-style partition-alignment) + (let ((customize-dir (hooks->directory hooks))) + (gexp->derivation + "debootstrap-variant" + #~(call-with-output-file (ungexp output "out") + (lambda (port) + (display + (string-append + (ungexp-splicing + `(,@(if proxy + `("PROXY=" ,proxy "\n") + '()) + ,@(if mirror + `("MIRROR=" ,mirror "\n") + '()) + ,@(if arch + `("ARCH=" ,arch "\n") + '()) + ,@(if suite + `("SUITE=" ,suite "\n") + '()) + ,@(if (not (null? extra-pkgs)) + `("EXTRA_PKGS=" ,(string-join extra-pkgs ",") "\n") + '()) + ,@(if (not (null? components)) + `("COMPONENTS=" ,(string-join components ",") "\n") + '()) + ,@(if customize-dir + `("CUSTOMIZE_DIR=" ,customize-dir "\n") + '()) + ,@(if generate-cache? + '("GENERATE_CACHE=yes\n") + '("GENERATE_CACHE=no\n")) + ,@(if clean-cache + `("CLEAN_CACHE=" ,(number->string clean-cache) "\n") + '()) + ,@(if partition-style + (if (symbol? partition-style) + `("PARTITION_STYLE=" + ,(symbol->string partition-style) "\n") + `("PARTITION_STYLE=" ,partition-style "\n")) + '()) + ,@(if partition-alignment + `("PARTITION_ALIGNMENT=" + ,(number->string partition-alignment) "\n") + '())))) + port))) + #:local-build? #t))))) + +(define (ganeti-os->directory os) + "Return the derivation to build the configuration directory to be installed +in /etc/ganeti/instance-$os for OS." + (let* ((name (ganeti-os-name os)) + (extension (ganeti-os-extension os)) + (variants (ganeti-os-variants os)) + (names (map ganeti-os-variant-name variants)) + (configs (map ganeti-os-variant-configuration variants))) + (with-imported-modules '((guix build utils)) + (define builder + #~(begin + (use-modules (guix build utils) + (ice-9 format) + (ice-9 match) + (srfi srfi-1)) + (mkdir-p #$output) + (unless (null? '#$names) + (let ((variants-dir (string-append #$output "/variants"))) + (mkdir-p variants-dir) + (call-with-output-file (string-append variants-dir "/variants.list") + (lambda (port) + (format port "~a~%" + (string-join '#$names "\n")))) + (for-each (match-lambda + ((name file) + (symlink file + (string-append variants-dir "/" name + #$extension)))) + + '#$(zip names configs)))))) + + (computed-file (string-append name "-os") builder)))) + +(define (ganeti-directory file-storage-file os) + (let ((dirs (map ganeti-os->directory os)) + (names (map ganeti-os-name os))) + (define builder + #~(begin + (use-modules (ice-9 match)) + (mkdir #$output) + (when #$file-storage-file + (symlink #$file-storage-file + (string-append #$output "/file-storage-paths"))) + (for-each (match-lambda + ((name dest) + (symlink dest + (string-append #$output "/instance-" name)))) + '#$(zip names dirs)))) + (computed-file "etc-ganeti" builder))) + +(define (file-storage-file paths) + (match paths + ((? null?) #f) + ((? list?) (plain-file + "file-storage-paths" + (string-join paths "\n"))) + (_ paths))) + +(define (ganeti-etc-service config) + (list `("ganeti" ,(ganeti-directory + (file-storage-file + (ganeti-configuration-file-storage-paths config)) + (ganeti-configuration-os config))))) + +(define (debootstrap-os variants) + (ganeti-os + (name "debootstrap") + (extension ".conf") + (variants variants))) + +(define (debootstrap-variant name configuration) + (ganeti-os-variant + (name name) + (configuration configuration))) + +(define %default-debootstrap-variants + (list (debootstrap-variant + "default" + (debootstrap-configuration)))) + +(define (guix-os variants) + (ganeti-os + (name "guix") + (extension ".scm") + (variants variants))) + +(define (guix-variant name configuration) + (ganeti-os-variant + (name name) + (configuration configuration))) + +(define %default-guix-variants + (list (guix-variant + "default" + (file-append ganeti-instance-guix + "/share/doc/ganeti-instance-guix/examples/dynamic.scm")))) + +;; The OS configurations usually come with a default OS. To make them work +;; out of the box, follow suit. +(define %default-ganeti-os + (list (debootstrap-os %default-debootstrap-variants) + (guix-os %default-guix-variants))) + +(define ganeti-service-type + (service-type (name 'ganeti) + (extensions + (list (service-extension activation-service-type + ganeti-activation) + (service-extension shepherd-root-service-type + ganeti-shepherd-services) + (service-extension etc-service-type + ganeti-etc-service) + (service-extension profile-service-type + (compose list ganeti-configuration-ganeti)) + (service-extension mcron-service-type + ganeti-mcron-jobs))) + (default-value (ganeti-configuration (os %default-ganeti-os))) + (description + "Ganeti is a family of services that are designed to run +on a fleet of machines and facilitate deployment and maintenance of virtual +servers (@dfn{instances}). It can migrate instances between nodes, automatically +restart failed instances, evacuate nodes, and much more."))) diff --git a/gnu/tests/ganeti.scm b/gnu/tests/ganeti.scm new file mode 100644 index 0000000000..0615edcde4 --- /dev/null +++ b/gnu/tests/ganeti.scm @@ -0,0 +1,265 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2020 Marius Bakke . +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu tests ganeti) + #:use-module (gnu) + #:use-module (gnu tests) + #:use-module (gnu system vm) + #:use-module (gnu services) + #:use-module (gnu services ganeti) + #:use-module (gnu services networking) + #:use-module (gnu services ssh) + #:use-module (gnu packages virtualization) + #:use-module (guix gexp) + #:use-module (ice-9 format) + #:export (%test-ganeti-kvm %test-ganeti-lxc)) + +(define %ganeti-os + (operating-system + (host-name "gnt1") + (timezone "Etc/UTC") + (locale "en_US.UTF-8") + + (bootloader (bootloader-configuration + (bootloader grub-bootloader) + (target "/dev/vda"))) + (file-systems (cons (file-system + (device (file-system-label "my-root")) + (mount-point "/") + (type "ext4")) + %base-file-systems)) + (firmware '()) + + ;; The hosts file must contain a nonlocal IP for host-name. + ;; In addition, the cluster name must resolve to an IP address that + ;; is not currently provisioned. + (hosts-file (plain-file "hosts" (format #f " +127.0.0.1 localhost +::1 localhost +10.0.2.2 gnt1.example.com gnt1 +192.168.254.254 ganeti.example.com +"))) + + (packages (append (list ganeti-instance-debootstrap ganeti-instance-guix) + %base-packages)) + (services + (append (list (static-networking-service "eth0" "10.0.2.2" + #:netmask "255.255.255.0" + #:gateway "10.0.2.1" + #:name-servers '("10.0.2.1")) + + (service openssh-service-type + (openssh-configuration + (permit-root-login 'without-password))) + + (service ganeti-service-type + (ganeti-configuration + (file-storage-paths '("/srv/ganeti/file-storage")) + (os %default-ganeti-os)))) + %base-services)))) + +(define* (run-ganeti-test hypervisor #:key + (master-netdev "eth0") + (hvparams '()) + (extra-packages '()) + (rapi-port 5080) + (noded-port 1811)) + "Run tests in %GANETI-OS." + (define os + (marionette-operating-system + (operating-system + (inherit %ganeti-os) + (packages (append extra-packages + (operating-system-packages %ganeti-os)))) + #:imported-modules '((gnu services herd) + (guix combinators)))) + + (define %forwarded-rapi-port 5080) + (define %forwarded-noded-port 1811) + + (define vm + (virtual-machine + (operating-system os) + ;; Some of the daemons are fairly memory-hungry. + (memory-size 512) + ;; Forward HTTP ports so we can access them from the "outside". + (port-forwardings `((,%forwarded-rapi-port . ,rapi-port) + (,%forwarded-noded-port . ,noded-port))))) + + (define test + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (srfi srfi-11) (srfi srfi-64) + (web uri) (web client) (web response) + (gnu build marionette)) + + (define marionette + (make-marionette (list #$vm))) + + (mkdir #$output) + (chdir #$output) + + (test-begin "ganeti") + + ;; Ganeti uses the Shepherd to start/stop daemons, so make sure + ;; it is ready before we begin. It takes a while because all + ;; Ganeti daemons fail to start initially. + (test-assert "shepherd is ready" + (wait-for-unix-socket "/var/run/shepherd/socket" marionette)) + + (test-eq "gnt-cluster init" + 0 + (marionette-eval + '(begin + (setenv + "PATH" + ;; Init needs to run 'ssh-keygen', 'ip', etc. + "/run/current-system/profile/sbin:/run/current-system/profile/bin") + (system* #$(file-append ganeti "/sbin/gnt-cluster") "init" + (string-append "--master-netdev=" #$master-netdev) + ;; TODO: Enable more disk backends. + "--enabled-disk-templates=file" + (string-append "--enabled-hypervisors=" + #$hypervisor) + (string-append "--hypervisor-parameters=" + #$hypervisor ":" + (string-join '#$hvparams "\n")) + ;; Set the default NIC mode to 'routed' to avoid having to + ;; configure a full bridge to placate 'gnt-cluster verify'. + "--nic-parameters=mode=routed,link=eth0" + "ganeti.example.com")) + marionette)) + + ;; Disable the watcher while doing daemon tests to prevent interference. + (test-eq "watcher pause" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/sbin/gnt-cluster") + "watcher" "pause" "1h")) + marionette)) + + (test-assert "force-start wconfd" + ;; Check that the 'force-start' Shepherd action works, used in a + ;; master-failover scenario. + (marionette-eval + '(begin + (setenv "PATH" "/run/current-system/profile/bin") + (invoke "herd" "stop" "ganeti-wconfd") + (invoke "herd" "disable" "ganeti-wconfd") + (invoke "herd" "force-start" "ganeti-wconfd")) + marionette)) + + ;; Verify that the cluster is healthy. + (test-eq "gnt-cluster verify 1" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/sbin/gnt-cluster") "verify")) + marionette)) + + ;; Try stopping and starting daemons with daemon-util like + ;; 'gnt-node add', 'gnt-cluster init', etc. + (test-eq "daemon-util stop-all" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/lib/ganeti/daemon-util") + "stop-all")) + marionette)) + + (test-eq "daemon-util start-all" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/lib/ganeti/daemon-util") + "start-all")) + marionette)) + + ;; Check that the cluster is still healthy after the daemon restarts. + (test-eq "gnt-cluster verify 2" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/sbin/gnt-cluster") "verify")) + marionette)) + + (test-eq "watcher continue" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/sbin/gnt-cluster") + "watcher" "continue")) + marionette)) + + ;; Try accessing the RAPI. This causes an expected failure: + ;; https://github.com/ganeti/ganeti/issues/1502 + ;; Run it anyway for easy testing of potential fixes. + (test-equal "http-get RAPI version" + '(200 "2") + (let-values + (((response text) + (http-get #$(simple-format + #f "http://localhost:~A/version" + %forwarded-rapi-port) + #:decode-body? #t))) + (list (response-code response) text))) + + (test-equal "gnt-os list" + "debootstrap+default\nguix+default\n" + (marionette-eval + '(begin + (use-modules (ice-9 popen)) + (let* ((port (open-pipe* + OPEN_READ + #$(file-append ganeti "/sbin/gnt-os") + "list" "--no-headers")) + (output (get-string-all port))) + (close-pipe port) + output)) + marionette)) + + (test-eq "gnt-cluster destroy" + 0 + (marionette-eval + '(begin + (system* #$(file-append ganeti "/sbin/gnt-cluster") + "destroy" "--yes-do-it")) + marionette)) + + (test-end) + (exit (= (test-runner-fail-count (test-runner-current)) 1))))) + + (gexp->derivation (string-append "ganeti-" hypervisor "-test") test)) + +(define %test-ganeti-kvm + (system-test + (name "ganeti-kvm") + (description "Provision a Ganeti cluster using the KVM hypervisor.") + (value (run-ganeti-test "kvm" + ;; Set kernel_path to an empty string to prevent + ;; 'gnt-cluster verify' from testing for its presence. + #:hvparams '("kernel_path=") + #:extra-packages (list qemu))))) + +(define %test-ganeti-lxc + (system-test + (name "ganeti-lxc") + (description "Provision a Ganeti cluster using LXC as the hypervisor.") + (value (run-ganeti-test "lxc" + #:extra-packages (list lxc))))) -- cgit v1.2.3 From c9ea5dfdbe1c4758cfbd7b4b665219d4b8a4bad6 Mon Sep 17 00:00:00 2001 From: Jakub Kądziołka Date: Fri, 17 Jul 2020 22:13:58 +0200 Subject: tests: docker-system: Use guile-3.0. This helps find the (json) module in the virtual machine. * gnu/tests/docker.scm (build-tarball&run-docker-test, run-docker-system-test): Replace guile-2.2 with guile-3.0. --- gnu/tests/docker.scm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/docker.scm b/gnu/tests/docker.scm index 5ab33e1104..ea6c9a33fe 100644 --- a/gnu/tests/docker.scm +++ b/gnu/tests/docker.scm @@ -157,7 +157,7 @@ inside %DOCKER-OS." (version "0") (source #f) (build-system trivial-build-system) - (arguments `(#:guile ,guile-2.2 + (arguments `(#:guile ,guile-3.0 #:builder (let ((out (assoc-ref %outputs "out"))) (mkdir out) @@ -171,7 +171,7 @@ standard output device and then enters a new line.") (home-page #f) (license license:public-domain))) (profile (profile-derivation (packages->manifest - (list guile-2.2 guile-json-3 + (list guile-3.0 guile-json-3 guest-script-package)) #:hooks '() #:locales? #f)) @@ -254,7 +254,7 @@ inside %DOCKER-OS." (define (wait-for-container-file container file) ;; Wait for FILE to show up in CONTAINER. (docker-cli "exec" container - #$(file-append guile-2.2 "/bin/guile") + #$(file-append guile-3.0 "/bin/guile") "-c" (object->string `(let loop ((n 15)) -- cgit v1.2.3 From 41daf1286575f3a1998493c893f6d5a9c5b62de8 Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Sun, 19 Jul 2020 09:59:57 +0200 Subject: services: ganeti: Use TLS on the remote API by default. * gnu/services/ganeti.scm (): Set SSL? to #t. * gnu/tests/ganeti.scm (%ganeti-os): Set SSL? to #f. * doc/guix.texi (Virtualization Services): Adjust accordingly. --- doc/guix.texi | 2 +- gnu/services/ganeti.scm | 2 +- gnu/tests/ganeti.scm | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'gnu/tests') diff --git a/doc/guix.texi b/doc/guix.texi index 2c5c017eea..df37349c4a 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -25497,7 +25497,7 @@ The maximum number of simultaneous client requests to handle. Further connections are allowed, but no responses are sent until enough connections have closed. -@item @code{ssl?} (default: @code{#f}) +@item @code{ssl?} (default: @code{#t}) Whether to use SSL/TLS encryption on the RAPI port. @item @code{ssl-key} (default: @file{"/var/lib/ganeti/server.pem"}) diff --git a/gnu/services/ganeti.scm b/gnu/services/ganeti.scm index 80a61818f7..f7d1aeb8da 100644 --- a/gnu/services/ganeti.scm +++ b/gnu/services/ganeti.scm @@ -450,7 +450,7 @@ be submitted via this daemon and it schedules and starts them."))) (max-clients ganeti-rapi-configuration-max-clients ;integer (default 20)) (ssl? ganeti-rapi-configuration-ssl? ;Boolean - (default #f)) + (default #t)) (ssl-key ganeti-rapi-configuration-ssl-key ;string (default "/var/lib/ganeti/server.pem")) (ssl-cert ganeti-rapi-configuration-ssl-cert ;string diff --git a/gnu/tests/ganeti.scm b/gnu/tests/ganeti.scm index 0615edcde4..ff853a7149 100644 --- a/gnu/tests/ganeti.scm +++ b/gnu/tests/ganeti.scm @@ -70,6 +70,11 @@ (service ganeti-service-type (ganeti-configuration (file-storage-paths '("/srv/ganeti/file-storage")) + (rapi-configuration + (ganeti-rapi-configuration + ;; Disable TLS so we can test the RAPI without + ;; pulling in GnuTLS. + (ssl? #f))) (os %default-ganeti-os)))) %base-services)))) -- cgit v1.2.3 From 4656180d5de1fef2846bea9af27ae509f32376ba Mon Sep 17 00:00:00 2001 From: Oleg Pykhalov Date: Wed, 22 Jul 2020 09:47:16 +0300 Subject: services: nix: Fix sandbox. * gnu/tests/package-management.scm: New file. * gnu/local.mk: Add this. * gnu/services/nix.scm (): New record. (nix-activation): Generate Nix config file which fixes sandbox. (nix-service-type): Add default value. (nix-shepherd-service): Allow provide Nix package. * doc/guix.texi (Miscellaneous Services)[Nix service]: Document record. --- doc/guix.texi | 21 +++++++ gnu/local.mk | 1 + gnu/services/nix.scm | 91 +++++++++++++++++---------- gnu/tests/package-management.scm | 130 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 32 deletions(-) create mode 100644 gnu/tests/package-management.scm (limited to 'gnu/tests') diff --git a/doc/guix.texi b/doc/guix.texi index 8696a9b554..feef91b59c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -27599,6 +27599,27 @@ $ source /run/current-system/profile/etc/profile.d/nix.sh @end defvr +@deftp {Data Type} nix-configuration +This data type represents the configuration of the Nix daemon. + +@table @asis +@item @code{nix} (default: @code{nix}) +The Nix package to use. + +@item @code{sandbox} (default: @code{#t}) +Specifies whether builds are sandboxed by default. + +@item @code{build-sandbox-items} (default: @code{'()}) +This is a list of strings or objects appended to the +@code{build-sandbox-items} field of the configuration file. + +@item @code{extra-config} (default: @code{'()}) +This is a list of strings or objects appended to the configuration file. +It is used to pass extra text to be added verbatim to the configuration +file. +@end table +@end deftp + @node Setuid Programs @section Setuid Programs diff --git a/gnu/local.mk b/gnu/local.mk index a1bd6a644a..3eee908752 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -674,6 +674,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/mail.scm \ %D%/tests/messaging.scm \ %D%/tests/networking.scm \ + %D%/tests/package-management.scm \ %D%/tests/reconfigure.scm \ %D%/tests/rsync.scm \ %D%/tests/security-token.scm \ diff --git a/gnu/services/nix.scm b/gnu/services/nix.scm index 3c0065207d..75b2df02dc 100644 --- a/gnu/services/nix.scm +++ b/gnu/services/nix.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2019 Oleg Pykhalov +;;; Copyright © 2019, 2020 Oleg Pykhalov ;;; ;;; This file is part of GNU Guix. ;;; @@ -31,7 +31,9 @@ #:use-module (guix store) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) + #:use-module (ice-9 match) #:use-module (ice-9 format) + #:use-module (guix modules) #:export (nix-service-type)) ;;; Commentary: @@ -40,10 +42,17 @@ ;;; ;;; Code: - -;;; -;;; Accounts -;;; +(define-record-type* + nix-configuration make-nix-configuration + nix-configuration? + (package nix-configuration-package ;package + (default nix)) + (sandbox nix-configuration-sandbox ;boolean + (default #t)) + (build-sandbox-items nix-configuration-build-sandbox-items ;list of strings + (default '())) + (extra-config nix-configuration-extra-options ;list of strings + (default '()))) ;; Copied from gnu/services/base.scm (define* (nix-build-accounts count #:key @@ -74,32 +83,50 @@ GID." (id 40000)) (nix-build-accounts 10 #:group "nixbld"))) -(define (nix-activation _) - "Return the activation gexp." - (with-imported-modules '((guix build utils)) - #~(begin - (use-modules (guix build utils) - (srfi srfi-26)) - (for-each (cut mkdir-p <>) '("/nix/store" "/nix/var/log" - "/nix/var/nix/gcroots/per-user" - "/nix/var/nix/profiles/per-user")) - (chown "/nix/store" - (passwd:uid (getpw "root")) (group:gid (getpw "nixbld01"))) - (chmod "/nix/store" #o775) - (for-each (cut chmod <> #o777) '("/nix/var/nix/profiles" - "/nix/var/nix/profiles/per-user"))))) +(define nix-activation + ;; Return the activation gexp. + (match-lambda + (($ package sandbox build-sandbox-items extra-config) + (with-imported-modules (source-module-closure + '((guix build store-copy))) + #~(begin + (use-modules (guix build utils) + (ice-9 format) + (srfi srfi-1) + (srfi srfi-26)) + (for-each (cut mkdir-p <>) '("/nix/store" "/nix/var/log" + "/nix/var/nix/gcroots/per-user" + "/nix/var/nix/profiles/per-user")) + (chown "/nix/store" + (passwd:uid (getpw "root")) (group:gid (getpw "nixbld01"))) + (chmod "/nix/store" #o775) + (for-each (cut chmod <> #o777) '("/nix/var/nix/profiles" + "/nix/var/nix/profiles/per-user")) + (mkdir-p "/etc/nix") + (with-output-to-file "/etc/nix/nix.conf" + (lambda _ + (format #t "sandbox = ~a~%" (if #$sandbox "true" "false")) + ;; config.nix captures store file names. + (format #t "build-sandbox-paths = ~{~a ~}~%" + (append (append-map (cut call-with-input-file <> read) + '#$(map references-file + (list package))) + '#$build-sandbox-items)) + (for-each (cut display <>) '#$extra-config)))))))) -(define (nix-shepherd-service _) - "Return a for Nix." - (list - (shepherd-service - (provision '(nix-daemon)) - (documentation "Run nix-daemon.") - (requirement '()) - (start #~(make-forkexec-constructor - (list (string-append #$nix "/bin/nix-daemon")))) - (respawn? #f) - (stop #~(make-kill-destructor))))) +(define nix-shepherd-service + ;; Return a for Nix. + (match-lambda + (($ package _ ...) + (list + (shepherd-service + (provision '(nix-daemon)) + (documentation "Run nix-daemon.") + (requirement '()) + (start #~(make-forkexec-constructor + (list (string-append #$package "/bin/nix-daemon")))) + (respawn? #f) + (stop #~(make-kill-destructor))))))) (define nix-service-type (service-type @@ -108,7 +135,7 @@ GID." (list (service-extension shepherd-root-service-type nix-shepherd-service) (service-extension account-service-type nix-accounts) (service-extension activation-service-type nix-activation))) - (default-value '()) - (description "Run the Nix daemon."))) + (description "Run the Nix daemon.") + (default-value (nix-configuration)))) ;;; nix.scm ends here diff --git a/gnu/tests/package-management.scm b/gnu/tests/package-management.scm new file mode 100644 index 0000000000..087eaf923e --- /dev/null +++ b/gnu/tests/package-management.scm @@ -0,0 +1,130 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2020 Oleg Pykhalov +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu tests package-management) + #:use-module (gnu packages base) + #:use-module (gnu packages package-management) + #:use-module (gnu services) + #:use-module (gnu services networking) + #:use-module (gnu services nix) + #:use-module (gnu system) + #:use-module (gnu system vm) + #:use-module (gnu tests) + #:use-module (guix gexp) + #:use-module (guix packages) + #:export (%test-nix)) + +;;; Commentary: +;;; +;;; This module provides a test definition for the nix-daemon +;;; +;;; Code: + +(define* (run-nix-test name test-os) + "Run tests in TEST-OS, which has nix-daemon running." + (define os + (marionette-operating-system + test-os + #:imported-modules '((gnu services herd)))) + + (define vm + (virtual-machine + (operating-system os) + (port-forwardings '((8080 . 80))) + (memory-size 1024))) + + (define test + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (srfi srfi-11) + (srfi srfi-64) + (gnu build marionette) + (web client) + (web response)) + + (define marionette + (make-marionette (list #$vm))) + + (mkdir #$output) + (chdir #$output) + + (test-begin #$name) + + ;; XXX: Shepherd reads the config file *before* binding its control + ;; socket, so /var/run/shepherd/socket might not exist yet when the + ;; 'marionette' service is started. + (test-assert "shepherd socket ready" + (marionette-eval + `(begin + (use-modules (gnu services herd)) + (let loop ((i 10)) + (cond ((file-exists? (%shepherd-socket-file)) + #t) + ((> i 0) + (sleep 1) + (loop (- i 1))) + (else + 'failure)))) + marionette)) + + (test-assert "Nix daemon running" + (marionette-eval + '(begin + ;; Wait for nix-daemon to be up and running. + (start-service 'nix-daemon) + (with-output-to-file "guix-test.nix" + (lambda () + (display "\ +with import ; + +derivation { + system = builtins.currentSystem; + name = \"guix-test\"; + builder = shell; + args = [\"-c\" \"mkdir $out\\necho FOO > $out/foo\"]; + PATH = coreutils; +} +"))) + (zero? (system* (string-append #$nix "/bin/nix-build") + "--substituters" "" "--debug" "--no-out-link" + "guix-test.nix"))) + marionette)) + + (test-end) + + (exit (= (test-runner-fail-count (test-runner-current)) 0))))) + + (gexp->derivation (string-append name "-test") test)) + +(define %nix-os + ;; Return operating system under test. + (let ((base-os + (simple-operating-system + (service nix-service-type) + (service dhcp-client-service-type)))) + (operating-system + (inherit base-os) + (packages (cons nix (operating-system-packages base-os)))))) + +(define %test-nix + (system-test + (name "nix") + (description "Connect to a running nix-daemon") + (value (run-nix-test name %nix-os)))) + +;;; package-management.scm ends here -- cgit v1.2.3