diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index c8ee9353000cc029b357b2e45a794c19489209b9..e6dfe4352411f23ffe3da948cc2c605ea1f67980 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -73,22 +73,24 @@ This item controls the frequency at which the lease maintenance crawler runs. The lease maintenance crawler visits all shares and renews their leases if necessary. The crawler will run at random intervals. The client will try to make the average (mean) interval between runs equal to this setting. -The value is an ISO8601 duration string. +The value is an integer number of seconds. For example to run on average every 26 days:: [storageclient.plugins.privatestorageio-zkapauthz-v1] - lease.crawl-interval.mean = P26D + lease.crawl-interval.mean = 2246400 + lease.crawl-interval.range ~~~~~~~~~~~~~~~~~~~~~~~~~~ This item also controls the frequency of lease maintenance crawler runs. The random intervals between runs have a uniform distribution with this item's value as its range. -The value is an ISO8601 duration string. +The value is an integer number of seconds. For example to make all intervals fall within a 7 day period:: [storageclient.plugins.privatestorageio-zkapauthz-v1] - lease.crawl-interval.mean = P3DT12H + lease.crawl-interval.mean = 302400 + lease.min-time-remaining ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -96,11 +98,11 @@ lease.min-time-remaining This item controls the lease renewal behavior of the lease maintenance crawler. It specifies an amount of time left on a lease. If the crawler encounters a lease with less time left than this then it will renew the lease. -The value is an ISO8601 duration string. +The value is an integer number of seconds. For example to renew leases on all shares which will expire in less than one week:: [storageclient.plugins.privatestorageio-zkapauthz-v1] - lease.min-time-remaining = P7D + lease.min-time-remaining = 604800 Server ------ diff --git a/setup.cfg b/setup.cfg index 272d0352b9788bdb6a26a85634423b34be3a9aaa..ebbd1ac295db4e0950f28815949e5900618e59b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,7 @@ install_requires = attrs zope.interface eliot - isodate + aniso8601 python-challenge-bypass-ristretto # The pip resolver sometimes finds treq's dependencies first and these are # incompatible with Tahoe-LAFS'. So duplicate them here (the ones that diff --git a/src/_zkapauthorizer/config.py b/src/_zkapauthorizer/config.py index c7ac61b220a100f7bb2160b7e6d3e2c808fd2666..ee98742bc6b4bca541dabd62f71114cad5887654 100644 --- a/src/_zkapauthorizer/config.py +++ b/src/_zkapauthorizer/config.py @@ -18,8 +18,6 @@ Helpers for reading values from the Tahoe-LAFS configuration. from datetime import timedelta -from isodate import parse_duration - from .lease_maintenance import LeaseMaintenanceConfig @@ -79,8 +77,8 @@ def lease_maintenance_from_tahoe_config(node_config): def _read_duration(cfg, option, default): """ - Read an ISO8601 "duration" from the ZKAPAuthorizer section of a Tahoe-LAFS - config. + Read an integer number of seconds from the ZKAPAuthorizer section of a + Tahoe-LAFS config. :param cfg: The Tahoe-LAFS config object to consult. :param option: The name of the option to read. @@ -97,4 +95,4 @@ def _read_duration(cfg, option, default): ) if value_str is None: return default - return parse_duration(value_str) + return timedelta(seconds=int(value_str)) diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py index 37bd85ddecb69bf063b31867701638352fa9ee70..ad901881175186407b9d442318d6be2e051b2f6b 100644 --- a/src/_zkapauthorizer/lease_maintenance.py +++ b/src/_zkapauthorizer/lease_maintenance.py @@ -29,7 +29,7 @@ from allmydata.util.hashutil import ( file_cancel_secret_hash, file_renewal_secret_hash, ) -from isodate import duration_isoformat, parse_datetime, parse_duration +from aniso8601 import parse_datetime from twisted.application.service import Service from twisted.internet.defer import inlineCallbacks, maybeDeferred from twisted.python.log import err @@ -456,24 +456,34 @@ class LeaseMaintenanceConfig(object): def lease_maintenance_config_to_dict(lease_maint_config): # type: (LeaseMaintenanceConfig) -> Dict[str, str] return { - "lease.crawl-interval.mean": duration_isoformat( + "lease.crawl-interval.mean": _format_duration( lease_maint_config.crawl_interval_mean, ), - "lease.crawl-interval.range": duration_isoformat( + "lease.crawl-interval.range": _format_duration( lease_maint_config.crawl_interval_range, ), - "lease.min-time-remaining": duration_isoformat( + "lease.min-time-remaining": _format_duration( lease_maint_config.min_lease_remaining, ), } +def _format_duration(td): + # type: (timedelta) -> str + return str(int(td.total_seconds())) + + +def _parse_duration(duration_str): + # type: (str) -> timedelta + return timedelta(seconds=int(duration_str)) + + def lease_maintenance_config_from_dict(d): # type: (Dict[str, str]) -> LeaseMaintenanceConfig return LeaseMaintenanceConfig( - crawl_interval_mean=parse_duration(d["lease.crawl-interval.mean"]), - crawl_interval_range=parse_duration(d["lease.crawl-interval.range"]), - min_lease_remaining=parse_duration(d["lease.min-time-remaining"]), + crawl_interval_mean=_parse_duration(d["lease.crawl-interval.mean"]), + crawl_interval_range=_parse_duration(d["lease.crawl-interval.range"]), + min_lease_remaining=_parse_duration(d["lease.min-time-remaining"]), ) diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 7361b9154975a0de8d2e0da1c9a4eede823d117e..27e2fe947ebbf01d329022d72353c6b072aedca8 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -24,7 +24,7 @@ from sqlite3 import OperationalError from sqlite3 import connect as _connect import attr -from isodate import parse_datetime as _parse_datetime +from aniso8601 import parse_datetime as _parse_datetime from twisted.logger import Logger from twisted.python.filepath import FilePath from zope.interface import Interface, implementer @@ -39,13 +39,16 @@ from .storage_common import ( from .validators import greater_than, has_length, is_base64_encoded -def parse_datetime(s): - # type: (str) -> datetime +def parse_datetime(s, **kw): """ - Parse an ISO8601 datetime, even if it uses a space separator instead of a - T separator. + Like ``aniso8601.parse_datetime`` but accept unicode as well. """ - return _parse_datetime(s.replace(u" ", u"T")) + if isinstance(s, unicode): + s = s.encode("utf-8") + assert isinstance(s, bytes) + if "delimiter" in kw and isinstance(kw["delimiter"], unicode): + kw["delimiter"] = kw["delimiter"].encode("utf-8") + return _parse_datetime(s, **kw) class ILeaseMaintenanceObserver(Interface): @@ -701,9 +704,9 @@ class VoucherStore(object): return None [(started, count, finished)] = activity return LeaseMaintenanceActivity( - parse_datetime(started), + parse_datetime(started, delimiter=u" "), count, - parse_datetime(finished), + parse_datetime(finished, delimiter=u" "), ) @@ -1070,11 +1073,11 @@ class Voucher(object): return Pending(counter=row[3]) if state == u"double-spend": return DoubleSpend( - parse_datetime(row[0]), + parse_datetime(row[0], delimiter=u" "), ) if state == u"redeemed": return Redeemed( - parse_datetime(row[0]), + parse_datetime(row[0], delimiter=u" "), row[1], ) raise ValueError("Unknown voucher state {}".format(state)) @@ -1089,7 +1092,7 @@ class Voucher(object): # value represents a leap second. However, since we also use # Python to generate the data in the first place, it should never # represent a leap second... I hope. - created=parse_datetime(created), + created=parse_datetime(created, delimiter=u" "), state=state_from_row(state, row[4:]), ) diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 4a61f8c9f5ba5f79d7e6994bb205b5a30952238d..df0893bb4a45c67d0543adf65494f61113d331eb 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -355,7 +355,7 @@ def interval_means(): Build timedeltas representing the mean time between lease maintenance runs. """ - return floats( + return integers( # It doesn't make sense to have a negative check interval mean. min_value=0, # We can't make this value too large or it isn't convertable to a @@ -363,10 +363,6 @@ def interval_means(): # questionable value. max_value=60 * 60 * 24 * 365, ).map( - # By representing the result as a timedelta we avoid the cases where - # the lower precision of timedelta compared to float drops the whole - # value (anything between 0 and 1 microsecond). This is just one - # example of how working with timedeltas is nicer, in general. lambda s: timedelta(seconds=s), ) @@ -378,8 +374,8 @@ def lease_maintenance_configurations(): return builds( LeaseMaintenanceConfig, interval_means(), - timedeltas(min_value=timedelta(0)), - timedeltas(min_value=timedelta(0)), + integers(min_value=0, max_value=60 * 60 * 24 * 365).map(timedelta), + integers(min_value=0, max_value=60 * 60 * 24 * 365).map(timedelta), ) diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index 4b1663d1d74c1925e657be540223a1ed2f07850b..954ad66d0dcf99d73bff11e75d443b2d1345dfad 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -26,6 +26,7 @@ from urllib import quote import attr from allmydata.client import config_from_string +from aniso8601 import parse_datetime from fixtures import TempDir from hypothesis import given, note from hypothesis.strategies import ( @@ -43,7 +44,6 @@ from hypothesis.strategies import ( text, tuples, ) -from isodate import parse_datetime from testtools import TestCase from testtools.content import text_content from testtools.matchers import ( diff --git a/src/_zkapauthorizer/tests/test_lease_maintenance.py b/src/_zkapauthorizer/tests/test_lease_maintenance.py index 57a86d9497f9e1af65a9a42660dd8de0a47626d3..a5af5299757b943b8fe46161957d59b0972b51c9 100644 --- a/src/_zkapauthorizer/tests/test_lease_maintenance.py +++ b/src/_zkapauthorizer/tests/test_lease_maintenance.py @@ -222,7 +222,7 @@ class LeaseMaintenanceConfigTests(TestCase): """ dumped = lease_maintenance_config_to_dict(config) loaded = lease_maintenance_config_from_dict(dumped) - self.assertThat(config, Equals(loaded)) + self.assertThat(loaded, Equals(config)) class LeaseMaintenanceServiceTests(TestCase):