From d3a2fef9a982401507a53f97095f078c31db35b0 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Mon, 10 Jan 2022 16:39:41 -0500 Subject: [PATCH] Pull the privatestorage test code out of the Nix test module --- nixos/tests/private-storage.nix | 171 +++-------------------------- nixos/tests/test_privatestorage.py | 148 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 156 deletions(-) create mode 100644 nixos/tests/test_privatestorage.py diff --git a/nixos/tests/private-storage.nix b/nixos/tests/private-storage.nix index a208ce24..d6fdfaf9 100644 --- a/nixos/tests/private-storage.nix +++ b/nixos/tests/private-storage.nix @@ -1,25 +1,14 @@ { pkgs }: let + makeTestScript = (import ../lib/testing.nix).makeTestScript; + sshPrivateKey = ./probeuser_ed25519; sshPublicKey = ./probeuser_ed25519.pub; + sshUsers = { root = (builtins.readFile sshPublicKey); probeuser = (builtins.readFile sshPublicKey); }; - # Generate a command which can be used with runOnNode to ssh to the given - # host. - ssh = username: hostname: [ - "cp" sshPrivateKey "/tmp/ssh_key" ";" - "chmod" "0400" "/tmp/ssh_key" ";" - "ssh" "-oStrictHostKeyChecking=no" "-i" "/tmp/ssh_key" "${username}@${hostname}" ":" - ]; - - # Separate helper programs so we can write as little python inside a string - # inside a nix expression as possible. - run-introducer = ./run-introducer.py; - run-client = ./run-client.py; - get-passes = ./get-passes.py; - exercise-storage = ./exercise-storage.py; # This is a test double of the Stripe API server. It is extremely simple. # It barely knows how to respond to exactly the API endpoints we use, @@ -72,18 +61,6 @@ let networking.firewall.enable = false; networking.dhcpcd.enable = false; }; - - # Return a python program fragment to run a shell command on one of the nodes. - # The first argument is the name of the node. The second is a list of the - # argv to run. - # - # The program's output is piped to systemd-cat and the python fragment - # evaluates to success if the command exits with a success status. - runOnNode = node: argv: - let - command = builtins.concatStringsSep " " argv; - in - "${node}.succeed('set -eo pipefail; ${command} | systemd-cat')"; in { # https://nixos.org/nixos/manual/index.html#sec-nixos-tests # https://nixos.mayflower.consulting/blog/2019/07/11/leveraging-nixos-tests-in-your-project/ @@ -177,134 +154,16 @@ in { }; # Test the machines with a Python program. - testScript = '' - # Boot the VMs. We used to do them all in parallel but the boot - # sequence got flaky at some point for some reason I don't - # understand. :/ It might be related to this: - # - # https://discourse.nixos.org/t/nixos-ppc64le-vm-does-not-have-dev-vda-device/11548/9 - # - # See <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix> for the Nix - # that constructs the QEMU command that gets run. - # - # Boot them one at a time for now. - issuer.connect() - introducer.connect() - storage.connect() - client.connect() - api_stripe_com.connect() - - # The issuer and the storage server should accept SSH connections. This - # doesn't prove it is so but if it fails it's a pretty good indication - # it isn't so. - storage.wait_for_open_port(22) - ${runOnNode "issuer" (ssh "probeuser" "storage")} - ${runOnNode "issuer" (ssh "root" "storage")} - issuer.wait_for_open_port(22) - ${runOnNode "storage" (ssh "probeuser" "issuer")} - ${runOnNode "storage" (ssh "root" "issuer")} - - # Set up a Tahoe-LAFS introducer. - introducer.copy_from_host('${pemFile}', '/tmp/node.pem') - - try: - ${runOnNode "introducer" [ run-introducer "/tmp/node.pem" (toString introducerPort) introducerFURL ]} - except: - code, output = introducer.execute('cat /tmp/stdout /tmp/stderr') - introducer.log(output) - raise - - # - # Get a Tahoe-LAFS storage server up. - # - code, version = storage.execute('tahoe --version') - storage.log(version) - - # The systemd unit should reach the running state. - storage.wait_for_unit('tahoe.storage.service') - - # Some while after that the Tahoe-LAFS node should listen on the web API - # port. The port number here has to agree with the port number set in - # the private-storage.nix module. - storage.wait_for_open_port(3456) - - # Once the web API is listening it should be possible to scrape some - # status from the node if it is really working. - storage.succeed('tahoe -d /var/db/tahoe-lafs/storage status') - - # It should have Eliot logging turned on as well. - storage.succeed('[ -e /var/db/tahoe-lafs/storage/logs/eliot.json ]') - - # - # Storage appears to be working so try to get a client to speak with it. - # - ${runOnNode "client" [ run-client "/tmp/client" introducerFURL issuerURL ristrettoPublicKey ]} - client.wait_for_open_port(3456) - - # Make sure the fake Stripe API server is ready for requests. - try: - api_stripe_com.wait_for_unit("api.stripe.com") - except: - code, output = api_stripe_com.execute('journalctl -u api.stripe.com') - api_stripe_com.log(output) - raise - - # Get some ZKAPs from the issuer. - try: - ${runOnNode "client" [ - get-passes - "http://127.0.0.1:3456" - "/tmp/client/private/api_auth_token" - issuerURL - voucher - ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr'); - client.log(output) - - # Dump the fake Stripe API server logs, too, since the error may arise - # from a PaymentServer/Stripe interaction. - code, output = api_stripe_com.execute('journalctl -u api.stripe.com') - api_stripe_com.log(output) - raise - - # The client should be prepped now. Make it try to use some storage. - try: - ${runOnNode "client" [ exercise-storage "/tmp/client" ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr') - client.log(output) - raise - - # It should be possible to restart the storage service without the - # storage node fURL changing. - try: - furlfile = '/var/db/tahoe-lafs/storage/private/storage-plugin.privatestorageio-zkapauthz-v1.furl' - before = storage.execute('cat ' + furlfile) - ${runOnNode "storage" [ "systemctl" "restart" "tahoe.storage" ]} - after = storage.execute('cat ' + furlfile) - if (before != after): - raise Exception('fURL changes after storage node restart') - except: - code, output = storage.execute('cat /tmp/stdout /tmp/stderr') - storage.log(output) - raise - - # The client should actually still work, too. - try: - ${runOnNode "client" [ exercise-storage "/tmp/client" ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr') - client.log(output) - raise - - # The issuer metrics should be accessible from the monitoring network. - issuer.execute('ifconfig lo:fauxvpn 172.23.23.2/24') - issuer.wait_until_succeeds("nc -z 172.23.23.2 80") - issuer.succeed('curl --silent --insecure --fail --output /dev/null http://172.23.23.2/metrics') - # The issuer metrics should NOT be accessible from any other network. - issuer.fail('curl --silent --insecure --fail --output /dev/null http://localhost/metrics') - client.fail('curl --silent --insecure --fail --output /dev/null http://issuer/metrics') - issuer.execute('ifconfig lo:fauxvpn down') - ''; + testScript = makeTestScript { + testpath = ./test_privatestorage.py; + kwargs = { + inherit sshPrivateKey pemFile introducerPort introducerFURL issuerURL ristrettoPublicKey voucher; + + # Supply some helper programs to help the tests stay a bit higher level. + run_introducer = ./run-introducer.py; + run_client = ./run-client.py; + get_passes = ./get-passes.py; + exercise_storage = ./exercise-storage.py; + }; + }; } diff --git a/nixos/tests/test_privatestorage.py b/nixos/tests/test_privatestorage.py new file mode 100644 index 00000000..dc060d51 --- /dev/null +++ b/nixos/tests/test_privatestorage.py @@ -0,0 +1,148 @@ +def runOnNode(node, argv): + """ + Run a shell command on one of the nodes. The first argument is the name + of the node. The second is a list of the argv to run. + + The program's output is piped to systemd-cat and the python fragment + evaluates to success if the command exits with a success status. + """ + try: + node.succeed('set -eo pipefail; {} | systemd-cat'.format(" ".join(argv))) + except Exception as e: + code, output = node.execute('cat /tmp/stdout /tmp/stderr') + introducer.log(output) + raise + +def ssh(username, sshPrivateKey, hostname): + """ + Generate a command which can be used with runOnNode to ssh to the given + host. + """ + return [ + "cp", sshPrivateKey, "/tmp/ssh_key", ";", + "chmod", "0400", "/tmp/ssh_key", ";", + "ssh", "-oStrictHostKeyChecking=no", "-i", "/tmp/ssh_key", + "{username}@{hostname}".format(username=username, hostname=hostname), ":", + ] + +def test( + sshPrivateKey, + pemFile, + run_introducer, + run_client, + get_passes, + exercise_storage, + introducerPort, + introducerFURL, + issuerURL, + ristrettoPublicKey, + voucher, +): + """ + """ + # Boot the VMs. We used to do them all in parallel but the boot + # sequence got flaky at some point for some reason I don't + # understand. :/ It might be related to this: + # + # https://discourse.nixos.org/t/nixos-ppc64le-vm-does-not-have-dev-vda-device/11548/9 + # + # See <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix> for the Nix + # that constructs the QEMU command that gets run. + # + # Boot them one at a time for now. + issuer.connect() + introducer.connect() + storage.connect() + client.connect() + api_stripe_com.connect() + + # The issuer and the storage server should accept SSH connections. This + # doesn't prove it is so but if it fails it's a pretty good indication + # it isn't so. + storage.wait_for_open_port(22) + runOnNode(issuer, ssh("probeuser", sshPrivateKey, "storage")) + runOnNode(issuer, ssh("root", sshPrivateKey, "storage")) + issuer.wait_for_open_port(22) + runOnNode(storage, ssh("probeuser", sshPrivateKey, "issuer")) + runOnNode(storage, ssh("root", sshPrivateKey, "issuer")) + + # Set up a Tahoe-LAFS introducer. + introducer.copy_from_host(pemFile, '/tmp/node.pem') + + runOnNode(introducer, [run_introducer, "/tmp/node.pem", str(introducerPort), introducerFURL]) + + # + # Get a Tahoe-LAFS storage server up. + # + code, version = storage.execute('tahoe --version') + storage.log(version) + + # The systemd unit should reach the running state. + storage.wait_for_unit('tahoe.storage.service') + + # Some while after that the Tahoe-LAFS node should listen on the web API + # port. The port number here has to agree with the port number set in + # the private-storage.nix module. + storage.wait_for_open_port(3456) + + # Once the web API is listening it should be possible to scrape some + # status from the node if it is really working. + storage.succeed('tahoe -d /var/db/tahoe-lafs/storage status') + + # It should have Eliot logging turned on as well. + storage.succeed('[ -e /var/db/tahoe-lafs/storage/logs/eliot.json ]') + + # + # Storage appears to be working so try to get a client to speak with it. + # + runOnNode(client, [run_client, "/tmp/client", introducerFURL, issuerURL, ristrettoPublicKey]) + client.wait_for_open_port(3456) + + # Make sure the fake Stripe API server is ready for requests. + try: + api_stripe_com.wait_for_unit("api.stripe.com") + except: + code, output = api_stripe_com.execute('journalctl -u api.stripe.com') + api_stripe_com.log(output) + raise + + # Get some ZKAPs from the issuer. + try: + runOnNode(client, [ + get_passes, + "http://127.0.0.1:3456", + "/tmp/client/private/api_auth_token", + issuerURL, + voucher, + ]) + except: + # Dump the fake Stripe API server logs, too, since the error may arise + # from a PaymentServer/Stripe interaction. + code, output = api_stripe_com.execute('journalctl -u api.stripe.com') + api_stripe_com.log(output) + + raise + + # The client should be prepped now. Make it try to use some storage. + runOnNode(client, [exercise_storage, "/tmp/client"]) + + # It should be possible to restart the storage service without the + # storage node fURL changing. + furlfile = '/var/db/tahoe-lafs/storage/private/storage-plugin.privatestorageio-zkapauthz-v1.furl' + before = storage.execute('cat ' + furlfile) + runOnNode(storage, ["systemctl", "restart", "tahoe.storage"]) + after = storage.execute('cat ' + furlfile) + if (before != after): + raise Exception('fURL changes after storage node restart') + + # The client should actually still work, too. + runOnNode(client, [exercise_storage, "/tmp/client"]) + + # The issuer metrics should be accessible from the monitoring network. + issuer.execute('ifconfig lo:fauxvpn 172.23.23.2/24') + issuer.wait_until_succeeds("nc -z 172.23.23.2 80") + issuer.succeed('curl --silent --insecure --fail --output /dev/null http://172.23.23.2/metrics') + # The issuer metrics should NOT be accessible from any other network. + issuer.fail('curl --silent --insecure --fail --output /dev/null http://localhost/metrics') + client.fail('curl --silent --insecure --fail --output /dev/null http://issuer/metrics') + issuer.execute('ifconfig lo:fauxvpn down') -- GitLab