Ejemplo n.º 1
0
    def __init__(
        self,
        permute_peers,
        tub_maker,
        node_config,
        storage_client_config=None,
    ):
        service.MultiService.__init__(self)
        assert permute_peers  # False not implemented yet
        self.permute_peers = permute_peers
        self._tub_maker = tub_maker

        self.node_config = node_config

        if storage_client_config is None:
            storage_client_config = StorageClientConfig()
        self.storage_client_config = storage_client_config

        # self.servers maps serverid -> IServer, and keeps track of all the
        # storage servers that we've heard about. Each descriptor manages its
        # own Reconnector, and will give us a RemoteReference when we ask
        # them for it.
        self.servers = BytesKeyDict()
        self._static_server_ids = set()  # ignore announcements for these
        self.introducer_client = None
        self._threshold_listeners = []  # tuples of (threshold, Deferred)
        self._connected_high_water_mark = 0
Ejemplo n.º 2
0
def create_account_maps(accounts):
    """
    Build mappings from account names to keys and rootcaps.

    :param accounts: An iterator if (name, key, rootcap) tuples.

    :return: A tuple of two dicts.  The first maps account names to rootcaps.
        The second maps account names to public keys.
    """
    rootcaps = BytesKeyDict()
    pubkeys = BytesKeyDict()
    for (name, key, rootcap) in accounts:
        name_bytes = name.encode("utf-8")
        rootcaps[name_bytes] = rootcap.encode("utf-8")
        pubkeys[name_bytes] = [key]
    return rootcaps, pubkeys
Ejemplo n.º 3
0
 def get_available_space(self):
     version = self.get_version()
     if version is None:
         return None
     protocol_v1_version = version.get(
         b'http://allmydata.org/tahoe/protocols/storage/v1', BytesKeyDict())
     available_space = protocol_v1_version.get(b'available-space')
     if available_space is None:
         available_space = protocol_v1_version.get(
             b'maximum-immutable-share-size', None)
     return available_space
Ejemplo n.º 4
0
 def __init__(self, client, accountfile):
     self.client = client
     self.passwords = BytesKeyDict()
     pubkeys = BytesKeyDict()
     self.rootcaps = BytesKeyDict()
     with open(abspath_expanduser_unicode(accountfile), "rb") as f:
         for line in f:
             line = line.strip()
             if line.startswith(b"#") or not line:
                 continue
             name, passwd, rest = line.split(None, 2)
             if passwd.startswith(b"ssh-"):
                 bits = rest.split()
                 keystring = b" ".join([passwd] + bits[:-1])
                 key = keys.Key.fromString(keystring)
                 rootcap = bits[-1]
                 pubkeys[name] = [key]
             else:
                 self.passwords[name] = passwd
                 rootcap = rest
             self.rootcaps[name] = rootcap
     self._pubkeychecker = SSHPublicKeyChecker(InMemorySSHKeyDB(pubkeys))
