web.scm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2017, 2019 Christopher Baines <mail@cbaines.net>
  4. ;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
  5. ;;; Copyright © 2018 Pierre-Antoine Rouby <pierre-antoine.rouby@inria.fr>
  6. ;;; Copyright © 2018 Marius Bakke <mbakke@fastmail.com>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (gnu tests web)
  23. #:use-module (gnu tests)
  24. #:use-module (gnu system)
  25. #:use-module (gnu system file-systems)
  26. #:use-module (gnu system shadow)
  27. #:use-module (gnu system vm)
  28. #:use-module (gnu services)
  29. #:use-module (gnu services web)
  30. #:use-module (gnu services databases)
  31. #:use-module (gnu services getmail)
  32. #:use-module (gnu services networking)
  33. #:use-module (gnu services shepherd)
  34. #:use-module (gnu services mail)
  35. #:use-module (gnu packages databases)
  36. #:use-module (gnu packages patchutils)
  37. #:use-module (gnu packages python)
  38. #:use-module (gnu packages web)
  39. #:use-module (guix packages)
  40. #:use-module (guix modules)
  41. #:use-module (guix records)
  42. #:use-module (guix gexp)
  43. #:use-module (guix store)
  44. #:use-module (guix utils)
  45. #:use-module (ice-9 match)
  46. #:export (%test-httpd
  47. %test-nginx
  48. %test-varnish
  49. %test-php-fpm
  50. %test-hpcguix-web
  51. %test-tailon
  52. %test-patchwork))
  53. (define %index.html-contents
  54. ;; Contents of the /index.html file.
  55. "Hello, guix!")
  56. (define %make-http-root
  57. ;; Create our server root in /srv.
  58. #~(begin
  59. (mkdir "/srv")
  60. (mkdir "/srv/http")
  61. (call-with-output-file "/srv/http/index.html"
  62. (lambda (port)
  63. (display #$%index.html-contents port)))))
  64. (define retry-on-error
  65. #~(lambda* (f #:key times delay)
  66. (let loop ((attempt 1))
  67. (match (catch
  68. #t
  69. (lambda ()
  70. (cons #t
  71. (f)))
  72. (lambda args
  73. (cons #f
  74. args)))
  75. ((#t . return-value)
  76. return-value)
  77. ((#f . error-args)
  78. (if (>= attempt times)
  79. error-args
  80. (begin
  81. (sleep delay)
  82. (loop (+ 1 attempt)))))))))
  83. (define* (run-webserver-test name test-os #:key (log-file #f) (http-port 8080))
  84. "Run tests in %NGINX-OS, which has nginx running and listening on
  85. HTTP-PORT."
  86. (define os
  87. (marionette-operating-system
  88. test-os
  89. #:imported-modules '((gnu services herd)
  90. (guix combinators))))
  91. (define forwarded-port 8080)
  92. (define vm
  93. (virtual-machine
  94. (operating-system os)
  95. (port-forwardings `((,http-port . ,forwarded-port)))))
  96. (define test
  97. (with-imported-modules '((gnu build marionette))
  98. #~(begin
  99. (use-modules (srfi srfi-11) (srfi srfi-64)
  100. (gnu build marionette)
  101. (web uri)
  102. (web client)
  103. (web response))
  104. (define marionette
  105. (make-marionette (list #$vm)))
  106. (mkdir #$output)
  107. (chdir #$output)
  108. (test-begin #$name)
  109. (test-assert #$(string-append name " service running")
  110. (marionette-eval
  111. '(begin
  112. (use-modules (gnu services herd))
  113. (match (start-service '#$(string->symbol name))
  114. (#f #f)
  115. (('service response-parts ...)
  116. (match (assq-ref response-parts 'running)
  117. ((#t) #t)
  118. ((pid) (number? pid))))))
  119. marionette))
  120. (test-assert "HTTP port ready"
  121. (wait-for-tcp-port #$forwarded-port marionette))
  122. ;; Retrieve the index.html file we put in /srv.
  123. (test-equal "http-get"
  124. '(200 #$%index.html-contents)
  125. (let-values
  126. (((response text)
  127. (http-get #$(simple-format
  128. #f "http://localhost:~A/index.html" forwarded-port)
  129. #:decode-body? #t)))
  130. (list (response-code response) text)))
  131. #$@(if log-file
  132. `((test-assert ,(string-append "log file exists " log-file)
  133. (marionette-eval
  134. '(file-exists? ,log-file)
  135. marionette)))
  136. '())
  137. (test-end)
  138. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  139. (gexp->derivation (string-append name "-test") test))
  140. ;;;
  141. ;;; HTTPD
  142. ;;;
  143. (define %httpd-os
  144. (simple-operating-system
  145. (service dhcp-client-service-type)
  146. (service httpd-service-type
  147. (httpd-configuration
  148. (config
  149. (httpd-config-file
  150. (listen '("8080"))))))
  151. (simple-service 'make-http-root activation-service-type
  152. %make-http-root)))
  153. (define %test-httpd
  154. (system-test
  155. (name "httpd")
  156. (description "Connect to a running HTTPD server.")
  157. (value (run-webserver-test name %httpd-os
  158. #:log-file "/var/log/httpd/error_log"))))
  159. ;;;
  160. ;;; NGINX
  161. ;;;
  162. (define %nginx-servers
  163. ;; Server blocks.
  164. (list (nginx-server-configuration
  165. (listen '("8080")))))
  166. (define %nginx-os
  167. ;; Operating system under test.
  168. (simple-operating-system
  169. (service dhcp-client-service-type)
  170. (service nginx-service-type
  171. (nginx-configuration
  172. (log-directory "/var/log/nginx")
  173. (server-blocks %nginx-servers)))
  174. (simple-service 'make-http-root activation-service-type
  175. %make-http-root)))
  176. (define %test-nginx
  177. (system-test
  178. (name "nginx")
  179. (description "Connect to a running NGINX server.")
  180. (value (run-webserver-test name %nginx-os
  181. #:log-file "/var/log/nginx/access.log"))))
  182. ;;;
  183. ;;; Varnish
  184. ;;;
  185. (define %varnish-vcl
  186. (mixed-text-file
  187. "varnish-test.vcl"
  188. "vcl 4.0;
  189. backend dummy { .host = \"127.1.1.1\"; }
  190. sub vcl_recv { return(synth(200, \"OK\")); }
  191. sub vcl_synth {
  192. synthetic(\"" %index.html-contents "\");
  193. set resp.http.Content-Type = \"text/plain\";
  194. return(deliver);
  195. }"))
  196. (define %varnish-os
  197. (simple-operating-system
  198. (service dhcp-client-service-type)
  199. ;; Pretend to be a web server that serves %index.html-contents.
  200. (service varnish-service-type
  201. (varnish-configuration
  202. (name "/tmp/server")
  203. ;; Use a small VSL buffer to fit in the test VM.
  204. (parameters '(("vsl_space" . "4M")))
  205. (vcl %varnish-vcl)))
  206. ;; Proxy the "server" using the builtin configuration.
  207. (service varnish-service-type
  208. (varnish-configuration
  209. (parameters '(("vsl_space" . "4M")))
  210. (backend "localhost:80")
  211. (listen '(":8080"))))))
  212. (define %test-varnish
  213. (system-test
  214. (name "varnish")
  215. (description "Test the Varnish Cache server.")
  216. (value (run-webserver-test "varnish-default" %varnish-os))))
  217. ;;;
  218. ;;; PHP-FPM
  219. ;;;
  220. (define %make-php-fpm-http-root
  221. ;; Create our server root in /srv.
  222. #~(begin
  223. (mkdir "/srv")
  224. (call-with-output-file "/srv/index.php"
  225. (lambda (port)
  226. (display "<?php
  227. phpinfo();
  228. echo(\"Computed by php:\".((string)(2+3)));
  229. ?>\n" port)))))
  230. (define %php-fpm-nginx-server-blocks
  231. (list (nginx-server-configuration
  232. (root "/srv")
  233. (locations
  234. (list (nginx-php-location)))
  235. (listen '("8042"))
  236. (ssl-certificate #f)
  237. (ssl-certificate-key #f))))
  238. (define %php-fpm-os
  239. ;; Operating system under test.
  240. (simple-operating-system
  241. (service dhcp-client-service-type)
  242. (service php-fpm-service-type)
  243. (service nginx-service-type
  244. (nginx-configuration
  245. (server-blocks %php-fpm-nginx-server-blocks)))
  246. (simple-service 'make-http-root activation-service-type
  247. %make-php-fpm-http-root)))
  248. (define* (run-php-fpm-test #:optional (http-port 8042))
  249. "Run tests in %PHP-FPM-OS, which has nginx running and listening on
  250. HTTP-PORT, along with php-fpm."
  251. (define os
  252. (marionette-operating-system
  253. %php-fpm-os
  254. #:imported-modules '((gnu services herd)
  255. (guix combinators))))
  256. (define vm
  257. (virtual-machine
  258. (operating-system os)
  259. (port-forwardings `((8080 . ,http-port)))))
  260. (define test
  261. (with-imported-modules '((gnu build marionette)
  262. (guix build utils))
  263. #~(begin
  264. (use-modules (srfi srfi-11) (srfi srfi-64)
  265. (gnu build marionette)
  266. (web uri)
  267. (web client)
  268. (web response))
  269. (define marionette
  270. (make-marionette (list #$vm)))
  271. (mkdir #$output)
  272. (chdir #$output)
  273. (test-begin "php-fpm")
  274. (test-assert "php-fpm running"
  275. (marionette-eval
  276. '(begin
  277. (use-modules (gnu services herd))
  278. (match (start-service 'php-fpm)
  279. (#f #f)
  280. (('service response-parts ...)
  281. (match (assq-ref response-parts 'running)
  282. ((pid) (number? pid))))))
  283. marionette))
  284. (test-assert "nginx running"
  285. (marionette-eval
  286. '(begin
  287. (use-modules (gnu services herd))
  288. (start-service 'nginx))
  289. marionette))
  290. (test-equal "http-get"
  291. 200
  292. (let-values (((response text)
  293. (http-get "http://localhost:8080/index.php"
  294. #:decode-body? #t)))
  295. (response-code response)))
  296. (test-equal "php computed result is sent"
  297. "Computed by php:5"
  298. (let-values (((response text)
  299. (http-get "http://localhost:8080/index.php"
  300. #:decode-body? #t)))
  301. (begin
  302. (use-modules (ice-9 regex))
  303. (let ((matches (string-match "Computed by php:5" text)))
  304. (and matches
  305. (match:substring matches 0))))))
  306. (test-end)
  307. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  308. (gexp->derivation "php-fpm-test" test))
  309. (define %test-php-fpm
  310. (system-test
  311. (name "php-fpm")
  312. (description "Test PHP-FPM through nginx.")
  313. (value (run-php-fpm-test))))
  314. ;;;
  315. ;;; hpcguix-web
  316. ;;;
  317. (define* (run-hpcguix-web-server-test name test-os)
  318. "Run tests in %HPCGUIX-WEB-OS, which has hpcguix-web running."
  319. (define os
  320. (marionette-operating-system
  321. test-os
  322. #:imported-modules '((gnu services herd)
  323. (guix combinators))))
  324. (define vm
  325. (virtual-machine
  326. (operating-system os)
  327. (port-forwardings '((8080 . 5000)))
  328. (memory-size 1024)))
  329. (define test
  330. (with-imported-modules '((gnu build marionette))
  331. #~(begin
  332. (use-modules (srfi srfi-11) (srfi srfi-64)
  333. (ice-9 match)
  334. (gnu build marionette)
  335. (web uri)
  336. (web client)
  337. (web response))
  338. (define marionette
  339. (make-marionette (list #$vm)))
  340. (mkdir #$output)
  341. (chdir #$output)
  342. (test-begin #$name)
  343. (test-assert "hpcguix-web running"
  344. (marionette-eval
  345. '(begin
  346. (use-modules (gnu services herd))
  347. (match (start-service 'hpcguix-web)
  348. (#f #f)
  349. (('service response-parts ...)
  350. (match (assq-ref response-parts 'running)
  351. ((pid) (number? pid))))))
  352. marionette))
  353. (test-equal "http-get"
  354. 200
  355. (begin
  356. (wait-for-tcp-port 5000 marionette)
  357. (#$retry-on-error
  358. (lambda ()
  359. (let-values (((response text)
  360. (http-get "http://localhost:8080")))
  361. (response-code response)))
  362. #:times 10
  363. #:delay 5)))
  364. (test-end)
  365. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  366. (gexp->derivation (string-append name "-test") test))
  367. (define %hpcguix-web-specs
  368. ;; Server config gexp.
  369. #~(define site-config
  370. (hpcweb-configuration
  371. (title-prefix "[TEST] HPCGUIX-WEB"))))
  372. (define %hpcguix-web-os
  373. (simple-operating-system
  374. (service dhcp-client-service-type)
  375. (service hpcguix-web-service-type
  376. (hpcguix-web-configuration
  377. (specs %hpcguix-web-specs)))))
  378. (define %test-hpcguix-web
  379. (system-test
  380. (name "hpcguix-web")
  381. (description "Connect to a running hpcguix-web server.")
  382. (value (run-hpcguix-web-server-test name %hpcguix-web-os))))
  383. (define %tailon-os
  384. ;; Operating system under test.
  385. (simple-operating-system
  386. (service dhcp-client-service-type)
  387. (service tailon-service-type
  388. (tailon-configuration
  389. (config-file
  390. (tailon-configuration-file
  391. (bind "0.0.0.0:8080")))))))
  392. (define* (run-tailon-test #:optional (http-port 8081))
  393. "Run tests in %TAILON-OS, which has tailon running and listening on
  394. HTTP-PORT."
  395. (define os
  396. (marionette-operating-system
  397. %tailon-os
  398. #:imported-modules '((gnu services herd)
  399. (guix combinators))))
  400. (define vm
  401. (virtual-machine
  402. (operating-system os)
  403. (port-forwardings `((,http-port . 8080)))))
  404. (define test
  405. (with-imported-modules '((gnu build marionette))
  406. #~(begin
  407. (use-modules (srfi srfi-11) (srfi srfi-64)
  408. (ice-9 match)
  409. (gnu build marionette)
  410. (web uri)
  411. (web client)
  412. (web response))
  413. (define marionette
  414. ;; Forward the guest's HTTP-PORT, where tailon is listening, to
  415. ;; port 8080 in the host.
  416. (make-marionette (list #$vm)))
  417. (mkdir #$output)
  418. (chdir #$output)
  419. (test-begin "tailon")
  420. (test-assert "service running"
  421. (marionette-eval
  422. '(begin
  423. (use-modules (gnu services herd))
  424. (start-service 'tailon))
  425. marionette))
  426. (test-equal "http-get"
  427. 200
  428. (#$retry-on-error
  429. (lambda ()
  430. (let-values (((response text)
  431. (http-get #$(format
  432. #f
  433. "http://localhost:~A/"
  434. http-port)
  435. #:decode-body? #t)))
  436. (response-code response)))
  437. #:times 10
  438. #:delay 5))
  439. (test-end)
  440. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  441. (gexp->derivation "tailon-test" test))
  442. (define %test-tailon
  443. (system-test
  444. (name "tailon")
  445. (description "Connect to a running Tailon server.")
  446. (value (run-tailon-test))))
  447. ;;;
  448. ;;; Patchwork
  449. ;;;
  450. (define (patchwork-initial-database-setup-service configuration)
  451. (define start-gexp
  452. #~(lambda ()
  453. (let ((pid (primitive-fork))
  454. (postgres (getpwnam "postgres")))
  455. (if (eq? pid 0)
  456. (dynamic-wind
  457. (const #t)
  458. (lambda ()
  459. (setgid (passwd:gid postgres))
  460. (setuid (passwd:uid postgres))
  461. (primitive-exit
  462. (if (and
  463. (zero?
  464. (system* #$(file-append postgresql "/bin/createuser")
  465. #$(patchwork-database-configuration-user
  466. configuration)))
  467. (zero?
  468. (system* #$(file-append postgresql "/bin/createdb")
  469. "-O"
  470. #$(patchwork-database-configuration-user
  471. configuration)
  472. #$(patchwork-database-configuration-name
  473. configuration))))
  474. 0
  475. 1)))
  476. (lambda ()
  477. (primitive-exit 1)))
  478. (zero? (cdr (waitpid pid)))))))
  479. (shepherd-service
  480. (requirement '(postgres))
  481. (provision '(patchwork-postgresql-user-and-database))
  482. (start start-gexp)
  483. (stop #~(const #f))
  484. (respawn? #f)
  485. (documentation "Setup patchwork database.")))
  486. (define (patchwork-os patchwork)
  487. (simple-operating-system
  488. (service dhcp-client-service-type)
  489. (service httpd-service-type
  490. (httpd-configuration
  491. (config
  492. (httpd-config-file
  493. (listen '("8080"))))))
  494. (service postgresql-service-type
  495. (postgresql-configuration
  496. (postgresql postgresql-10)))
  497. (service patchwork-service-type
  498. (patchwork-configuration
  499. (patchwork patchwork)
  500. (domain "localhost")
  501. (settings-module
  502. (patchwork-settings-module
  503. (allowed-hosts (list domain))
  504. (default-from-email "")))
  505. (getmail-retriever-config
  506. (getmail-retriever-configuration
  507. (type "SimpleIMAPSSLRetriever")
  508. (server "imap.example.com")
  509. (port 993)
  510. (username "username")
  511. (password "password")
  512. (extra-parameters
  513. '((mailboxes . ("INBOX"))))))))
  514. (simple-service 'patchwork-database-setup
  515. shepherd-root-service-type
  516. (list
  517. (patchwork-initial-database-setup-service
  518. (patchwork-database-configuration))))))
  519. (define (run-patchwork-test patchwork)
  520. "Run tests in %NGINX-OS, which has nginx running and listening on
  521. HTTP-PORT."
  522. (define os
  523. (marionette-operating-system
  524. (patchwork-os patchwork)
  525. #:imported-modules '((gnu services herd)
  526. (guix combinators))))
  527. (define forwarded-port 8080)
  528. (define vm
  529. (virtual-machine
  530. (operating-system os)
  531. (port-forwardings `((8080 . ,forwarded-port)))
  532. (memory-size 1024)))
  533. (define test
  534. (with-imported-modules '((gnu build marionette))
  535. #~(begin
  536. (use-modules (srfi srfi-11) (srfi srfi-64)
  537. (ice-9 match)
  538. (gnu build marionette)
  539. (web uri)
  540. (web client)
  541. (web response))
  542. (define marionette
  543. (make-marionette (list #$vm)))
  544. (mkdir #$output)
  545. (chdir #$output)
  546. (test-begin "patchwork")
  547. (test-assert "patchwork-postgresql-user-and-service started"
  548. (marionette-eval
  549. '(begin
  550. (use-modules (gnu services herd))
  551. (match (start-service 'patchwork-postgresql-user-and-database)
  552. (#f #f)
  553. (('service response-parts ...)
  554. (match (assq-ref response-parts 'running)
  555. ((#t) #t)
  556. ((pid) (number? pid))))))
  557. marionette))
  558. (test-assert "httpd running"
  559. (marionette-eval
  560. '(begin
  561. (use-modules (gnu services herd))
  562. (start-service 'httpd))
  563. marionette))
  564. (test-equal "http-get"
  565. 200
  566. (#$retry-on-error
  567. (lambda ()
  568. (let-values
  569. (((response text)
  570. (http-get #$(simple-format
  571. #f "http://localhost:~A/" forwarded-port)
  572. #:decode-body? #t)))
  573. (response-code response)))
  574. #:times 10
  575. #:delay 5))
  576. (test-end)
  577. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  578. (gexp->derivation "patchwork-test" test))
  579. (define %test-patchwork
  580. (system-test
  581. (name "patchwork")
  582. (description "Connect to a running Patchwork service.")
  583. (value (run-patchwork-test patchwork))))