diff --git a/service/test/api_client_test.py b/service/test/api_client_test.py new file mode 100755 index 0000000..2620b5a --- /dev/null +++ b/service/test/api_client_test.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import os, signal, atexit, random, unittest + +from util import eprint, gprint, backup_id_to_str, random_id +from kbupd import Kbupd +from partition import Partition +from peer_ca import PeerCa +from kbupdapiclient import KbupdApiClient +from netem import start_playback + +BACKUP_DATA_LENGTH = 48 + +class NetemTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ca = PeerCa() + + scripts = os.environ.get('SCRIPT', '').split(',') + cls.scripts = scripts if scripts != [''] else [] + if len(cls.scripts) > 0: + cls.netem_threads = start_playback(*cls.scripts) + + @classmethod + def tearDownClass(cls): + if hasattr(cls, 'netem_threads'): + for thread in cls.netem_threads: + thread.exit.set() + thread.join() + cls.netem_threads = None + + super().tearDownClass() + +class KbupdTestCase(NetemTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.partitions = [] + cls.enclave_name = "test" + + partition = Partition(cls.ca) + cls.partitions.append(partition) + gprint("Started service %s" % partition.service_id) + gprint("Started partition %s" % partition.get_spec()) + gprint() + + partition = partition.split_partition() + partition.start_partition() + cls.partitions.append(partition) + gprint("Started service %s" % partition.service_id) + gprint("Started 2nd partition %s" % partition.get_spec()) + gprint() + cls.partitions[0].wait_partition_started_source() + cls.partitions[0].resume_partition() + cls.partitions[0].pause_partition() + cls.partitions[0].resume_partition() + cls.partitions[0].wait_partition_source() + partition.wait_partition_destination() + partition.finish_partition() + cls.partitions[0].finish_partition() + + cls.frontend = Kbupd(1337, "frontend", cls.ca, + "--enclave-name", cls.enclave_name, + "--max-backup-data-length", str(BACKUP_DATA_LENGTH), + "--partitions", ';'.join([ p.get_spec() for p in cls.partitions ])) + gprint("Started frontend %s" % cls.frontend.node_id) + gprint() + + cls.backup_data = random_id(BACKUP_DATA_LENGTH) + cls.backup_pin = random_id(32) + + cls.client = KbupdApiClient(cls.frontend, cls.enclave_name, cls.partitions[0].service_id, "test_%030x" % random.randrange(16**32)) + + @classmethod + def tearDownClass(cls): + for p in cls.partitions: + p.kill() + cls.frontend.kill() + + super().tearDownClass() + + def test_00_valid_requests(self): + client = self.client + + # Verify we're starting without stale data + print(client.request(r"status=Missing", "restore", pin = self.backup_pin, valid_from = 0)) + + client.request(r"status=Ok", "backup", pin = self.backup_pin, valid_from = 0) + client.request(r"status=Ok", "restore", pin = self.backup_pin, valid_from = 0) + client.request(None, "delete") + client.request(r"status=Missing", "restore", pin = self.backup_pin, valid_from = 0) + + client.request(r"status=Ok", "backup", pin = self.backup_pin, valid_from = 0) + client.request(r"status=Ok", "restore", pin = self.backup_pin, valid_from = 0) + client.request(None, "delete_all") + client.request(r"status=Missing", "restore", pin = self.backup_pin, valid_from = 0) + +def kill_all(*args): + Kbupd.kill_all() + raise(Exception("SIGTERM")) + +def cleanup(): + if len(Kbupd.processes) > 0: + Kbupd.kill_all() + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, kill_all) + atexit.register(cleanup) + unittest.installHandler() + unittest.main(failfast=True) diff --git a/service/test/kbupd.py b/service/test/kbupd.py index dde1e45..efd1cc7 100644 --- a/service/test/kbupd.py +++ b/service/test/kbupd.py @@ -12,6 +12,7 @@ class Kbupd(): if os.path.isfile(os.path.join(bd, "kbupd")): self.kbupd_bin = os.path.join(bd, "kbupd") self.kbupctl_bin = os.path.join(bd, "kbupctl") + self.kbupd_api_client_bin = os.path.join(bd, "kbupd_api_client") self.kbuptlsd_bin = os.path.join(bd, "kbuptlsd") break for bd in (os.getenv("CONFIG_DIR"), ".", "config"): @@ -186,6 +187,12 @@ class Kbupd(): stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True) + def kbupd_api_client(self, *args): + return subprocess.run([self.kbupd_api_client_bin, + "--connect", "http://127.0.0.1:%s" % self.api_port, *args], + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + def grep_log(self, regex): with open(self.log_file, 'r') as logfd: return [ line[:-1] for line in logfd if re.search(regex, line) ] diff --git a/service/test/kbupdapiclient.py b/service/test/kbupdapiclient.py new file mode 100644 index 0000000..04f958f --- /dev/null +++ b/service/test/kbupdapiclient.py @@ -0,0 +1,57 @@ +import atexit, re, time +from datetime import datetime + +from util import eprint + +def timestamp(): + return datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + +class KbupdApiClient(): + log = None + + def __init__(self, frontend, enclave_name, service_id, + username, shared_auth_secret = "0000000000000000000000000000000000000000000000000000000000000000"): + self.frontend = frontend + self.enclave_name = enclave_name + self.service_id = service_id + self.username = username + self.shared_auth_secret = shared_auth_secret + + if not self.log: + KbupdApiClient.log = open("api_client_test.log", 'w') + atexit.register(KbupdApiClient.log.close) + + def request(self, regex, subcommand, pin = None, service_id = None, valid_from = None): + if subcommand in ("backup", "restore", "delete"): + if service_id is None: + service_id = self.service_id + + cmd = ["--username", self.username, "--token-secret", self.shared_auth_secret, subcommand] + + if subcommand != "delete_all": + cmd.extend(["--enclave-name", self.enclave_name]) + + if pin is not None: + cmd.extend(["--backup-pin", str(pin)]) + if service_id is not None: + cmd.extend(["--service-id", str(service_id)]) + if valid_from is not None: + cmd.extend(["--valid-from", str(valid_from)]) + + self.log.write(timestamp() + " CMD: " + ' '.join(cmd) + '\n') + + kbupd_api_client = self.frontend.kbupd_api_client(*cmd) + output = kbupd_api_client.stdout.decode() + stderr = kbupd_api_client.stderr.decode() + + self.log.write(timestamp() + " COMPLETED\n") + self.log.write(stderr) + self.log.flush() + + if regex is not None and re.search(regex, output) == None and re.search(regex, stderr) == None: + eprint() + eprint("TEST '%s' FAILED, expecting %s, got:" % (" ".join(cmd), regex)) + eprint(output) + raise Exception("Test failed") + + return dict(re.findall(r'(\S+)=(\S*)', output))