Ejemplo n.º 5
0
class StorageFarmBroker(service.MultiService):
    """I live on the client, and know about storage servers. For each server
    that is participating in a grid, I either maintain a connection to it or
    remember enough information to establish a connection to it on demand.
    I'm also responsible for subscribing to the IntroducerClient to find out
    about new servers as they are announced by the Introducer.

    :ivar _tub_maker: A one-argument callable which accepts a dictionary of
        "handler overrides" and returns a ``foolscap.api.Tub``.

    :ivar StorageClientConfig storage_client_config: Values from the node
        configuration file relating to storage behavior.
    """
    @property
    def preferred_peers(self):
        return self.storage_client_config.preferred_peers

    def __init__(
        self,
        permute_peers,
        tub_maker,
        node_config,
        storage_client_config=None,
    ):
        service.MultiService.__init__(self)
        assert permute_peers  # False not implemented yet
        self.permute_peers = permute_peers
        self._tub_maker = tub_maker

        self.node_config = node_config

        if storage_client_config is None:
            storage_client_config = StorageClientConfig()
        self.storage_client_config = storage_client_config

        # self.servers maps serverid -> IServer, and keeps track of all the
        # storage servers that we've heard about. Each descriptor manages its
        # own Reconnector, and will give us a RemoteReference when we ask
        # them for it.
        self.servers = BytesKeyDict()
        self._static_server_ids = set()  # ignore announcements for these
        self.introducer_client = None
        self._threshold_listeners = []  # tuples of (threshold, Deferred)
        self._connected_high_water_mark = 0

    @log_call(action_type=u"storage-client:broker:set-static-servers")
    def set_static_servers(self, servers):
        # Sorting the items gives us a deterministic processing order.  This
        # doesn't really matter but it makes the logging behavior more
        # predictable and easier to test (and at least one test does depend on
        # this sorted order).
        servers = {ensure_text(key): value for (key, value) in servers.items()}
        for (server_id, server) in sorted(servers.items()):
            try:
                storage_server = self._make_storage_server(
                    server_id.encode("utf-8"),
                    server,
                )
            except Exception:
                # TODO: The _make_storage_server failure is logged but maybe
                # we should write a traceback here.  Notably, tests don't
                # automatically fail just because we hit this case.  Well
                # written tests will still fail if a surprising exception
                # arrives here but they might be harder to debug without this
                # information.
                pass
            else:
                if isinstance(server_id, str):
                    server_id = server_id.encode("utf-8")
                self._static_server_ids.add(server_id)
                self.servers[server_id] = storage_server
                storage_server.setServiceParent(self)
                storage_server.start_connecting(self._trigger_connections)

    def get_client_storage_plugin_web_resources(self, node_config):
        """
        Get all of the client-side ``IResource`` implementations provided by
        enabled storage plugins.

        :param allmydata.node._Config node_config: The complete node
            configuration for the node from which these web resources will be
            served.

        :return dict[unicode, IResource]: Resources for all of the plugins.
        """
        plugins = {
            plugin.name: plugin
            for plugin in getPlugins(IFoolscapStoragePlugin)
        }
        return UnicodeKeyDict({
            name: plugins[name].get_client_resource(node_config)
            for (name,
                 config) in self.storage_client_config.storage_plugins.items()
        })

    @log_call(
        action_type=u"storage-client:broker:make-storage-server",
        include_args=["server_id"],
        include_result=False,
    )
    def _make_storage_server(self, server_id, server):
        """
        Create a new ``IServer`` for the given storage server announcement.

        :param bytes server_id: The unique identifier for the server.

        :param dict server: The server announcement.  See ``Static Server
            Definitions`` in the configuration documentation for details about
            the structure and contents.

        :return IServer: The object-y representation of the server described
            by the given announcement.
        """
        assert isinstance(server_id, bytes)
        handler_overrides = server.get("connections", {})
        s = NativeStorageServer(
            server_id,
            server["ann"],
            self._tub_maker,
            handler_overrides,
            self.node_config,
            self.storage_client_config,
        )
        s.on_status_changed(lambda _: self._got_connection())
        return s

    def when_connected_enough(self, threshold):
        """
        :returns: a Deferred that fires if/when our high water mark for
        number of connected servers becomes (or ever was) above
        "threshold".
        """
        d = defer.Deferred()
        self._threshold_listeners.append((threshold, d))
        self._check_connected_high_water_mark()
        return d

    # these two are used in unit tests
    def test_add_rref(self, serverid, rref, ann):
        s = self._make_storage_server(
            serverid,
            {"ann": ann.copy()},
        )
        s._rref = rref
        s._is_connected = True
        self.servers[serverid] = s

    def test_add_server(self, server_id, s):
        s.on_status_changed(lambda _: self._got_connection())
        self.servers[server_id] = s

    def use_introducer(self, introducer_client):
        self.introducer_client = ic = introducer_client
        ic.subscribe_to("storage", self._got_announcement)

    def _got_connection(self):
        # this is called by NativeStorageServer when it is connected
        self._check_connected_high_water_mark()

    def _check_connected_high_water_mark(self):
        current = len(self.get_connected_servers())
        if current > self._connected_high_water_mark:
            self._connected_high_water_mark = current

        remaining = []
        for threshold, d in self._threshold_listeners:
            if self._connected_high_water_mark >= threshold:
                eventually(d.callback, None)
            else:
                remaining.append((threshold, d))
        self._threshold_listeners = remaining

    def _should_ignore_announcement(self, server_id, ann):
        """
        Determine whether a new storage announcement should be discarded or used
        to update our collection of storage servers.

        :param bytes server_id: The unique identifier for the storage server
            which made the announcement.

        :param dict ann: The announcement.

        :return bool: ``True`` if the announcement should be ignored,
            ``False`` if it should be used to update our local storage server
            state.
        """
        # Let local static configuration always override any announcement for
        # a particular server.
        if server_id in self._static_server_ids:
            log.msg(format="ignoring announcement for static server '%(id)s'",
                    id=server_id,
                    facility="tahoe.storage_broker",
                    umid="AlxzqA",
                    level=log.UNUSUAL)
            return True

        try:
            old = self.servers[server_id]
        except KeyError:
            # We don't know anything about this server.  Let's use the
            # announcement to change that.
            return False
        else:
            # Determine if this announcement is at all difference from the
            # announcement we already have for the server.  If it is the same,
            # we don't need to change anything.
            return old.get_announcement() == ann

    def _got_announcement(self, key_s, ann):
        """
        This callback is given to the introducer and called any time an
        announcement is received which has a valid signature and does not have
        a sequence number less than or equal to a previous sequence number
        seen for that server by that introducer.

        Note sequence numbers are not considered between different introducers
        so if we use more than one introducer it is possible for them to
        deliver us stale announcements in some cases.
        """
        precondition(isinstance(key_s, bytes), key_s)
        precondition(key_s.startswith(b"v0-"), key_s)
        precondition(ann["service-name"] == "storage", ann["service-name"])
        server_id = key_s

        if self._should_ignore_announcement(server_id, ann):
            return

        s = self._make_storage_server(
            server_id,
            {u"ann": ann},
        )

        try:
            old = self.servers.pop(server_id)
        except KeyError:
            pass
        else:
            # It's a replacement, get rid of the old one.
            old.stop_connecting()
            old.disownServiceParent()
            # NOTE: this disownServiceParent() returns a Deferred that
            # doesn't fire until Tub.stopService fires, which will wait for
            # any existing connections to be shut down. This doesn't
            # generally matter for normal runtime, but unit tests can run
            # into DirtyReactorErrors if they don't block on these. If a test
            # replaces one server with a newer version, then terminates
            # before the old one has been shut down, it might get
            # DirtyReactorErrors. The fix would be to gather these Deferreds
            # into a structure that will block StorageFarmBroker.stopService
            # until they have fired (but hopefully don't keep reference
            # cycles around when they fire earlier than that, which will
            # almost always be the case for normal runtime).

        # now we forget about them and start using the new one
        s.setServiceParent(self)
        self.servers[server_id] = s
        s.start_connecting(self._trigger_connections)
        # the descriptor will manage their own Reconnector, and each time we
        # need servers, we'll ask them if they're connected or not.

    def _trigger_connections(self):
        # when one connection is established, reset the timers on all others,
        # to trigger a reconnection attempt in one second. This is intended
        # to accelerate server connections when we've been offline for a
        # while. The goal is to avoid hanging out for a long time with
        # connections to only a subset of the servers, which would increase
        # the chances that we'll put shares in weird places (and not update
        # existing shares of mutable files). See #374 for more details.
        for dsc in list(self.servers.values()):
            dsc.try_to_connect()

    def get_servers_for_psi(self, peer_selection_index):
        # return a list of server objects (IServers)
        assert self.permute_peers == True
        connected_servers = self.get_connected_servers()
        preferred_servers = frozenset(
            s for s in connected_servers
            if s.get_longname() in self.preferred_peers)

        def _permuted(server):
            seed = server.get_permutation_seed()
            is_unpreferred = server not in preferred_servers
            return (is_unpreferred,
                    permute_server_hash(peer_selection_index, seed))

        return sorted(connected_servers, key=_permuted)

    def get_all_serverids(self):
        return frozenset(self.servers.keys())

    def get_connected_servers(self):
        return frozenset(
            [s for s in self.servers.values() if s.is_connected()])

    def get_known_servers(self):
        return frozenset(self.servers.values())

    def get_nickname_for_serverid(self, serverid):
        if serverid in self.servers:
            return self.servers[serverid].get_nickname()
        return None

    def get_stub_server(self, serverid):
        if serverid in self.servers:
            return self.servers[serverid]
        # some time before 1.12, we changed "serverid" to be "key_s" (the
        # printable verifying key, used in V2 announcements), instead of the
        # tubid. When the immutable uploader delegates work to a Helper,
        # get_stub_server() is used to map the returning server identifiers
        # to IDisplayableServer instances (to get a name, for display on the
        # Upload Results web page). If the Helper is running 1.12 or newer,
        # it will send pubkeys, but if it's still running 1.11, it will send
        # tubids. This clause maps the old tubids to our existing servers.
        for s in list(self.servers.values()):
            if isinstance(s, NativeStorageServer):
                if serverid == s.get_tubid():
                    return s
        return StubServer(serverid)