Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration for lease maintenance behavior #255

Merged
merged 17 commits into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@ When tokens are received from an issuer during redemption,
these are the only public keys which will satisfy the redeemer and cause the tokens to be made available to the client to be spent.
Tokens received with any other public key will be sequestered and will *not* be spent until some further action is taken.

lease.crawl-interval.mean
~~~~~~~~~~~~~~~~~~~~~~~~~

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 integer number of seconds.
For example to run on average every 26 days::

[storageclient.plugins.privatestorageio-zkapauthz-v1]
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 integer number of seconds.
For example to make all intervals fall within a 7 day period::

[storageclient.plugins.privatestorageio-zkapauthz-v1]
lease.crawl-interval.range = 302400


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 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 = 604800

Server
------

Expand Down
22 changes: 7 additions & 15 deletions src/_zkapauthorizer/_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"""

import random
from datetime import datetime, timedelta
from datetime import datetime
from functools import partial
from weakref import WeakValueDictionary

Expand All @@ -33,9 +33,11 @@
from zope.interface import implementer

from .api import ZKAPAuthorizerStorageClient, ZKAPAuthorizerStorageServer
from .config import lease_maintenance_from_tahoe_config
from .controller import get_redeemer
from .lease_maintenance import (
SERVICE_NAME,
LeaseMaintenanceConfig,
lease_maintenance_service,
maintain_leases_from_root,
)
Expand Down Expand Up @@ -220,26 +222,15 @@ def get_now():

store = storage_server._get_store(node_config)

maint_config = lease_maintenance_from_tahoe_config(node_config)

# Create the operation which performs the lease maintenance job when
# called.
maintain_leases = maintain_leases_from_root(
get_root_nodes=partial(get_root_nodes, client_node, node_config),
storage_broker=client_node.get_storage_broker(),
secret_holder=client_node._secret_holder,
# The greater the min lease remaining time, the more of each lease
# period is "wasted" by renewing the lease before it has expired. The
# premise of ZKAPAuthorizer's use of leases is that if they expire,
# the storage server is free to reclaim the storage by forgetting
# about the share. However, since we do not know of any
# ZKAPAuthorizer-enabled storage grids which will garbage collect
# shares when leases expire, we have no reason not to use a zero
# duration here - for now.
#
# In the long run, storage servers must run with garbage collection
# enabled. Ideally, before that happens, we will have a system that
# doesn't involve trading of wasted lease time against reliability of
# leases being renewed before the shares are garbage collected.
min_lease_remaining=timedelta(seconds=0),
min_lease_remaining=maint_config.min_lease_remaining,
progress=store.start_lease_maintenance,
get_now=get_now,
)
Expand All @@ -252,6 +243,7 @@ def get_now():
reactor,
last_run_path,
random,
lease_maint_config=maint_config,
)


Expand Down
98 changes: 98 additions & 0 deletions src/_zkapauthorizer/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright 2019 PrivateStorage.io, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Helpers for reading values from the Tahoe-LAFS configuration.
"""
Comment on lines +15 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we want to have separate modules for client and server config. Other than empty_config (which I guess is a test fixture), all the stuff here is client specific.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be, yea. Since this project started it has become clear that two different plugins may have made more sense than a single plugin with such a tightly coupled implementation. We could probably try to keep the pieces as separate as possible until a more definitive decoupling happens. I'll keep it in mind for future development.


from datetime import timedelta

from .lease_maintenance import LeaseMaintenanceConfig


class _EmptyConfig(object):
"""
Weakly pretend to be a Tahoe-LAFS configuration object with no
configuration.
"""

def get_config(self, section, option, default=object(), boolean=False):
return default


empty_config = _EmptyConfig()


def lease_maintenance_from_tahoe_config(node_config):
# type: (_Config) -> LeaseMaintenanceConfig
"""
Return a ``LeaseMaintenanceConfig`` representing the values from the given
configuration object.
"""
return LeaseMaintenanceConfig(
crawl_interval_mean=_read_duration(
node_config,
u"lease.crawl-interval.mean",
timedelta(days=26),
),
crawl_interval_range=_read_duration(
node_config,
u"lease.crawl-interval.range",
timedelta(days=4),
),
# The greater the min lease remaining time, the more of each lease
# period is "wasted" by renewing the lease before it has expired. The
# premise of ZKAPAuthorizer's use of leases is that if they expire,
# the storage server is free to reclaim the storage by forgetting
# about the share. However, since we do not know of any
# ZKAPAuthorizer-enabled storage grids which will garbage collect
# shares when leases expire, we have no reason not to use a zero
# duration here - for now.
#
# In the long run, storage servers must run with garbage collection
# enabled. Ideally, before that happens, we will have a system that
# doesn't involve trading of wasted lease time against reliability of
# leases being renewed before the shares are garbage collected.
#
# Also, since this is configuration, you can set it to something else
# if you want.
min_lease_remaining=_read_duration(
node_config,
u"lease.min-time-remaining",
timedelta(days=0),
),
)


