aboutsummaryrefslogtreecommitdiff
path: root/myrpki/yamltest.py
diff options
context:
space:
mode:
Diffstat (limited to 'myrpki/yamltest.py')
-rw-r--r--myrpki/yamltest.py123
1 files changed, 121 insertions, 2 deletions
diff --git a/myrpki/yamltest.py b/myrpki/yamltest.py
index de7ecc0b..5e03ecf7 100644
--- a/myrpki/yamltest.py
+++ b/myrpki/yamltest.py
@@ -1,6 +1,22 @@
"""
-Convert testbed.py YAML configuration format to myrpki .conf and .csv
-format. Much of the YAML handling code lifted from testbed.py.
+Test framework, using the same YAML test description format as
+testbed.py, but using the myrpki.py and myirbe.py tools to do all the
+back-end work. Reads YAML file, generates .csv and .conf files, runs
+daemons and waits for one of them to exit.
+
+Much of the YAML handling code lifted from testbed.py.
+
+Still to do:
+
+- Generate rsyncd.conf and run rsync so that tests can include rcynic
+ runs aganist generated data. Not particularly difficult, just
+ tedious, and likely to require fildding with publication paths
+ (again).
+
+- Implement testebd.py-style delta actions, that is, modify the
+ allocation database under control of the YAML file, dump out new
+ .csv files, and run myrpki.py and myirbe.py again to feed resulting
+ changes into running daemons.
$Id$
@@ -41,6 +57,9 @@ section_regexp = re.compile("\s*\[\s*(.+?)\s*\]\s*$")
variable_regexp = re.compile("\s*([-a-zA-Z0-9_]+)\s*=\s*(.+?)\s*$")
def cleanpath(*names):
+ """
+ Construct normalized pathnames.
+ """
return os.path.normpath(os.path.join(*names))
this_dir = os.getcwd()
@@ -57,6 +76,9 @@ prog_rootd = cleanpath(rpkid_dir, "rootd.py")
prog_openssl = cleanpath(this_dir, "../openssl/openssl/apps/openssl")
class roa_request(object):
+ """
+ Representation of a ROA request.
+ """
def __init__(self, asn, ipv4, ipv6):
self.asn = asn
@@ -79,9 +101,15 @@ class roa_request(object):
@classmethod
def parse(cls, yaml):
+ """
+ Parse a ROA request from YAML format.
+ """
return cls(yaml.get("asn"), yaml.get("ipv4"), yaml.get("ipv6"))
class allocation_db(list):
+ """
+ Our allocation database.
+ """
def __init__(self, yaml):
list.__init__(self)
@@ -109,10 +137,19 @@ class allocation_db(list):
assert not a.is_root() and not a.hosted_by.is_hosted()
def dump(self):
+ """
+ Show contents of allocatino database.
+ """
for a in self:
a.dump()
def make_rootd_openssl(self):
+ """
+ Factory for a function to run the OpenSSL comand line tool on the
+ root node of our allocation database. Could easily be generalized
+ if there were a need, but as it happens we only ever need to do
+ this for the root node.
+ """
env = { "PATH" : os.environ["PATH"],
"BPKI_DIRECTORY" : self.root.path("bpki.rootd"),
"RANDFILE" : ".OpenSSL.whines.unless.I.set.this" }
@@ -120,6 +157,12 @@ class allocation_db(list):
return lambda *args: subprocess.check_call((prog_openssl,) + args, cwd = cwd, env = env)
class allocation(object):
+ """
+ One entity in our allocation database. Every entity in the database
+ is assumed to hold resources, so needs at least myrpki services.
+ Entities that don't have the hosted_by property run their own copies
+ of rpkid, irdbd, and pubd, so they also need myirbe services.
+ """
parent = None
crl_interval = None
@@ -129,6 +172,9 @@ class allocation(object):
@classmethod
def allocate_port(cls):
+ """
+ Allocate a TCP port.
+ """
cls.base_port += 1
return cls.base_port
@@ -136,6 +182,10 @@ class allocation(object):
@classmethod
def allocate_engine(cls):
+ """
+ Allocate an engine number, mostly used to construct MySQL database
+ names.
+ """
cls.base_engine += 1
return cls.base_engine
@@ -177,6 +227,10 @@ class allocation(object):
self.rootd_port = self.allocate_port()
def closure(self):
+ """
+ Compute resource closure of this node and its children, to avoid a
+ lot of tedious (and error-prone) duplication in the YAML file.
+ """
resources = self.base
for kid in self.kids:
resources = resources.union(kid.closure())
@@ -184,6 +238,9 @@ class allocation(object):
return resources
def dump(self):
+ """
+ Show content of this allocation node.
+ """
print str(self)
def __str__(self):
@@ -205,24 +262,42 @@ class allocation(object):
return s + " Until: %s\n" % self.resources.valid_until
def is_root(self):
+ """
+ Is this the root node?
+ """
return self.parent is None
def is_hosted(self):
+ """
+ Is this entity hosted?
+ """
return self.hosted_by is not None
def path(self, *names):
+ """
+ Construct pathnames in this entity's test directory.
+ """
return cleanpath(test_dir, self.name, *names)
def outfile(self, filename):
+ """
+ Open and log an output file.
+ """
path = self.path(filename)
print "Writing", path
return open(path, "w")
def up_down_url(self):
+ """
+ Construct service URL for this node's parent.
+ """
parent_port = self.parent.hosted_by.rpkid_port if self.parent.is_hosted() else self.parent.rpkid_port
return "https://localhost:%d/up-down/%s/%s" % (parent_port, self.parent.name, self.name)
def dump_asns(self, fn):
+ """
+ Write Autonomous System Numbers CSV file.
+ """
f = self.outfile(fn)
for k in self.kids:
for a in k.resources.asn:
@@ -230,12 +305,18 @@ class allocation(object):
f.close()
def dump_children(self, fn):
+ """
+ Write children CSV file.
+ """
f = self.outfile(fn)
for k in self.kids:
f.write("%s\t%s\t%s\n" % (k.name, k.resources.valid_until, k.path("bpki.myrpki/ca.cer")))
f.close()
def dump_parents(self, fn):
+ """
+ Write parents CSV file.
+ """
f = self.outfile(fn)
if self.is_root():
f.write("%s\t%s\t%s\t%s\n" % ("rootd", "https://localhost:%d/" % self.rootd_port, self.path("bpki.rootd/ca.cer"), self.path("bpki.rootd/ca.cer")))
@@ -245,6 +326,9 @@ class allocation(object):
f.close()
def dump_prefixes(self, fn):
+ """
+ Write prefixes CSV file.
+ """
f = self.outfile(fn)
for k in self.kids:
for p in k.resources.v4 + k.resources.v6:
@@ -252,6 +336,9 @@ class allocation(object):
f.close()
def dump_roas(self, fn):
+ """
+ Write ROA CSV file.
+ """
f = self.outfile(fn)
for r in self.roa_requests:
for p in r.v4 + r.v6 if r.v4 and r.v6 else r.v4 or r.v6 or ():
@@ -259,6 +346,9 @@ class allocation(object):
f.close()
def dump_conf(self, fn):
+ """
+ Write configuration file for OpenSSL and RPKI tools.
+ """
host = self.hosted_by if self.is_hosted() else self
@@ -315,6 +405,9 @@ class allocation(object):
f.close()
def run_myirbe(self):
+ """
+ Run myirbe.py if this entity is not hosted by another engine.
+ """
if not self.is_hosted():
print "Running myirbe.py for", self.name
cmd = ["python", prog_myirbe]
@@ -322,10 +415,17 @@ class allocation(object):
subprocess.check_call(cmd, cwd = self.path())
def run_myrpki(self):
+ """
+ Run myrpki.py for this entity.
+ """
print "Running myrpki.py for", self.name
subprocess.check_call(("python", prog_myrpki), cwd = self.path())
def run_python_daemon(self, prog):
+ """
+ Start a Python daemon and return a subprocess.Popen object
+ representing the running daemon.
+ """
basename = os.path.basename(prog)
p = subprocess.Popen(("python", prog, "-c", self.path("myrpki.conf")),
cwd = self.path(),
@@ -335,15 +435,27 @@ class allocation(object):
return p
def run_rpkid(self):
+ """
+ Run rpkid.
+ """
return self.run_python_daemon(prog_rpkid)
def run_irdbd(self):
+ """
+ Run irdbd.
+ """
return self.run_python_daemon(prog_irdbd)
def run_pubd(self):
+ """
+ Run pubd.
+ """
return self.run_python_daemon(prog_pubd)
def run_rootd(self):
+ """
+ Run rootd.
+ """
return self.run_python_daemon(prog_rootd)
os.environ["TZ"] = "UTC"
@@ -359,11 +471,18 @@ for o, a in opts:
if o in ("-c", "--config"):
cfg_file = a
+# We can't usefully process more than one YAMl file at a time, so
+# whine if there's more than one argument left.
+
if len(argv) > 1:
raise RuntimeError, "Unexpected arguments %r" % argv
yaml_file = argv[0] if argv else "../rpkid/testbed.1.yaml"
+# Allow optional config file for this tool to override default
+# passwords: this is mostly so that I can show a complete working
+# example without publishing my own server's passwords.
+
try:
cfg = rpki.config.parser(cfg_file, "yamltest")
rpkid_password = cfg.get("rpkid_db_pass")