unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
blob 7a713916bb35dccc3a2ba8fa7030c2bc0789a911 9473 bytes (raw)
name: src/cuirass/remote.scm 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
 
;;; remote.scm -- Build on remote machines.
;;; Copyright © 2020 Mathieu Othacehe <othacehe@gnu.org>
;;;
;;; This file is part of Cuirass.
;;;
;;; 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 (cuirass remote)
  #:use-module (guix config)
  #:use-module (guix derivations)
  #:use-module (guix records)
  #:use-module (guix store)
  #:use-module (guix ui)
  #:use-module (guix build download)
  #:use-module ((guix build utils) #:select (mkdir-p))
  #:use-module (guix scripts publish)
  #:use-module (simple-zmq)
  #:use-module (rnrs bytevectors)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (ice-9 match)
  #:use-module (ice-9 rdelim)
  #:export (worker
            worker?
            worker-name
            worker-systems
            worker->sexp
            sexp->worker
            generate-worker-name

            publish-server
            add-substitute-url

            zmq-frontend-socket-name
            zmq-frontend-endpoint
            zmq-poll*
            zmq-socket-ready?
            zmq-empty-delimiter

            zmq-build-request-message
            zmq-no-build-message
            zmq-build-started-message
            zmq-build-failed-message
            zmq-build-succeeded-message
            zmq-worker-ready-message
            zmq-worker-request-work-message
            zmq-read-message

            remote-server-service-type
            remote-build-socket
            remote-build
            remote-build-poll))

\f
;;;
;;; Workers.
;;;

(define-record-type* <worker>
  worker make-worker
  worker?
  (name           worker-name)
  (systems        worker-systems))

(define (worker->sexp worker)
  "Return an sexp describing WORKER."
  (let ((name (worker-name worker))
        (systems (worker-systems worker)))
    `(worker
      (name ,name)
      (systems ,systems))))

(define (sexp->worker sexp)
  "Turn SEXP, an sexp as returned by 'worker->sexp', into a <worker> record."
  (match sexp
    (('worker ('name name) ('systems systems))
     (worker
      (name name)
      (systems systems)))))


(define %seed
  (seed->random-state
   (logxor (getpid) (car (gettimeofday)))))

(define (integer->alphanumeric-char n)
  "Map N, an integer in the [0..62] range, to an alphanumeric character."
  (cond ((< n 10)
         (integer->char (+ (char->integer #\0) n)))
        ((< n 36)
         (integer->char (+ (char->integer #\A) (- n 10))))
        ((< n 62)
         (integer->char (+ (char->integer #\a) (- n 36))))
        (else
         (error "integer out of bounds" n))))

(define (random-string len)
  "Compute a random string of size LEN where each character is alphanumeric."
  (let loop ((chars '())
             (len len))
    (if (zero? len)
        (list->string chars)
        (let ((n (random 62 %seed)))
          (loop (cons (integer->alphanumeric-char n) chars)
                (- len 1))))))

(define (generate-worker-name)
  "Return the service name of the server."
  (string-append (gethostname) "-" (random-string 4)))

\f
;;;
;;; Store publishing.
;;;

(define (add-substitute-url store url)
  "Add URL to the list of STORE substitutes-urls."
  (set-build-options store
                     #:use-substitutes? #t
                     #:fallback? #f
                     #:keep-going? #t
                     #:print-build-trace #t
                     #:build-verbosity 1
                     #:substitute-urls
                     (cons url %default-substitute-urls)))

(define* (publish-server port
                         #:key
                         public-key
                         private-key)
  "This procedure starts a publishing server listening on PORT in a new
process and returns the pid of the forked process.  Use PUBLIC-KEY and
PRIVATE-KEY to sign narinfos."
  (match (primitive-fork)
    (0
     (parameterize ((%public-key public-key)
                    (%private-key private-key))
       (with-store store
         (let* ((address (make-socket-address AF_INET INADDR_ANY 0))
                (socket-address
                 (make-socket-address (sockaddr:fam address)
                                      (sockaddr:addr address)
                                      port))
                (socket (open-server-socket socket-address)))
           (run-publish-server socket store
                               #:compressions
                               (list %default-gzip-compression))))))
    (pid pid)))

\f
;;;
;;; ZMQ.
;;;

(define %zmq-context
  (zmq-create-context))

(define (zmq-frontend-socket-name)
  "Return the name of the ZMQ frontend socket."
  (string-append %state-directory "/remote-build-socket"))

(define (zmq-frontend-endpoint)
  "Return a ZMQ endpoint allowing client connections using the IPC transport."
  (string-append "ipc://" (zmq-frontend-socket-name)))

(define (EINTR-safe proc)
  "Return a variant of PROC that catches EINTR 'zmq-error' exceptions and
retries a call to PROC."
  (define (safe . args)
    (catch 'zmq-error
      (lambda ()
        (apply proc args))
      (lambda (key errno . rest)
        (if (= errno EINTR)
            (apply safe args)
            (apply throw key errno rest)))))

  safe)

(define zmq-poll*
  ;; Return a variant of ZMQ-POLL that catches EINTR errors.
  (EINTR-safe zmq-poll))

(define (zmq-socket-ready? items socket)
  "Return #t if the given SOCKET is part of ITEMS, a list returned by a
'zmq-poll' call, return #f otherwise."
  (find (lambda (item)
          (eq? (poll-item-socket item) socket))
        items))

(define (zmq-read-message msg)
  (call-with-input-string msg read))

(define (zmq-empty-delimiter)
  "Return an empty ZMQ delimiter used to format message envelopes."
  (make-bytevector 0))

;; ZMQ Messages.
(define* (zmq-build-request-message drv #:optional system)
  "Return a message requesting the build of DRV for SYSTEM."
  (format #f "~s" `(build (drv ,drv) (system ,system))))