def _read_duration(cfg, option, default):
"""
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.

:return: ``None`` if the option is missing, otherwise the parsed duration
as a ``timedelta``.
"""
# type: (_Config, str) -> Optional[timedelta]
section_name = u"storageclient.plugins.privatestorageio-zkapauthz-v1"
exarkun marked this conversation as resolved.
Show resolved Hide resolved
value_str = cfg.get_config(
section=section_name,
option=option,
default=None,
)
if value_str is None:
return default
return timedelta(seconds=int(value_str))
79 changes: 68 additions & 11 deletions src/_zkapauthorizer/lease_maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,17 @@ class _FuzzyTimerService(Service):

:ivar IReactorTime reactor: A Twisted reactor to use to schedule runs of
the operation.

:ivar get_config: A function to call to return the service's
configuration. The configuration is represented as a service-specific
object.
"""

name = attr.ib()
operation = attr.ib()
initial_interval = attr.ib()
sample_interval_distribution = attr.ib()
get_config = attr.ib() # type: () -> Any
reactor = attr.ib()

def startService(self):
Expand Down Expand Up @@ -352,8 +357,7 @@ def lease_maintenance_service(
reactor,
last_run_path,
random,
interval_mean=None,
interval_range=None,
lease_maint_config,
):
"""
Get an ``IService`` which will maintain leases on ``root_node`` and any
Expand All @@ -372,19 +376,15 @@ def lease_maintenance_service(
:param random: An object like ``random.Random`` which can be used as a
source of scheduling delay.

:param timedelta interval_mean: The mean time between lease renewal checks.

:param timedelta interval_range: The range of the uniform distribution of
lease renewal checks (centered on ``interval_mean``).
:param lease_maint_config: Configuration for the tweakable lease
maintenance parameters.

:param maintain_leases: A no-argument callable which performs a round of
lease-maintenance. The resulting service calls this periodically.
"""
if interval_mean is None:
interval_mean = timedelta(days=26)
if interval_range is None:
interval_range = timedelta(days=4)
halfrange = interval_range / 2
interval_mean = lease_maint_config.crawl_interval_mean
interval_range = lease_maint_config.crawl_interval_range
halfrange = lease_maint_config.crawl_interval_range / 2

def sample_interval_distribution():
return timedelta(
Expand Down Expand Up @@ -414,6 +414,9 @@ def sample_interval_distribution():
timedelta(0),
)

def get_lease_maint_config():
return lease_maint_config

return _FuzzyTimerService(
SERVICE_NAME,
lambda: bracket(
Expand All @@ -426,10 +429,64 @@ def sample_interval_distribution():
),
initial_interval,
sample_interval_distribution,
get_lease_maint_config,
reactor,
)


@attr.s(frozen=True)
class LeaseMaintenanceConfig(object):
"""
Represent the configuration for a lease maintenance service.

:ivar crawl_interval_mean: The mean time between lease renewal checks.

:ivar crawl_interval_range: The range of the uniform distribution of lease
renewal checks (centered on ``interval_mean``).

:ivar min_lease_remaining: The minimum amount of time remaining to allow
on a lease without renewing it.
"""

crawl_interval_mean = attr.ib() # type: datetime.timedelta
crawl_interval_range = attr.ib() # type: datetime.timedelta
min_lease_remaining = attr.ib() # type: datetime.timedelta


def lease_maintenance_config_to_dict(lease_maint_config):
# type: (LeaseMaintenanceConfig) -> Dict[str, str]
return {
"lease.crawl-interval.mean": _format_duration(
lease_maint_config.crawl_interval_mean,
),
"lease.crawl-interval.range": _format_duration(
lease_maint_config.crawl_interval_range,
),
"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"]),
)


def write_time_to_path(path, when):
"""
Write an ISO8601 datetime string to a file.
Expand Down
Loading