diff --git a/nixos/modules/tests/exercise-storage.py b/nixos/modules/tests/exercise-storage.py new file mode 100755 index 0000000000000000000000000000000000000000..1d7197e6251a7570aafd7ac61e0564a22fbf67c9 --- /dev/null +++ b/nixos/modules/tests/exercise-storage.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python2 + +from sys import argv +from os import urandom + +def main(): + (clientDir,) = argv[1:] + + someData = urandom(2 ** 16) + with mkstemp() as (fd, name): + write(fd, someData) + + cap = get([ + "tahoe", "-d", clientDir, + "put", name, + ]) + + dataReadBack = get([ + "tahoe", "-d", clientDir, + "get", cap, + ]) + + assert someData == dataReadBack + + +def get(argv): + return check_output(argv) + + +if __name__ == '__main__': + main() diff --git a/nixos/modules/tests/get-passes.py b/nixos/modules/tests/get-passes.py new file mode 100755 index 0000000000000000000000000000000000000000..f387f9bef5ab8bad50d9b7579930dfb8f48e0d44 --- /dev/null +++ b/nixos/modules/tests/get-passes.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 + +from sys import argv +from requests import post +from json import dumps + +def main(): + clientAPIRoot, issuerAPIRoot = argv[1:] + + voucher = "0123456789" + zkapauthz = clientAPIRoot + "/storage-plugins/privatestorage-zkapauthorizer-v1" + + # Simulate a payment for a voucher. + post( + issuerAPIRoot + "/v1/stripe/webhook", + dumps(charge_succeeded_json(voucher)), + ) + + # Tell the client to redeem the voucher. + post( + zkapauthz + "/voucher", + dumps({"voucher": voucher}), + ) + + # Poll the vouchers list for a while to see it get redeemed. + expected = {"number": voucher, "redeemed": True} + retry( + "find redeemed voucher", + lambda: expected == get(zkapauthz + "/voucher/" + voucher), + ) + + +def retry(description, f): + for i in range(60): + print("trying to {}...".format(description)) + if f(): + print("{} succeeded".format(description)) + break + sleep(1.0) + raise ValueError("failed to {} after many tries".format(description)) + + +def charge_succeeded_json(voucher): + # This structure copy/pasted from Stripe webhook web interface. + base_payload = { + "id": "evt_1FKSX2DeTd13VRuuhPaUDA2f", + "object": "event", + "api_version": "2016-07-06", + "created": 1568910660, + "data": { + "object": { + "id": "ch_1FKSX2DeTd13VRuuG9BXbqji", + "object": "charge", + "amount": 999, + "amount_refunded": 0, + "application": None, + "application_fee": None, + "application_fee_amount": None, + "balance_transaction": "txn_1FKSX2DeTd13VRuuqO1CJZ1e", + "billing_details": { + "address": { + "city": None, + "country": None, + "line1": None, + "line2": None, + "postal_code": None, + "state": None + }, + "email": None, + "name": None, + "phone": None + }, + "captured": True, + "created": 1568910660, + "currency": "usd", + "customer": None, + "description": None, + "destination": None, + "dispute": None, + "failure_code": None, + "failure_message": None, + "fraud_details": { + }, + "invoice": None, + "livemode": False, + "metadata": { + "Voucher": None, + }, + "on_behalf_of": None, + "order": None, + "outcome": { + "network_status": "approved_by_network", + "reason": None, + "risk_level": "normal", + "risk_score": 44, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": True, + "payment_intent": None, + "payment_method": "card_1FKSX2DeTd13VRuus5VEjmjG", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": None, + "address_postal_code_check": None, + "cvc_check": None + }, + "country": "US", + "exp_month": 9, + "exp_year": 2020, + "fingerprint": "HTJeRR4MXhAAkctF", + "funding": "credit", + "last4": "4242", + "three_d_secure": None, + "wallet": None + }, + "type": "card" + }, + "receipt_email": None, + "receipt_number": None, + "receipt_url": "https://pay.stripe.com/receipts/acct_198xN4DeTd13VRuu/ch_1FKSX2DeTd13VRuuG9BXbqji/rcpt_Fq8oAItSiNmcm0beiie6lUYin920E7a", + "refunded": False, + "refunds": { + "object": "list", + "data": [ + ], + "has_more": False, + "total_count": 0, + "url": "/v1/charges/ch_1FKSX2DeTd13VRuuG9BXbqji/refunds" + }, + "review": None, + "shipping": None, + "source": { + "id": "card_1FKSX2DeTd13VRuus5VEjmjG", + "object": "card", + "address_city": None, + "address_country": None, + "address_line1": None, + "address_line1_check": None, + "address_line2": None, + "address_state": None, + "address_zip": None, + "address_zip_check": None, + "brand": "Visa", + "country": "US", + "customer": None, + "cvc_check": None, + "dynamic_last4": None, + "exp_month": 9, + "exp_year": 2020, + "fingerprint": "HTJeRR4MXhAAkctF", + "funding": "credit", + "last4": "4242", + "metadata": { + }, + "name": None, + "tokenization_method": None + }, + "source_transfer": None, + "statement_descriptor": None, + "statement_descriptor_suffix": None, + "status": "succeeded", + "transfer_data": None, + "transfer_group": None + } + }, + "livemode": False, + "pending_webhooks": 2, + "request": "req_5ozjOIAcOkvVUK", + "type": "charge.succeeded" + } + # Indicate the voucher the payment references. + base_payload["data"]["object"]["metadata"]["Voucher"] = voucher + return base_payload + + +if __name__ == '__main__': + main() diff --git a/nixos/modules/tests/private-storage.nix b/nixos/modules/tests/private-storage.nix index b2f2f1f0af63d47257b701bcca655b1d2d4d2289..8e319ced066d311151cc9a6a035597c544caef5a 100644 --- a/nixos/modules/tests/private-storage.nix +++ b/nixos/modules/tests/private-storage.nix @@ -1,9 +1,18 @@ let pkgs = import <nixpkgs> { }; + + # Separate helper programs so we can write as little perl 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; + # Here are the preconstructed secrets which we can assign to the introducer. # This is a lot easier than having the introducer generate them and then # discovering and configuring the other nodes with them. pemFile = ./node.pem; + tubID = "rr7y46ixsg6qmck4jkkc7hke6xe4sv5f"; swissnum = "2k6p3wrabat5jrj7otcih4cjdema4q3m"; introducerPort = 35151; @@ -29,8 +38,10 @@ import <nixpkgs/nixos/tests/make-test.nix> { client = { config, pkgs, ... }: { environment.systemPackages = [ + pkgs.python2 pkgs.tahoe-lafs pkgs.daemonize + (pkgs.python3.withPackages (ps: [ ps.requests ])) ]; } // networkConfig; @@ -62,35 +73,14 @@ import <nixpkgs/nixos/tests/make-test.nix> { testScript = '' # Start booting all the VMs in parallel to speed up operations down below. - startAll; + # startAll; - # # Set up a Tahoe-LAFS introducer. - # - my ($code, $version) = $introducer->execute("tahoe --version"); - $introducer->log($version); - - $introducer->succeed( - 'tahoe create-introducer ' . - '--port tcp:${toString introducerPort} ' . - '--location tcp:introducer:${toString introducerPort} ' . - '/tmp/introducer' - ); $introducer->copyFileFromHost( '${pemFile}', - '/tmp/introducer/private/node.pem' - ); - $introducer->copyFileFromHost( - '${introducerFURLFile}', - '/tmp/introducer/private/introducer.furl' + '/tmp/node.pem' ); - $introducer->succeed( - 'daemonize ' . - '-e /tmp/stderr ' . - '-o /tmp/stdout ' . - '$(type -p tahoe) run /tmp/introducer' - ); - + $introducer->succeed('set -eo pipefail; ${run-introducer} /tmp/node.pem ${toString introducerPort} ${introducerFURL} | systemd-cat'); eval { $introducer->waitForOpenPort(${toString introducerPort}); # Signal success. :/ @@ -105,7 +95,7 @@ import <nixpkgs/nixos/tests/make-test.nix> { # # Get a Tahoe-LAFS storage server up. # - my ($code, $version) = $storage->execute("tahoe --version"); + my ($code, $version) = $storage->execute('tahoe --version'); $storage->log($version); # The systemd unit should reach the running state. @@ -123,51 +113,13 @@ import <nixpkgs/nixos/tests/make-test.nix> { # # Storage appears to be working so try to get a client to speak with it. # - my ($code, $version) = $client->execute("tahoe --version"); - $client->log($version); - - # Create a Tahoe-LAFS client on it. - $client->succeed( - 'tahoe create-client ' . - '--shares-needed 1 ' . - '--shares-happy 1 ' . - '--shares-total 1 ' . - '--introducer ${introducerFURL} /tmp/client' - ); - - # Launch it - $client->succeed( - 'daemonize ' . - '-e /tmp/stderr ' . - '-o /tmp/stdout ' . - '$(type -p tahoe) run /tmp/client' - ); + $client->succeed('${run-client} ${introducerFURL}'); $client->waitForOpenPort(3456); - # # Get some ZKAPs from the issuer. - # - - # Simulate a payment for a voucher. - $voucher = "0123456789"; - $client->succeed("${simulate-payment} $voucher"); - - # Tell the client to redeem the voucher. - $client->succeed("${redeem-voucher} $voucher"); + $client->succeed('${get-passes} http://127.0.0.1:3456 http://issuer'); # The client should be prepped now. Make it try to use some storage. - my ($code, $out) = $client->execute( - 'tahoe -d /tmp/client ' . - 'put /etc/issue' - ); - ($code == 0) or do { - my ($code, $log) = $client->execute('cat /tmp/stdout /tmp/stderr'); - $client->log($log); - die "put failed"; - }; - $client->succeed( - 'tahoe -d /tmp/client ' . - "get $out" - ); + $client->succeed('${exercise-storage}'); ''; } diff --git a/nixos/modules/tests/run-client.py b/nixos/modules/tests/run-client.py new file mode 100755 index 0000000000000000000000000000000000000000..cdf8ffeb62393c89fc86107bd7e1ffdc8fddfbb5 --- /dev/null +++ b/nixos/modules/tests/run-client.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +from os import environ +from sys import argv +from shutil import which +from subprocess import check_output + +def main(): + (introducerFURL,) = argv[1:] + + # PYTHONHOME set for Python 3 for this script breaks Python 2 used by + # Tahoe. :/ This is kind of a NixOS Python packaging bug. + del environ["PYTHONHOME"] + + run(["tahoe", "--version"]) + run([ + "tahoe", "create-client", + "--shares-needed", "1", + "--shares-happy", "1", + "--shares-total", "1", + "--introducer", introducerFURL, + "/tmp/client", + ]) + + run([ + "daemonize", + "-o", "/tmp/stdout", + "-e", "/tmp/stderr", + which("tahoe"), "run", "/tmp/client", + ]) + +def run(argv): + print("{}: {}".format(argv, check_output(argv))) + + +if __name__ == '__main__': + main() diff --git a/nixos/modules/tests/run-introducer.py b/nixos/modules/tests/run-introducer.py new file mode 100755 index 0000000000000000000000000000000000000000..40885590500b1c47bfac78c3526dc6c4eac4aeaf --- /dev/null +++ b/nixos/modules/tests/run-introducer.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +from sys import argv +from os import environ, makedirs, rename +from shutil import which +from subprocess import check_output +from socket import socket +from time import sleep + +log = print + +def main(): + pemFile, introducerPort, introducerFURL = argv[1:] + + # PYTHONHOME set for Python 3 for this script breaks Python 2 used by + # Tahoe. :/ This is kind of a NixOS Python packaging bug. + del environ["PYTHONHOME"] + + run(["tahoe", "--version"]) + run([ + "tahoe", "create-introducer", + "--port", "tcp:" + introducerPort, + "--location", "tcp:introducer:" + introducerPort, + "/tmp/introducer", + ]) + rename(pemFile, "/tmp/introducer/private/node.pem") + with open("/tmp/introducer/private/introducer.furl", "w") as f: + f.write(introducerFURL) + run([ + "daemonize", + "-o", "/tmp/stdout", + "-e", "/tmp/stderr", + which("tahoe"), "run", "/tmp/introducer", + ]) + + retry( + "waiting for open introducer port", + lambda: checkOpen(35151), + ) + + +def checkOpen(portNumber): + s = socket() + try: + s.connect(("127.0.0.1", portNumber)) + except: + return False + else: + return True + finally: + s.close() + + +def retry(description, f): + for i in range(60): + log("trying to {}...".format(description)) + if f(): + log("{} succeeded".format(description)) + return + sleep(1.0) + raise ValueError("failed to {} after many tries".format(description)) + + +def run(argv): + log("Running {}".format(argv)) + log("{}: {}".format(argv, check_output(argv))) + + +if __name__ == '__main__': + main()