aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019, 2020, 2021, 2022 Christopher Baines <mail@cbaines.net>
;;;
;;; 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 <http://www.gnu.org/licenses/>.

(define-module (gnu tests guix)
  #:use-module (gnu home)
  #:use-module (gnu home services)
  #:use-module (gnu tests)
  #:use-module (gnu system)
  #:use-module (gnu system file-systems)
  #:use-module (gnu system shadow)
  #:use-module (gnu system vm)
  #:use-module (gnu services)
  #:use-module (gnu services guix)
  #:use-module (gnu services databases)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services networking)
  #:use-module (gnu packages databases)
  #:use-module (guix packages)
  #:use-module (guix modules)
  #:use-module (guix records)
  #:use-module (guix gexp)
  #:use-module (guix store)
  #:use-module (guix utils)
  #:use-module (ice-9 match)
  #:export (%test-guix-build-coordinator
            %test-guix-data-service
            %test-guix-home-service
            %test-nar-herder
            %test-bffe))

;;;
;;; Guix Build Coordinator
;;;

(define %guix-build-coordinator-os
  (simple-operating-system
   (service dhcp-client-service-type)
   (service guix-build-coordinator-service-type)))

(define (run-guix-build-coordinator-test)
  (define os
    (marionette-operating-system
     %guix-build-coordinator-os
     #:imported-modules '((gnu services herd)
                          (guix combinators))))

  (define forwarded-port 8745)

  (define vm
    (virtual-machine
     (operating-system os)
     (memory-size 1024)
     (port-forwardings `((,forwarded-port . 8745)))))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-11) (srfi srfi-64)
                       (gnu build marionette)
                       (web uri)
                       (web client)
                       (web response))

          (define marionette
            (make-marionette (list #$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "guix-build-coordinator")

          (test-assert "service running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'guix-build-coordinator)
                  (#f #f)
                  (('service response-parts ...)
                   (match (assq-ref response-parts 'running)
                     ((pid) pid)))))
             marionette))

          (test-equal "http-get"
            200
            (let-values
                (((response text)
                  (http-get #$(simple-format
                               #f "http://localhost:~A/metrics" forwarded-port)
                            #:decode-body? #t)))
              (response-code response)))

          (test-end))))

  (gexp->derivation "guix-build-coordinator-test" test))

(define %test-guix-build-coordinator
  (system-test
   (name "guix-build-coordinator")
   (description "Connect to a running Guix Build Coordinator.")
   (value (run-guix-build-coordinator-test))))


;;;
;;; Guix Data Service
;;;

(define guix-data-service-initial-database-setup-service
  (let ((user "guix_data_service")
        (name "guix_data_service"))
    (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 '(guix-data-service-initial-database-setup))
     (start start-gexp)
     (stop #~(const #f))
     (respawn? #f)
     (one-shot? #t)
     (documentation "Setup Guix Data Service database."))))

(define %guix-data-service-os
  (simple-operating-system
   (service dhcp-client-service-type)
   (service postgresql-service-type
            (postgresql-configuration
             (postgresql postgresql)
             (config-file
              (postgresql-config-file
               (hba-file
                (plain-file "pg_hba.conf"
                            "
local	all	all			trust
host	all	all	127.0.0.1/32 	trust
host	all	all	::1/128 	trust"))))))
   (service guix-data-service-type
            (guix-data-service-configuration
             (host "0.0.0.0")))
   (simple-service 'guix-data-service-database-setup
                   shepherd-root-service-type
                   (list guix-data-service-initial-database-setup-service))))

(define (run-guix-data-service-test)
  (define os
    (marionette-operating-system
     %guix-data-service-os
     #:imported-modules '((gnu services herd)
                          (guix combinators))))

  (define forwarded-port 8080)

  (define vm
    (virtual-machine
     (operating-system os)
     (memory-size 1024)
     (port-forwardings `((,forwarded-port . 8765)))))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-11) (srfi srfi-64)
                       (gnu build marionette)
                       (web uri)
                       (web client)
                       (web response))

          (define marionette
            (make-marionette (list #$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "guix-data-service")

          (test-assert "service running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'guix-data-service)
                  (#f #f)
                  (('service response-parts ...)
                   (match (assq-ref response-parts 'running)
                     ((pid) pid)))))
             marionette))

          (test-assert "process jobs service running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'guix-data-service-process-jobs)
                  (#f #f)
                  (('service response-parts ...)
                   (match (assq-ref response-parts 'running)
                     ((pid) pid)))))
             marionette))

          ;; The service starts immediately but replies with status 500 until
          ;; initialization is complete, so keep trying for a while.
          (define (try-http-get attempts)
            (let ((status
                   (let-values (((response text)
                                 (http-get #$(simple-format
                                              #f "http://localhost:~A/healthcheck"
                                              forwarded-port))))
                     (response-code response))))
              (if (or (= status 200) (<= attempts 1))
                  status
                  (begin (sleep 5)
                         (try-http-get (- attempts 1))))))

          (test-equal "http-get"
            200
            (try-http-get 12))

          (test-end))))

  (gexp->derivation "guix-data-service-test" test))