(define (zmq-no-build-message)
  "Return a message that indicates that no builds are available."
  (format #f "~s" `(no-build)))

(define (zmq-build-started-message drv)
  "Return a message that indicates that the build of DRV has started."
  (format #f "~s" `(build-started (drv ,drv))))

(define (zmq-build-failed-message drv)
  "Return a message that indicates that the build of DRV has failed."
  (format #f "~s" `(build-failed (drv ,drv))))

(define (zmq-build-succeeded-message drv url)
  "Return a message that indicates that the build of DRV is done."
  (format #f "~s" `(build-succeeded (drv ,drv) (url ,url))))

(define (zmq-worker-ready-message worker)
  "Return a message that indicates that WORKER is ready."
  (format #f "~s" `(worker-ready ,worker)))

(define (zmq-worker-request-work-message name)
  "Return a message that indicates that WORKER is requesting work."
  (format #f "~s" `(worker-request-work ,name)))

\f
;;;
;;; Remote builds.
;;;

(define remote-server-service-type
  "_remote-server._tcp")

(define (remote-build-socket)
  "Return a socket used to communicate with the remote build server."
  (let ((socket (zmq-create-socket %zmq-context ZMQ_DEALER))
        (endpoint (zmq-frontend-endpoint)))
    (zmq-connect socket endpoint)
    socket))

(define* (remote-build socket drvs systems)
  "Builds DRVS using the remote build mechanism.  A build command is sent on
SOCKET to the build server for each derivation.

SYSTEMS is a list describing the systems of each derivations in the DRVS list.
It is used for performance reasons, so that the remote server doesn't need to
call 'read-derivation-from-file' for each derivation, which can be an
expensive operation."
  (for-each
   (lambda (drv system)
     ;; We need to prefix the command with an empty delimiter
     ;; because the DEALER socket is connected to a ROUTER
     ;; socket. See "zmq-start-proxy" procedure.
     (zmq-send-msg-parts-bytevector
      socket
      (list (make-bytevector 0)
            (string->bv (zmq-build-request-message drv system)))))
   drvs systems))

(define* (remote-build-poll socket event-proc
                            #:key
                            (timeout 1000))
  "Poll SOCKET for messages and call EVENT-PROC each time a build event is
received, return if no event occured for TIMEOUT milliseconds."
  (define (parse-result result)
    (match (zmq-read-message result)
      (('build-started ('drv drv))
       (event-proc (list 'build-started drv)))
      (('build-succeeded ('drv drv) ('url url))
       (event-proc (list 'build-succeeded drv)))
      (('build-failed ('drv drv))
       (event-proc (list 'build-failed drv)))))

  (let* ((poll-items (list
                      (poll-item socket ZMQ_POLLIN)))
         (items (zmq-poll* poll-items timeout)))
    (when (zmq-socket-ready? items socket)
      (match (zmq-get-msg-parts-bytevector socket '())
        ((empty result)
         (parse-result (bv->string result)))))))

debug log:

solving 7a71391 ...
found 7a71391 in https://yhetil.org/guix-patches/87czzso4dj.fsf@gnu.org/

applying [1/1] https://yhetil.org/guix-patches/87czzso4dj.fsf@gnu.org/
diff --git a/src/cuirass/remote.scm b/src/cuirass/remote.scm
new file mode 100644
index 0000000..7a71391

Checking patch src/cuirass/remote.scm...
Applied patch src/cuirass/remote.scm cleanly.

index at:
100644 7a713916bb35dccc3a2ba8fa7030c2bc0789a911	src/cuirass/remote.scm

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).