GNU bug report logs - #75144
[PATCH] machine: Implement 'hetzner-environment-type'.

Previous Next

Package: guix-patches;

Reported by: Roman Scherer <roman <at> burningswell.com>

Date: Fri, 27 Dec 2024 16:48:02 UTC

Severity: normal

Tags: patch

Done: Ludovic Courtès <ludo <at> gnu.org>

Bug is archived. No further changes may be made.

Full log


View this message in rfc822 format

From: Roman Scherer <roman <at> burningswell.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Roman Scherer <roman <at> burningswell.com>, Maxim Cournoyer <maxim.cournoyer <at> gmail.com>, Simon Tournier <zimon.toutoune <at> gmail.com>, Mathieu Othacehe <othacehe <at> gnu.org>, Tobias Geerinckx-Rice <me <at> tobias.gr>, Josselin Poiret <dev <at> jpoiret.xyz>, Christopher Baines <guix <at> cbaines.net>, 75144 <at> debbugs.gnu.org
Subject: [bug#75144] [PATCH] machine: Implement 'hetzner-environment-type'.
Date: Sun, 19 Jan 2025 17:59:06 +0100
[Message part 1 (text/plain, inline)]
Hi Ludo,

thanks for your review. Here is a v2, I hope I addressed your previous
comments with it, but I need some help.

As you suggested I also added some tests. Some use mocking, and some run
against the Hetzner API, if the GUIX_HETZNER_API_TOKEN env var is set.

./pre-inst-env make check TESTS="tests/machine/hetzner/http.scm"
./pre-inst-env make check TESTS="tests/machine/hetzner.scm"

All tests pass when I run them in the Geiser REPL, where I developed them.

But I have some trouble with one test that uses mocking. The
"deploy-machine-mock-with-unprovisioned-server" test in
tests/machine/hetzner.scm only fails when run in the terminal. :?

I'm using the "mock" function from (guix tests) to mock some HTTP and SSH
calls. The issue is that I see different behaviour whether I run the tests in
Geiser vs in the Terminal.

In Geiser I see the following output for this test, in it passes:

-------------------------------------------------------------------------------
creating 'cx42' server for 'guix-x86'...
successfully created 'cx42' x86 server for 'guix-x86'
enabling rescue system on 'guix-x86'...
MOCK ENABLE RESUCE
successfully enabled rescue system on 'guix-x86'
powering on server for 'guix-x86'...
MOCK POWER ON
successfully powered on server for 'guix-x86'
connecting via SSH to '1.2.3.4' using '/tmp/guix-hetzner-machine-test-key'...
MOCK OPEN SSH SESSION
installing rescue system packages on 'guix-x86'...
MOCK RUNNING SCRIPT: /tmp/guix/deploy/hetzner-machine-rescue-install-packages
successfully installed rescue system packages on 'guix-x86'
setting up partitions on 'guix-x86'...
MOCK RUNNING SCRIPT: /tmp/guix/deploy/hetzner-machine-rescue-partition
successfully setup partitions on 'guix-x86'
installing guix operating system on 'guix-x86'...
MOCK RUNNING SCRIPT: /tmp/guix/deploy/hetzner-machine-rescue-install-os
successfully installed guix operating system on 'guix-x86'
rebooting server for 'guix-x86'...
successfully rebooted server for 'guix-x86'
connecting via SSH to '1.2.3.4' using '/tmp/guix-hetzner-machine-test-key'...
MOCK OPEN SSH SESSION
-------------------------------------------------------------------------------

You can see that calls to "hetzner-machine-ssh-run-script" are mocked, because
"MOCK RUNNING SCRIPT" is printed multiple times.

But in a "guix shell -D" terminal I see the following output for the test, and
it is failing:

-------------------------------------------------------------------------------

creating 'cx42' server for 'guix-x86'...
successfully created 'cx42' x86 server for 'guix-x86'
enabling rescue system on 'guix-x86'...
MOCK ENABLE RESUCE
successfully enabled rescue system on 'guix-x86'
powering on server for 'guix-x86'...
MOCK POWER ON
successfully powered on server for 'guix-x86'
connecting via SSH to '1.2.3.4' using '/tmp/guix-hetzner-machine-test-key'...
MOCK OPEN SSH SESSION
installing rescue system packages on 'guix-x86'...
test-name: deploy-machine-mock-with-unprovisioned-server
location: /home/roman/workspace/guix/tests/machine/hetzner.scm:189

actual-value: #f
actual-error:
+ (guile-ssh-error
+   "%gssh-make-sftp-session"
+   "Could not create a SFTP session"
+   #<session #<undefined>@1.2.3.4:22 (disconnected) ffff85596de0>
+   #f)
result: FAIL

;;; [2025/01/19 17:39:16.791023, 0] [GSSH ERROR] Could not create a SFTP session: #<session #<undefined>@1.2.3.4:22 (disconnected) ffff85596de0>

-------------------------------------------------------------------------------

The tests fails here trying to use a disconnected SSH session object, that I
returned in a mocked call. This code should actually never be reached, because
I mock the "hetzner-machine-ssh-run-script" call. But for some reason the mock
is not working here. The "MOCK RUNNING SCRIPT" output is missing.

Do you have any ideas what could be going on here? I suspect this might be due
to some optimization or env issue, but I'm pretty lost.

I attached a WIP v2 for now. Will send a v3 and a separate patch for the ssh
modification once I fixed this mock test.

Thanks, Roman.

[v2-0001-machine-Implement-hetzner-environment-type.patch (text/x-patch, attachment)]
[Message part 3 (text/plain, inline)]
Ludovic Courtès <ludo <at> gnu.org> writes:

> Hello Roman,
>
> Roman Scherer <roman <at> burningswell.com> skribis:
>
>> * gnu/machine/hetzner.scm: New file.
>> * gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
>> * guix/ssh.scm (open-ssh-session): Add stricthostkeycheck option.
>> * doc/guix.texi (Invoking guix deploy): Add documentation for
>> 'hetzner-configuration'.
>>
>> Change-Id: Idc17dbc33279ecbf3cbfe2c53d7699140f8b9f41
>
> Thumbs up for this big piece of work, one that I think is important for
> the project!  ‘guix deploy’ is a great idea but it desperately needs
> more backends like this one.
>
> I’m not familiar with Hetzner so I’ll comment on more general aspects.
> Chris, perhaps you can provide feedback on Hetzner-specific issues?  I
> think we could put this backend to good use for Guix infra since a few
> services are running at Hetzner.
>
>> +@deftp {Data Type} hetzner-configuration
>> +This is the data type describing the server that should be created for a
>> +machine with an @code{environment} of @code{hetzner-environment-type}.
>
> Could you add a sentence providing more context like:
>
>   It allows you to configure deployment to a @acronym{VPS, virtual
>   private server} hosted by @uref{https://www.hetzner.com, Hetzner}.
>
>> +@item @code{authorize?} (default: @code{#t})
>> +If true, the coordinator's public signing key
>
> “coordinator” has nothing to do here I guess.
>
>> +@item @code{labels} (default: @code{'()})
>> +A user defined alist of key/value pairs attached to the server. Keys and
>> +values must be strings. For more information, see
>> +@uref{https://docs.hetzner.cloud/#labels, Labels}.
>
> Maybe add a short example?
>
>> +@item @code{location} (default: @code{"fsn1"})
>> +The name of a @uref{https://docs.hetzner.com/cloud/general/locations,
>> +location} to create the server in.
>
> Maybe add: “For example, @code{"fsn1"} corresponds to the Hetzner site
> in Falkenstein, Germany, while @code{"sin"} corresponds to its site in
> Singapore.”
>
>> +@item @code{server-type} (default: @code{"cx42"})
>> +The name of the
>> +@uref{https://docs.hetzner.com/cloud/servers/overview#server-types,
>> +server type} this server should be created with.
>
> Likewise, an example would be elcome.
>
>> +@item @code{ssh-key}
>> +The path to the SSH private key to use to authenticate with the remote
>> +host.
>
> s/path to/file name of/
>
>> +The following example shows the definition of 2 machines that are
>
> s/2/two/
>
>> +vCPUs and 32 GB of RAM on the @code{aarch64} architecture, the second
>
> s/@code{aarch64}/AArch64/
>
>> +shared vCPUs and 32 GB of RAM on the @code{x86_64} architecture.
>
> Drop @code.
>
>> +@lisp
>> +(use-modules (gnu machine)
>> +             (gnu machine hetzner))
>> +
>> +(list (machine
>> +       (operating-system %hetzner-os-arm)
>> +       (environment hetzner-environment-type)
>> +       (configuration (hetzner-configuration
>> +                       (server-type "cax41")
>> +                       (ssh-key "/home/charlie/.ssh/id_rsa"))))
>> +      (machine
>> +       (operating-system %hetzner-os-x86)
>> +       (environment hetzner-environment-type)
>> +       (configuration (hetzner-configuration
>> +                       (server-type "cpx51")
>> +                       (ssh-key "/home/charlie/.ssh/id_rsa")))))
>
> Nice!
>
>> +API key} should provision 2 machines for you.
>
> s/2/two/
>
>> +  #:use-module (ice-9 receive)
>
> The code base preferable uses SRFI-71 for multiple-value returns.
>
>> +      (raise (formatted-message
>> +              (G_ "Expected a list of Hetzner API responses")))))
>
> Messages should start with a lower-case letter (for all the messages in
> this file).
>
> Please add the file to ‘po/guix/POTFILES.in’ so that it’s actually
> subject to translation.
>
>> +(define (hetzner-api-response-read port)
>> +  "Read the Hetzner API response from PORT."
>> +  (let* ((response (read-response port))
>> +         (body (read-response-body response)))
>> +    (hetzner-api-response
>> +     (body (json-string->scm (bytevector->string body "UTF-8")))
>
> Just ‘string->utf8’ (shorter).
>
> More importantly: instead of ‘json-string->scm’ (which gives an alist,
> leading to ‘assoc-ref’ calls all over the code base along with free-form
> alists, which is very error-prone), could you use ‘define-json-mapping’?
>
> In essence it’s like ‘define-record-type’ but it additionally define how
> to map a JSON dictionary to a Scheme record.  There are several examples
> in Guix, such as (guix swh).
>
> For clarity, it might be useful to move all the hetzner-api-* bits to a
> separate module, for example (gnu machine hetzner http).  WDYT?
>
>
> The rest of the code looks nice to me (modulo alists :-)) but that’s
> about all I can say.  It’s quite a significant body of code.  What would
> you suggest to prevent bitrot and support maintenance?  Are there parts
> of it that could be usefully tested automatically, possibly by mocking
> part of the Hetzner API?  Or are there tips on how you tested it that
> could be written down in the file itself?
>
>
> Could you move the (guix ssh) bits to a separate patch?
>
>> +++ b/guix/ssh.scm
>> @@ -103,7 +103,8 @@ (define* (open-ssh-session host #:key user port identity
>>                             host-key
>>                             (compression %compression)
>>                             (timeout 3600)
>> -                           (connection-timeout 10))
>> +                           (connection-timeout 10)
>> +                           (stricthostkeycheck #t))
>>    "Open an SSH session for HOST and return it.  IDENTITY specifies the file
>>  name of a private key to use for authenticating with the host.  When USER,
>>  PORT, or IDENTITY are #f, use default values or whatever '~/.ssh/config'
>
> Please update the docstring.
>
> Rather ‘strict-host-key-check?’ to match naming conventions, even if
> Guile-SSH calls it that way.
>
>> @@ -137,7 +138,8 @@ (define* (open-ssh-session host #:key user port identity
>>
>>                                 ;; Speed up RPCs by creating sockets with
>>                                 ;; TCP_NODELAY.
>> -                               #:nodelay #t)))
>> +                               #:nodelay #t
>> +                               #:stricthostkeycheck stricthostkeycheck)))
>
> Not sure what this does actually.  Looks like the main part is the
> “when stricthostkeycheck” condition that comes below, no?
>
> Could you send a second version?
>
> Thank you!
>
> Ludo’.
[signature.asc (application/pgp-signature, inline)]

This bug report was last modified 123 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.