(define %test-guix-data-service
  (system-test
   (name "guix-data-service")
   (description "Connect to a running Guix Data Service.")
   (value (run-guix-data-service-test))))


;;;
;;; Guix Home
;;;

(define %guix-home-service-he
  (home-environment
   (services
    (list (simple-service 'guix-home-service-test
                          home-files-service-type
                          `(("guix-home-service-activated"
                             ,(plain-file "guix-home-service-activated"
                                          "Guix Home service activated"))))))))

(define %guix-home-service-os
  (simple-operating-system
   (service guix-home-service-type
            `(("alice" ,%guix-home-service-he)))))

(define (run-guix-home-service-test)
  (define os
    (marionette-operating-system
     %guix-home-service-os
     #:imported-modules '((gnu services herd))))

  (define vm
    (virtual-machine
     (operating-system os)
     (memory-size 1024)))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-64)
                       (gnu build marionette))

          (define marionette
            (make-marionette (list #$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "guix-home-service")

          (test-assert "service started"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'guix-home-alice)
                  (#f #f)
                  ;; herd returns (running #f), likely because of one shot,
                  ;; so consider any non-error a success.
                  (('service response-parts ...) #t)))
             marionette))

          (test-assert "file-exists"
            (marionette-eval
             '(begin
                (sleep 3) ;make sure service has time to symlink files
                (file-exists? "/home/alice/guix-home-service-activated"))
             marionette))

          (test-end))))

  (gexp->derivation "guix-home-service-test" test))

(define %test-guix-home-service
  (system-test
   (name "guix-home-service")
   (description "Activate a Guix home environment.")
   (value (run-guix-home-service-test))))


;;;
;;; Nar Herder
;;;

(define %nar-herder-os
  (simple-operating-system
   (service dhcp-client-service-type)
   (service nar-herder-service-type
            (nar-herder-configuration
             (host "0.0.0.0")
             ;; Not a realistic value, but works for the test
             (storage "/tmp")))))

(define (run-nar-herder-test)
  (define os
    (marionette-operating-system
     %nar-herder-os
     #:imported-modules '((gnu services herd)
                          (guix combinators))))

  (define forwarded-port
    (nar-herder-configuration-port
     (nar-herder-configuration)))

  (define vm
    (virtual-machine
     (operating-system os)
     (memory-size 1024)
     (port-forwardings `((,forwarded-port . ,forwarded-port)))))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-11) (srfi srfi-64)
                       (gnu build marionette)
                       (web uri)
                       (web client)
                       (web response))

          (define marionette
            (make-marionette (list #$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "nar-herder")

          (test-assert "service running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'nar-herder)
                  (#f #f)
                  (('service response-parts ...)
                   (match (assq-ref response-parts 'running)
                     ((pid) pid)))))
             marionette))

          (test-equal "http-get"
            404
            (let-values
                (((response text)
                  (http-get #$(simple-format
                               #f "http://localhost:~A/" forwarded-port)
                            #:decode-body? #t)))
              (response-code response)))

          (test-end))))

  (gexp->derivation "nar-herder-test" test))

(define %test-nar-herder
  (system-test
   (name "nar-herder")
   (description "Connect to a running Nar Herder server.")
   (value (run-nar-herder-test))))


;;;
;;; Build Farm Front-end
;;;

(define %bffe-os
  (simple-operating-system
   (service dhcp-client-service-type)
   (service guix-build-coordinator-service-type)
   (service bffe-service-type
            (bffe-configuration
             (arguments
              #~(list
                 #:web-server-args
                 '(#:port 8767
                   #:controller-args
                   (#:title "Test title"))))))))

(define (run-bffe-test)
  (define os
    (marionette-operating-system
     %bffe-os
     #:imported-modules '((gnu services herd)
                          (guix combinators))))

  (define forwarded-port 8767)

  (define vm
    (virtual-machine
     (operating-system os)
     (memory-size 1024)
     (port-forwardings `((,forwarded-port . 8767)))))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-11) (srfi srfi-64)
                       (gnu build marionette)
                       (web uri)
                       (web client)
                       (web response))

          (define marionette
            (make-marionette (list #$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "bffe")

          (test-assert "service running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (match (start-service 'bffe)
                  (#f #f)
                  (('service response-parts ...)
                   (match (assq-ref response-parts 'running)
                     ((pid) pid)))))
             marionette))

          (test-equal "http-get"
            200
            (let-values
                (((response text)
                  (http-get #$(simple-format
                               #f "http://localhost:~A/" forwarded-port)
                            #:decode-body? #t)))
              (response-code response)))

          (test-end))))

  (gexp->derivation "bffe-test" test))

(define %test-bffe
  (system-test
   (name "bffe")
   (description "Connect to a running Build Farm Front-end.")
   (value (run-bffe-test))))
hd") (uid %system-id-max) (gid 500) (real-name "sshd") (shell "/nologin") (directory "/var/empty")) (password-entry (name "guix") (uid 30000) (gid 499) (real-name "Guix") (shell "/nologin") (directory "/var/empty"))) (allocate-passwd (list (user-account (name "alice") (comment "Alice") (shell "/bin/sh") (group "users")) (user-account (name "bob") (comment "Bob") (shell "/bin/gash") (group "wheel")) (user-account (name "sshd") (system? #t) (comment "sshd") (home-directory "/var/empty") (shell "/nologin") (group "sshd")) (user-account (name "guix") (system? #t) (comment "Guix") (home-directory "/var/empty") (shell "/nologin") (group "guix") (uid 30000))) (list (group-entry (name "users") (gid 1000)) (group-entry (name "wheel") (gid 1001)) (group-entry (name "sshd") (gid 500)) (group-entry (name "guix") (gid 499))))) (test-equal "allocate-passwd with previous state" ;; Make sure bits of state are preserved: UID, no reuse of previously-used ;; UIDs, and shell. (list (password-entry (name "alice") (uid 1234) (gid 1000) (real-name "Alice Smith") (shell "/bin/sh") (directory "/home/alice")) (password-entry (name "charlie") (uid 1236) (gid 1000) (real-name "Charlie") (shell "/bin/sh") (directory "/home/charlie"))) (allocate-passwd (list (user-account (name "alice") (comment "Alice") (shell "/bin/sh") ;honored (group "users")) (user-account (name "charlie") (comment "Charlie") (shell "/bin/sh") (group "users"))) (list (group-entry (name "users") (gid 1000))) (list (password-entry (name "alice") (uid 1234) (gid 9999) (real-name "Alice Smith") (shell "/gnu/.../bin/gash") ;ignored (directory "/home/alice")) (password-entry (name "bob") (uid 1235) (gid 1001) (real-name "Bob") (shell "/bin/sh") (directory "/home/bob"))))) (test-equal "user+group-databases" ;; The whole shebang. (list (list (group-entry (name "a") (gid %id-min) (members '("bob"))) (group-entry (name "b") (gid (+ 1 %id-min)) (members '("alice"))) (group-entry (name "s") (gid %system-id-max))) (list (password-entry (name "alice") (real-name "Alice") (uid %id-min) (gid %id-min) (directory "/a")) (password-entry (name "bob") (real-name "Bob") (uid (+ 1 %id-min)) (gid (+ 1 %id-min)) (directory "/b")) (password-entry (name "nobody") (uid 65534) (gid %system-id-max) (directory "/var/empty"))) (list (shadow-entry (name "alice") (last-change 100) (password (crypt "initial pass" "$6$"))) (shadow-entry (name "bob") (last-change 50) (password (crypt "foo" "$6$"))) (shadow-entry (name "nobody") (last-change 100)))) (call-with-values (lambda () (user+group-databases (list (user-account (name "alice") (comment "Alice") (home-directory "/a") (group "a") (supplementary-groups '("b")) (password (crypt "initial pass" "$6$"))) (user-account (name "bob") (comment "Bob") (home-directory "/b") (group "b") (supplementary-groups '("a"))) (user-account (name "nobody") (group "s") (uid 65534) (home-directory "/var/empty"))) (list (user-group (name "a")) (user-group (name "b")) (user-group (name "s") (system? #t))) #:current-passwd '() #:current-shadow (list (shadow-entry (name "bob") (password (crypt "foo" "$6$")) (last-change 50))) #:current-groups '() #:current-time (lambda (type) (make-time type 0 (* 24 3600 100))))) list)) (test-equal "subuid+subgid-databases" ;; The whole process. (list (list (subid-entry (name "root") (start %subordinate-id-min) (count 100)) (subid-entry (name "alice") (start (+ %subordinate-id-min 100)) (count 200)) (subid-entry (name "bob") (start (+ %subordinate-id-min 100 200)) (count 200))) (list (subid-entry (name "root") (start %subordinate-id-min) (count 200)) (subid-entry (name "alice") (start (+ %subordinate-id-min 200)) (count 400)) (subid-entry (name "charlie") (start (+ %subordinate-id-min 200 400)) (count 300)))) (call-with-values (lambda () (subuid+subgid-databases (list (subid-range (name "root") (start %subordinate-id-min) (count 100)) (subid-range (name "alice") (start (+ %subordinate-id-min 100)) (count 200)) (subid-range (name "bob") (count 200))) (list (subid-range (name "alice") (count 400)) (subid-range (name "charlie") (count 300))) #:current-subgids (list (subid-range (name "root") (start %subordinate-id-min) (count 200))) #:current-subuids '())) list)) (test-end "accounts")