diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2021-05-11 15:01:00 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2021-05-16 23:28:11 +0200 |
| commit | 938ffcbb0589adc07dc12c79eda3e1e2bb9e7cf8 (patch) | |
| tree | 9e997f834176b4e438aa1a007d44532af59b1388 | |
| parent | dc3504913de4a2c549482001f7087362f5400f29 (diff) | |
publish: Add '--negative-ttl'.
* guix/scripts/publish.scm (show-help, %options): Add '--negative-ttl'.
(render-narinfo, render-narinfo/cached, make-request-handler): Add #:negative-ttl
and honor it.
(run-publish-server): Add #:narinfo-negative-ttl and honor it.
(guix-publish): Honor '--negative-ttl'.
* tests/publish.scm ("negative TTL", "no negative TTL"): New tests.
| -rw-r--r-- | doc/guix.texi | 10 | ||||
| -rw-r--r-- | guix/scripts/publish.scm | 30 | ||||
| -rw-r--r-- | tests/publish.scm | 32 |
3 files changed, 63 insertions, 9 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index bfc714c5b68..a10943f2d55 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -12703,6 +12703,16 @@ Additionally, when @option{--cache} is used, cached entries that have | |||
| 12703 | not been accessed for @var{ttl} and that no longer have a corresponding | 12703 | not been accessed for @var{ttl} and that no longer have a corresponding |
| 12704 | item in the store, may be deleted. | 12704 | item in the store, may be deleted. |
| 12705 | 12705 | ||
| 12706 | @item --negative-ttl=@var{ttl} | ||
| 12707 | Similarly produce @code{Cache-Control} HTTP headers to advertise the | ||
| 12708 | time-to-live (TTL) of @emph{negative} lookups---missing store items, for | ||
| 12709 | which the HTTP 404 code is returned. By default, no negative TTL is | ||
| 12710 | advertised. | ||
| 12711 | |||
| 12712 | This parameter can help adjust server load and substitute latency by | ||
| 12713 | instructing cooperating clients to be more or less patient when a store | ||
| 12714 | item is missing. | ||
| 12715 | |||
| 12706 | @item --cache-bypass-threshold=@var{size} | 12716 | @item --cache-bypass-threshold=@var{size} |
| 12707 | When used in conjunction with @option{--cache}, store items smaller than | 12717 | When used in conjunction with @option{--cache}, store items smaller than |
| 12708 | @var{size} are immediately available, even when they are not yet in | 12718 | @var{size} are immediately available, even when they are not yet in |
diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm index 39bb224cad2..ef6fa5f074a 100644 --- a/guix/scripts/publish.scm +++ b/guix/scripts/publish.scm | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> | 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> |
| 3 | ;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org> | 3 | ;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org> |
| 4 | ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> | 4 | ;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> |
| 5 | ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 5 | ;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 6 | ;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com> | 6 | ;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com> |
| 7 | ;;; | 7 | ;;; |
| @@ -102,6 +102,8 @@ Publish ~a over HTTP.\n") %store-directory) | |||
| 102 | (display (G_ " | 102 | (display (G_ " |
| 103 | --ttl=TTL announce narinfos can be cached for TTL seconds")) | 103 | --ttl=TTL announce narinfos can be cached for TTL seconds")) |
| 104 | (display (G_ " | 104 | (display (G_ " |
| 105 | --negative-ttl=TTL announce missing narinfos can be cached for TTL seconds")) | ||
| 106 | (display (G_ " | ||
| 105 | --nar-path=PATH use PATH as the prefix for nar URLs")) | 107 | --nar-path=PATH use PATH as the prefix for nar URLs")) |
| 106 | (display (G_ " | 108 | (display (G_ " |
| 107 | --public-key=FILE use FILE as the public key for signatures")) | 109 | --public-key=FILE use FILE as the public key for signatures")) |
| @@ -224,6 +226,13 @@ usage." | |||
| 224 | (leave (G_ "~a: invalid duration~%") arg)) | 226 | (leave (G_ "~a: invalid duration~%") arg)) |
| 225 | (alist-cons 'narinfo-ttl (time-second duration) | 227 | (alist-cons 'narinfo-ttl (time-second duration) |
| 226 | result)))) | 228 | result)))) |
| 229 | (option '("negative-ttl") #t #f | ||
| 230 | (lambda (opt name arg result) | ||
| 231 | (let ((duration (string->duration arg))) | ||
| 232 | (unless duration | ||
| 233 | (leave (G_ "~a: invalid duration~%") arg)) | ||
| 234 | (alist-cons 'narinfo-negative-ttl (time-second duration) | ||
| 235 | result)))) | ||
| 227 | (option '("nar-path") #t #f | 236 | (option '("nar-path") #t #f |
| 228 | (lambda (opt name arg result) | 237 | (lambda (opt name arg result) |
| 229 | (alist-cons 'nar-path arg result))) | 238 | (alist-cons 'nar-path arg result))) |
| @@ -390,14 +399,14 @@ References: ~a~%" | |||
| 390 | 399 | ||
| 391 | (define* (render-narinfo store request hash | 400 | (define* (render-narinfo store request hash |
| 392 | #:key ttl (compressions (list %no-compression)) | 401 | #:key ttl (compressions (list %no-compression)) |
| 393 | (nar-path "nar")) | 402 | (nar-path "nar") negative-ttl) |
| 394 | "Render metadata for the store path corresponding to HASH. If TTL is true, | 403 | "Render metadata for the store path corresponding to HASH. If TTL is true, |
| 395 | advertise it as the maximum validity period (in seconds) via the | 404 | advertise it as the maximum validity period (in seconds) via the |
| 396 | 'Cache-Control' header. This allows 'guix substitute' to cache it for an | 405 | 'Cache-Control' header. This allows 'guix substitute' to cache it for an |
| 397 | appropriate duration. NAR-PATH specifies the prefix for nar URLs." | 406 | appropriate duration. NAR-PATH specifies the prefix for nar URLs." |
| 398 | (let ((store-path (hash-part->path store hash))) | 407 | (let ((store-path (hash-part->path store hash))) |
| 399 | (if (string-null? store-path) | 408 | (if (string-null? store-path) |
| 400 | (not-found request #:phrase "") | 409 | (not-found request #:phrase "" #:ttl negative-ttl) |
| 401 | (values `((content-type . (application/x-nix-narinfo)) | 410 | (values `((content-type . (application/x-nix-narinfo)) |
| 402 | ,@(if ttl | 411 | ,@(if ttl |
| 403 | `((cache-control (max-age . ,ttl))) | 412 | `((cache-control (max-age . ,ttl))) |
| @@ -512,7 +521,7 @@ interpreted as the basename of a store item." | |||
| 512 | 521 | ||
| 513 | (define* (render-narinfo/cached store request hash | 522 | (define* (render-narinfo/cached store request hash |
| 514 | #:key ttl (compressions (list %no-compression)) | 523 | #:key ttl (compressions (list %no-compression)) |
| 515 | (nar-path "nar") | 524 | (nar-path "nar") negative-ttl |
| 516 | cache pool) | 525 | cache pool) |
| 517 | "Respond to the narinfo request for REQUEST. If the narinfo is available in | 526 | "Respond to the narinfo request for REQUEST. If the narinfo is available in |
| 518 | CACHE, then send it; otherwise, return 404 and \"bake\" that nar and narinfo | 527 | CACHE, then send it; otherwise, return 404 and \"bake\" that nar and narinfo |
| @@ -536,7 +545,7 @@ requested using POOL." | |||
| 536 | #:compression | 545 | #:compression |
| 537 | (first compressions))))) | 546 | (first compressions))))) |
| 538 | (cond ((string-null? item) | 547 | (cond ((string-null? item) |
| 539 | (not-found request)) | 548 | (not-found request #:ttl negative-ttl)) |
| 540 | ((file-exists? cached) | 549 | ((file-exists? cached) |
| 541 | ;; Narinfo is in cache, send it. | 550 | ;; Narinfo is in cache, send it. |
| 542 | (values `((content-type . (application/x-nix-narinfo)) | 551 | (values `((content-type . (application/x-nix-narinfo)) |
| @@ -584,7 +593,7 @@ requested using POOL." | |||
| 584 | #:phrase "We're baking it" | 593 | #:phrase "We're baking it" |
| 585 | #:ttl 300))) ;should be available within 5m | 594 | #:ttl 300))) ;should be available within 5m |
| 586 | (else | 595 | (else |
| 587 | (not-found request #:phrase ""))))) | 596 | (not-found request #:phrase "" #:ttl negative-ttl))))) |
| 588 | 597 | ||
| 589 | (define (compress-nar cache item compression) | 598 | (define (compress-nar cache item compression) |
| 590 | "Save in directory CACHE the nar for ITEM compressed with COMPRESSION." | 599 | "Save in directory CACHE the nar for ITEM compressed with COMPRESSION." |
| @@ -974,7 +983,7 @@ methods, return the applicable compression." | |||
| 974 | (define* (make-request-handler store | 983 | (define* (make-request-handler store |
| 975 | #:key | 984 | #:key |
| 976 | cache pool | 985 | cache pool |
| 977 | narinfo-ttl | 986 | narinfo-ttl narinfo-negative-ttl |
| 978 | (nar-path "nar") | 987 | (nar-path "nar") |
| 979 | (compressions (list %no-compression))) | 988 | (compressions (list %no-compression))) |
| 980 | (define compression-type? | 989 | (define compression-type? |
| @@ -1006,10 +1015,12 @@ methods, return the applicable compression." | |||
| 1006 | #:cache cache | 1015 | #:cache cache |
| 1007 | #:pool pool | 1016 | #:pool pool |
| 1008 | #:ttl narinfo-ttl | 1017 | #:ttl narinfo-ttl |
| 1018 | #:negative-ttl narinfo-negative-ttl | ||
| 1009 | #:nar-path nar-path | 1019 | #:nar-path nar-path |
| 1010 | #:compressions compressions) | 1020 | #:compressions compressions) |
| 1011 | (render-narinfo store request hash | 1021 | (render-narinfo store request hash |
| 1012 | #:ttl narinfo-ttl | 1022 | #:ttl narinfo-ttl |
| 1023 | #:negative-ttl narinfo-negative-ttl | ||
| 1013 | #:nar-path nar-path | 1024 | #:nar-path nar-path |
| 1014 | #:compressions compressions))) | 1025 | #:compressions compressions))) |
| 1015 | ;; /nar/file/NAME/sha256/HASH | 1026 | ;; /nar/file/NAME/sha256/HASH |
| @@ -1068,7 +1079,7 @@ methods, return the applicable compression." | |||
| 1068 | #:key | 1079 | #:key |
| 1069 | advertise? port | 1080 | advertise? port |
| 1070 | (compressions (list %no-compression)) | 1081 | (compressions (list %no-compression)) |
| 1071 | (nar-path "nar") narinfo-ttl | 1082 | (nar-path "nar") narinfo-ttl narinfo-negative-ttl |
| 1072 | cache pool) | 1083 | cache pool) |
| 1073 | (when advertise? | 1084 | (when advertise? |
| 1074 | (let ((name (service-name))) | 1085 | (let ((name (service-name))) |
| @@ -1084,6 +1095,7 @@ methods, return the applicable compression." | |||
| 1084 | #:pool pool | 1095 | #:pool pool |
| 1085 | #:nar-path nar-path | 1096 | #:nar-path nar-path |
| 1086 | #:narinfo-ttl narinfo-ttl | 1097 | #:narinfo-ttl narinfo-ttl |
| 1098 | #:narinfo-negative-ttl narinfo-negative-ttl | ||
| 1087 | #:compressions compressions) | 1099 | #:compressions compressions) |
| 1088 | concurrent-http-server | 1100 | concurrent-http-server |
| 1089 | `(#:socket ,socket))) | 1101 | `(#:socket ,socket))) |
| @@ -1127,6 +1139,7 @@ methods, return the applicable compression." | |||
| 1127 | (user (assoc-ref opts 'user)) | 1139 | (user (assoc-ref opts 'user)) |
| 1128 | (port (assoc-ref opts 'port)) | 1140 | (port (assoc-ref opts 'port)) |
| 1129 | (ttl (assoc-ref opts 'narinfo-ttl)) | 1141 | (ttl (assoc-ref opts 'narinfo-ttl)) |
| 1142 | (negative-ttl (assoc-ref opts 'narinfo-negative-ttl)) | ||
| 1130 | (compressions (match (filter-map (match-lambda | 1143 | (compressions (match (filter-map (match-lambda |
| 1131 | (('compression . compression) | 1144 | (('compression . compression) |
| 1132 | compression) | 1145 | compression) |
| @@ -1192,6 +1205,7 @@ consider using the '--user' option!~%"))) | |||
| 1192 | "publish worker")) | 1205 | "publish worker")) |
| 1193 | #:nar-path nar-path | 1206 | #:nar-path nar-path |
| 1194 | #:compressions compressions | 1207 | #:compressions compressions |
| 1208 | #:narinfo-negative-ttl negative-ttl | ||
| 1195 | #:narinfo-ttl ttl)))))) | 1209 | #:narinfo-ttl ttl)))))) |
| 1196 | 1210 | ||
| 1197 | ;;; Local Variables: | 1211 | ;;; Local Variables: |
diff --git a/tests/publish.scm b/tests/publish.scm index 3e67c435acf..c3d086995ab 100644 --- a/tests/publish.scm +++ b/tests/publish.scm | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> | 2 | ;;; Copyright © 2015 David Thompson <davet@gnu.org> |
| 3 | ;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org> | 3 | ;;; Copyright © 2020 by Amar M. Singh <nly@disroot.org> |
| 4 | ;;; Copyright © 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> | 4 | ;;; Copyright © 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> |
| 5 | ;;; | 5 | ;;; |
| 6 | ;;; This file is part of GNU Guix. | 6 | ;;; This file is part of GNU Guix. |
| 7 | ;;; | 7 | ;;; |
| @@ -700,6 +700,36 @@ References: ~%" | |||
| 700 | (= (response-content-length response) (stat:size (stat log))) | 700 | (= (response-content-length response) (stat:size (stat log))) |
| 701 | (first (response-content-type response)))))) | 701 | (first (response-content-type response)))))) |
| 702 | 702 | ||
| 703 | (test-equal "negative TTL" | ||
| 704 | `(404 42) | ||
| 705 | |||
| 706 | (call-with-temporary-directory | ||
| 707 | (lambda (cache) | ||
| 708 | (let ((thread (with-separate-output-ports | ||
| 709 | (call-with-new-thread | ||
| 710 | (lambda () | ||
| 711 | (guix-publish "--port=6786" "-C0" | ||
| 712 | "--negative-ttl=42s")))))) | ||
| 713 | (wait-until-ready 6786) | ||
| 714 | |||
| 715 | (let* ((base "http://localhost:6786/") | ||
| 716 | (url (string-append base (make-string 32 #\z) | ||
| 717 | ".narinfo")) | ||
| 718 | (response (http-get url))) | ||
| 719 | (list (response-code response) | ||
| 720 | (match (assq-ref (response-headers response) 'cache-control) | ||
| 721 | ((('max-age . ttl)) ttl) | ||
| 722 | (_ #f)))))))) | ||
| 723 | |||
| 724 | (test-equal "no negative TTL" | ||
| 725 | `(404 #f) | ||
| 726 | (let* ((uri (publish-uri | ||
| 727 | (string-append "/" (make-string 32 #\z) | ||
| 728 | ".narinfo"))) | ||
| 729 | (response (http-get uri))) | ||
| 730 | (list (response-code response) | ||
| 731 | (assq-ref (response-headers response) 'cache-control)))) | ||
| 732 | |||
| 703 | (test-equal "/log/NAME not found" | 733 | (test-equal "/log/NAME not found" |
| 704 | 404 | 734 | 404 |
| 705 | (let ((uri (publish-uri "/log/does-not-exist"))) | 735 | (let ((uri (publish-uri "/log/does-not-exist"))) |
