From: Brian Warner Date: Mon, 23 Apr 2012 22:02:22 +0000 (-0400) Subject: Fix introweb display for mixed V1/V2 clients. Closes #1721. X-Git-Url: https://git.rkrishnan.org/specifications/banana.xhtml?a=commitdiff_plain;h=84c9f3bfb4bdfde0e771d6ee291689b0a691e623;p=tahoe-lafs%2Ftahoe-lafs.git Fix introweb display for mixed V1/V2 clients. Closes #1721. This significantly cleans up the IntroducerServer web-status renderers. Instead of poking around in the introducer's internals, now the web-status renderers get clean AnnouncementDescriptor and SubscriberDescriptor objects. They are still somewhat foolscap-centric, but will provide a clean abstraction boundary for future improvements. The specific #1721 bug was that old (V1) subscribers were handled by wrapping their RemoteReference in a special WrapV1SubscriberInV2Interface object, but the web-status display was trying to peek inside the object to learn what host+port it was associated with, and the wrapper did not proxy those extra attributes. A test was added to test_introducer to make sure the introweb page renders properly and at least contains the nicknames of both the V1 and V2 clients. --- diff --git a/src/allmydata/introducer/common.py b/src/allmydata/introducer/common.py index 2f6e9c89..c7347971 100644 --- a/src/allmydata/introducer/common.py +++ b/src/allmydata/introducer/common.py @@ -1,6 +1,7 @@ import re, simplejson -from allmydata.util import keyutil, base32 +from foolscap.api import SturdyRef +from allmydata.util import keyutil, base32, rrefutil def make_index(ann, key_s): """Return something that can be used as an index (e.g. a tuple of @@ -34,7 +35,7 @@ def convert_announcement_v1_to_v2(ann_t): assert type(ver) is str assert type(oldest) is str ann = {"version": 0, - "nickname": nickname.decode("utf-8"), + "nickname": nickname.decode("utf-8", "replace"), "app-versions": {}, "my-version": ver, "oldest-supported": oldest, @@ -90,3 +91,67 @@ def unsign_from_foolscap(ann_t): key_vs = claimed_key_vs ann = simplejson.loads(msg.decode("utf-8")) return (ann, key_vs) + +class SubscriberDescriptor: + """This describes a subscriber, for status display purposes. It contains + the following attributes: + + .service_name: what they subscribed to (string) + .when: time when they subscribed (seconds since epoch) + .nickname: their self-provided nickname, or "?" (unicode) + .version: their self-provided version (string) + .app_versions: versions of each library they use (dict str->str) + .advertised_addresses: what hosts they listen on (list of strings) + .remote_address: the external address from which they connected (string) + .tubid: for subscribers connecting with Foolscap, their tubid (string) + """ + + def __init__(self, service_name, when, + nickname, version, app_versions, + advertised_addresses, remote_address, tubid): + self.service_name = service_name + self.when = when + self.nickname = nickname + self.version = version + self.app_versions = app_versions + self.advertised_addresses = advertised_addresses + self.remote_address = remote_address + self.tubid = tubid + +class AnnouncementDescriptor: + """This describes an announcement, for status display purposes. It + contains the following attributes, which will be empty ("" for + strings) if the client did not provide them: + + .when: time the announcement was first received (seconds since epoch) + .index: the announcements 'index', a tuple of (string-or-None). + The server remembers one announcement per index. + .canary: a Referenceable on the announcer, so the server can learn + when they disconnect (for the status display) + .announcement: raw dictionary of announcement data + .service_name: which service they are announcing (string) + .version: 'my-version' portion of announcement (string) + .nickname: their self-provided nickname, or "" (unicode) + + The following attributes will be empty ([] for lists, "" for strings) + unless the announcement included an 'anonymous-storage-FURL'. + + .advertised_addresses: which hosts they listen on (list of strings) + .tubid: their tubid (string) + """ + + def __init__(self, when, index, canary, ann_d): + self.when = when + self.index = index + self.canary = canary + self.announcement = ann_d + self.service_name = ann_d["service-name"] + self.version = ann_d.get("my-version", "") + self.nickname = ann_d.get("nickname", u"") + furl = ann_d.get("anonymous-storage-FURL") + if furl: + self.advertised_addresses = rrefutil.hosts_for_furl(furl) + self.tubid = SturdyRef(furl).tubID + else: + self.advertised_addresses = [] + self.tubid = "" diff --git a/src/allmydata/introducer/old.py b/src/allmydata/introducer/old.py index e0bdacf7..36a9af76 100644 --- a/src/allmydata/introducer/old.py +++ b/src/allmydata/introducer/old.py @@ -8,6 +8,8 @@ from allmydata.interfaces import InsufficientVersionError from allmydata.util import log, idlib, rrefutil from foolscap.api import StringConstraint, TupleOf, SetOf, DictOf, Any, \ RemoteInterface, Referenceable, eventually, SturdyRef +from allmydata.introducer.common import SubscriberDescriptor, \ + AnnouncementDescriptor FURL = StringConstraint(1000) # We keep a copy of the old introducer (both client and server) here to @@ -362,7 +364,7 @@ class IntroducerService_v1(service.MultiService, Referenceable): self.introducer_url = None # 'index' is (service_name, tubid) self._announcements = {} # dict of index -> (announcement, timestamp) - self._subscribers = {} # dict of (rref->timestamp) dicts + self._subscribers = {} # [service_name]->[rref]->timestamp self._debug_counts = {"inbound_message": 0, "inbound_duplicate": 0, "inbound_update": 0, @@ -380,10 +382,35 @@ class IntroducerService_v1(service.MultiService, Referenceable): kwargs["facility"] = "tahoe.introducer" return log.msg(*args, **kwargs) - def get_announcements(self): - return self._announcements + def get_announcements(self, include_stub_clients=True): + announcements = [] + for index, (ann_t, when) in self._announcements.items(): + (furl, service_name, ri_name, nickname, ver, oldest) = ann_t + if service_name == "stub_client" and not include_stub_clients: + continue + ann_d = {"nickname": nickname.decode("utf-8", "replace"), + "my-version": ver, + "service-name": service_name, + "anonymous-storage-FURL": furl, + } + ad = AnnouncementDescriptor(when, index, None, ann_d) + announcements.append(ad) + return announcements + def get_subscribers(self): - return self._subscribers + s = [] + for service_name, subscribers in self._subscribers.items(): + for rref, when in subscribers.items(): + tubid = rref.getRemoteTubID() or "?" + advertised_addresses = rrefutil.hosts_for_rref(rref) + remote_address = rrefutil.stringify_remote_address(rref) + nickname, version, app_versions = u"?", u"?", {} + sd = SubscriberDescriptor(service_name, when, + nickname, version, app_versions, + advertised_addresses, remote_address, + tubid) + s.append(sd) + return s def remote_get_version(self): return self.VERSION diff --git a/src/allmydata/introducer/server.py b/src/allmydata/introducer/server.py index 41f92b23..394fdf00 100644 --- a/src/allmydata/introducer/server.py +++ b/src/allmydata/introducer/server.py @@ -5,12 +5,12 @@ from twisted.application import service from foolscap.api import Referenceable import allmydata from allmydata import node -from allmydata.util import log +from allmydata.util import log, rrefutil from allmydata.introducer.interfaces import \ RIIntroducerPublisherAndSubscriberService_v2 from allmydata.introducer.common import convert_announcement_v1_to_v2, \ convert_announcement_v2_to_v1, unsign_from_foolscap, make_index, \ - get_tubid_string_from_ann + get_tubid_string_from_ann, SubscriberDescriptor, AnnouncementDescriptor class IntroducerNode(node.Node): PORTNUMFILE = "introducer.port" @@ -56,7 +56,7 @@ class WrapV1SubscriberInV2Interface: # for_v1 """ def __init__(self, original): - self.original = original + self.original = original # also used for tests def __eq__(self, them): return self.original == them def __ne__(self, them): @@ -69,6 +69,8 @@ class WrapV1SubscriberInV2Interface: # for_v1 return self.original.getSturdyRef() def getPeer(self): return self.original.getPeer() + def getLocationHints(self): + return self.original.getLocationHints() def callRemote(self, methname, *args, **kwargs): m = getattr(self, "wrap_" + methname) return m(*args, **kwargs) @@ -133,16 +135,42 @@ class IntroducerService(service.MultiService, Referenceable): kwargs["facility"] = "tahoe.introducer.server" return log.msg(*args, **kwargs) - def get_announcements(self): - return self._announcements + def get_announcements(self, include_stub_clients=True): + """Return a list of AnnouncementDescriptor for all announcements""" + announcements = [] + for (index, (_, canary, ann, when)) in self._announcements.items(): + if ann["service-name"] == "stub_client": + if not include_stub_clients: + continue + ad = AnnouncementDescriptor(when, index, canary, ann) + announcements.append(ad) + return announcements + def get_subscribers(self): - """Return a list of (service_name, when, subscriber_info, rref) for - all subscribers. subscriber_info is a dict with the following keys: - version, nickname, app-versions, my-version, oldest-supported""" + """Return a list of SubscriberDescriptor objects for all subscribers""" s = [] for service_name, subscriptions in self._subscribers.items(): for rref,(subscriber_info,when) in subscriptions.items(): - s.append( (service_name, when, subscriber_info, rref) ) + # note that if the subscriber didn't do Tub.setLocation, + # tubid will be None. Also, subscribers do not tell us which + # pubkey they use; only publishers do that. + tubid = rref.getRemoteTubID() or "?" + advertised_addresses = rrefutil.hosts_for_rref(rref) + remote_address = rrefutil.stringify_remote_address(rref) + # these three assume subscriber_info["version"]==0, but + # should tolerate other versions + if not subscriber_info: + # V1 clients that haven't yet sent their stub_info data + subscriber_info = {} + nickname = subscriber_info.get("nickname", u"?") + version = subscriber_info.get("my-version", u"?") + app_versions = subscriber_info.get("app-versions", {}) + # 'when' is the time they subscribed + sd = SubscriberDescriptor(service_name, when, + nickname, version, app_versions, + advertised_addresses, remote_address, + tubid) + s.append(sd) return s def remote_get_version(self): diff --git a/src/allmydata/test/test_introducer.py b/src/allmydata/test/test_introducer.py index 841771ef..1c61ad71 100644 --- a/src/allmydata/test/test_introducer.py +++ b/src/allmydata/test/test_introducer.py @@ -4,7 +4,7 @@ from base64 import b32decode import simplejson from twisted.trial import unittest -from twisted.internet import defer +from twisted.internet import defer, address from twisted.python import log from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue @@ -19,6 +19,7 @@ from allmydata.introducer.common import get_tubid_string_from_ann, \ from allmydata.introducer import old # test compatibility with old introducer .tac files from allmydata.introducer import IntroducerNode +from allmydata.web import introweb from allmydata.util import pollmixin, keyutil import allmydata.test.common_util as testutil @@ -95,17 +96,19 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin): announcements = i.get_announcements() self.failUnlessEqual(len(announcements), 1) key1 = ("storage", "v0-"+keyid, None) - self.failUnless(key1 in announcements) - (ign, ign, ann1_out, ign) = announcements[key1] + self.failUnlessEqual(announcements[0].index, key1) + ann1_out = announcements[0].announcement self.failUnlessEqual(ann1_out["anonymous-storage-FURL"], furl1) furl2 = "pb://%s@127.0.0.1:36106/swissnum" % keyid ann2 = (furl2, "storage", "RIStorage", "nick1", "ver23", "ver0") i.remote_publish(ann2) + announcements = i.get_announcements() self.failUnlessEqual(len(announcements), 2) key2 = ("storage", None, keyid) - self.failUnless(key2 in announcements) - (ign, ign, ann2_out, ign) = announcements[key2] + wanted = [ad for ad in announcements if ad.index == key2] + self.failUnlessEqual(len(wanted), 1) + ann2_out = wanted[0].announcement self.failUnlessEqual(ann2_out["anonymous-storage-FURL"], furl2) @@ -295,6 +298,7 @@ class Client(unittest.TestCase): d.addCallback(_then2) return d +NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE class SystemTestMixin(ServiceMixin, pollmixin.PollMixin): @@ -342,9 +346,9 @@ class Queue(SystemTestMixin, unittest.TestCase): return self.poll(_got_announcement) d.addCallback(_offline) def _done(ign): - v = list(introducer.get_announcements().values())[0] - (ign, ign, ann1_out, ign) = v - self.failUnlessEqual(ann1_out["anonymous-storage-FURL"], furl1) + v = introducer.get_announcements()[0] + furl = v.announcement["anonymous-storage-FURL"] + self.failUnlessEqual(furl, furl1) d.addCallback(_done) # now let the ack get back @@ -404,11 +408,11 @@ class SystemTest(SystemTestMixin, unittest.TestCase): log.msg("creating client %d: %s" % (i, tub.getShortTubID())) if i == 0: c = old.IntroducerClient_v1(tub, self.introducer_furl, - u"nickname-%d" % i, + NICKNAME % str(i), "version", "oldest") else: c = IntroducerClient(tub, self.introducer_furl, - u"nickname-%d" % i, + NICKNAME % str(i), "version", "oldest", {"component": "component-v1"}) received_announcements[c] = {} @@ -541,7 +545,7 @@ class SystemTest(SystemTestMixin, unittest.TestCase): ann = anns[nodeid0] nick = ann["nickname"] self.failUnlessEqual(type(nick), unicode) - self.failUnlessEqual(nick, u"nickname-0") + self.failUnlessEqual(nick, NICKNAME % "0") if server_version == V1: for c in publishing_clients: cdc = c._debug_counts @@ -566,6 +570,12 @@ class SystemTest(SystemTestMixin, unittest.TestCase): ]: expected = 2 self.failUnlessEqual(cdc["outbound_message"], expected) + # now check the web status, make sure it renders without error + ir = introweb.IntroducerRoot(self.parent) + self.parent.nodeid = "NODEID" + text = ir.renderSynchronously().decode("utf-8") + self.failUnlessIn(NICKNAME % "0", text) # the v1 client + self.failUnlessIn(NICKNAME % "1", text) # a v2 client log.msg("_check1 done") d.addCallback(_check1) @@ -699,13 +709,17 @@ class SystemTest(SystemTestMixin, unittest.TestCase): class FakeRemoteReference: def notifyOnDisconnect(self, *args, **kwargs): pass def getRemoteTubID(self): return "62ubehyunnyhzs7r6vdonnm2hpi52w6y" + def getLocationHints(self): return [("ipv4", "here.example.com", "1234"), + ("ipv4", "there.example.com", "2345")] + def getPeer(self): return address.IPv4Address("TCP", "remote.example.com", + 3456) class ClientInfo(unittest.TestCase): def test_client_v2(self): introducer = IntroducerService() tub = introducer_furl = None app_versions = {"whizzy": "fizzy"} - client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2", + client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2", "my_version", "oldest", app_versions) #furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" #ann_s = make_ann_t(client_v2, furl1, None) @@ -713,16 +727,13 @@ class ClientInfo(unittest.TestCase): subscriber = FakeRemoteReference() introducer.remote_subscribe_v2(subscriber, "storage", client_v2._my_subscriber_info) - s = introducer.get_subscribers() - self.failUnlessEqual(len(s), 1) - sn, when, si, rref = s[0] - self.failUnlessIdentical(rref, subscriber) - self.failUnlessEqual(sn, "storage") - self.failUnlessEqual(si["version"], 0) - self.failUnlessEqual(si["oldest-supported"], "oldest") - self.failUnlessEqual(si["app-versions"], app_versions) - self.failUnlessEqual(si["nickname"], u"nick-v2") - self.failUnlessEqual(si["my-version"], "my_version") + subs = introducer.get_subscribers() + self.failUnlessEqual(len(subs), 1) + s0 = subs[0] + self.failUnlessEqual(s0.service_name, "storage") + self.failUnlessEqual(s0.app_versions, app_versions) + self.failUnlessEqual(s0.nickname, NICKNAME % u"v2") + self.failUnlessEqual(s0.version, "my_version") def test_client_v1(self): introducer = IntroducerService() @@ -730,50 +741,39 @@ class ClientInfo(unittest.TestCase): introducer.remote_subscribe(subscriber, "storage") # the v1 subscribe interface had no subscriber_info: that was usually # sent in a separate stub_client pseudo-announcement - s = introducer.get_subscribers() - self.failUnlessEqual(len(s), 1) - sn, when, si, rref = s[0] - # rref will be a WrapV1SubscriberInV2Interface around the real - # subscriber - self.failUnlessIdentical(rref.original, subscriber) - self.failUnlessEqual(si, None) # not known yet - self.failUnlessEqual(sn, "storage") + subs = introducer.get_subscribers() + self.failUnlessEqual(len(subs), 1) + s0 = subs[0] + self.failUnlessEqual(s0.nickname, u"?") # not known yet + self.failUnlessEqual(s0.service_name, "storage") # now submit the stub_client announcement furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" ann = (furl1, "stub_client", "RIStubClient", - u"nick-v1".encode("utf-8"), "my_version", "oldest") + (NICKNAME % u"v1").encode("utf-8"), "my_version", "oldest") introducer.remote_publish(ann) # the server should correlate the two - s = introducer.get_subscribers() - self.failUnlessEqual(len(s), 1) - sn, when, si, rref = s[0] - self.failUnlessIdentical(rref.original, subscriber) - self.failUnlessEqual(sn, "storage") - - self.failUnlessEqual(si["version"], 0) - self.failUnlessEqual(si["oldest-supported"], "oldest") + subs = introducer.get_subscribers() + self.failUnlessEqual(len(subs), 1) + s0 = subs[0] + self.failUnlessEqual(s0.service_name, "storage") # v1 announcements do not contain app-versions - self.failUnlessEqual(si["app-versions"], {}) - self.failUnlessEqual(si["nickname"], u"nick-v1") - self.failUnlessEqual(si["my-version"], "my_version") + self.failUnlessEqual(s0.app_versions, {}) + self.failUnlessEqual(s0.nickname, NICKNAME % u"v1") + self.failUnlessEqual(s0.version, "my_version") # a subscription that arrives after the stub_client announcement # should be correlated too subscriber2 = FakeRemoteReference() introducer.remote_subscribe(subscriber2, "thing2") - s = introducer.get_subscribers() - subs = dict([(sn, (si,rref)) for sn, when, si, rref in s]) + subs = introducer.get_subscribers() self.failUnlessEqual(len(subs), 2) - (si,rref) = subs["thing2"] - self.failUnlessIdentical(rref.original, subscriber2) - self.failUnlessEqual(si["version"], 0) - self.failUnlessEqual(si["oldest-supported"], "oldest") + s0 = [s for s in subs if s.service_name == "thing2"][0] # v1 announcements do not contain app-versions - self.failUnlessEqual(si["app-versions"], {}) - self.failUnlessEqual(si["nickname"], u"nick-v1") - self.failUnlessEqual(si["my-version"], "my_version") + self.failUnlessEqual(s0.app_versions, {}) + self.failUnlessEqual(s0.nickname, NICKNAME % u"v1") + self.failUnlessEqual(s0.version, "my_version") class Announcements(unittest.TestCase): def test_client_v2_unsigned(self): @@ -789,14 +789,13 @@ class Announcements(unittest.TestCase): introducer.remote_publish_v2(ann_s0, canary0) a = introducer.get_announcements() self.failUnlessEqual(len(a), 1) - (index, (ann_s, canary, ann, when)) = a.items()[0] - self.failUnlessIdentical(canary, canary0) - self.failUnlessEqual(index, ("storage", None, tubid)) - self.failUnlessEqual(ann["app-versions"], app_versions) - self.failUnlessEqual(ann["nickname"], u"nick-v2") - self.failUnlessEqual(ann["service-name"], "storage") - self.failUnlessEqual(ann["my-version"], "my_version") - self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1) + self.failUnlessIdentical(a[0].canary, canary0) + self.failUnlessEqual(a[0].index, ("storage", None, tubid)) + self.failUnlessEqual(a[0].announcement["app-versions"], app_versions) + self.failUnlessEqual(a[0].nickname, u"nick-v2") + self.failUnlessEqual(a[0].service_name, "storage") + self.failUnlessEqual(a[0].version, "my_version") + self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1) def test_client_v2_signed(self): introducer = IntroducerService() @@ -813,14 +812,13 @@ class Announcements(unittest.TestCase): introducer.remote_publish_v2(ann_t0, canary0) a = introducer.get_announcements() self.failUnlessEqual(len(a), 1) - (index, (ann_s, canary, ann, when)) = a.items()[0] - self.failUnlessIdentical(canary, canary0) - self.failUnlessEqual(index, ("storage", pks, None)) - self.failUnlessEqual(ann["app-versions"], app_versions) - self.failUnlessEqual(ann["nickname"], u"nick-v2") - self.failUnlessEqual(ann["service-name"], "storage") - self.failUnlessEqual(ann["my-version"], "my_version") - self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1) + self.failUnlessIdentical(a[0].canary, canary0) + self.failUnlessEqual(a[0].index, ("storage", pks, None)) + self.failUnlessEqual(a[0].announcement["app-versions"], app_versions) + self.failUnlessEqual(a[0].nickname, u"nick-v2") + self.failUnlessEqual(a[0].service_name, "storage") + self.failUnlessEqual(a[0].version, "my_version") + self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1) def test_client_v1(self): introducer = IntroducerService() @@ -833,14 +831,13 @@ class Announcements(unittest.TestCase): a = introducer.get_announcements() self.failUnlessEqual(len(a), 1) - (index, (ann_s, canary, ann, when)) = a.items()[0] - self.failUnlessEqual(canary, None) - self.failUnlessEqual(index, ("storage", None, tubid)) - self.failUnlessEqual(ann["app-versions"], {}) - self.failUnlessEqual(ann["nickname"], u"nick-v1".encode("utf-8")) - self.failUnlessEqual(ann["service-name"], "storage") - self.failUnlessEqual(ann["my-version"], "my_version") - self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1) + self.failUnlessEqual(a[0].index, ("storage", None, tubid)) + self.failUnlessEqual(a[0].canary, None) + self.failUnlessEqual(a[0].announcement["app-versions"], {}) + self.failUnlessEqual(a[0].nickname, u"nick-v1".encode("utf-8")) + self.failUnlessEqual(a[0].service_name, "storage") + self.failUnlessEqual(a[0].version, "my_version") + self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1) class TooNewServer(IntroducerService): diff --git a/src/allmydata/util/rrefutil.py b/src/allmydata/util/rrefutil.py index fd2259b6..a14d15f1 100644 --- a/src/allmydata/util/rrefutil.py +++ b/src/allmydata/util/rrefutil.py @@ -1,5 +1,7 @@ -from foolscap.api import Violation, RemoteException, DeadReferenceError +from twisted.internet import address +from foolscap.api import Violation, RemoteException, DeadReferenceError, \ + SturdyRef def add_version_to_remote_reference(rref, default): """I try to add a .version attribute to the given RemoteReference. I call @@ -23,3 +25,36 @@ def trap_and_discard(f, *errorTypes): def trap_deadref(f): return trap_and_discard(f, DeadReferenceError) + + +def hosts_for_rref(rref, ignore_localhost=True): + # actually, this only returns hostnames + advertised = [] + for hint in rref.getLocationHints(): + # Foolscap-0.2.5 and earlier used strings in .locationHints, but we + # require a newer version that uses tuples of ("ipv4", host, port) + assert not isinstance(hint, str), hint + if hint[0] == "ipv4": + host = hint[1] + if ignore_localhost and host == "127.0.0.1": + continue + advertised.append(host) + return advertised + +def hosts_for_furl(furl, ignore_localhost=True): + advertised = [] + for hint in SturdyRef(furl).locationHints: + assert not isinstance(hint, str), hint + if hint[0] == "ipv4": + host = hint[1] + if ignore_localhost and host == "127.0.0.1": + continue + advertised.append(host) + return advertised + +def stringify_remote_address(rref): + remote = rref.getPeer() + if isinstance(remote, address.IPv4Address): + return "%s:%d" % (remote.host, remote.port) + # loopback is a non-IPv4Address + return str(remote) diff --git a/src/allmydata/web/introweb.py b/src/allmydata/web/introweb.py index 783b2bee..fb804951 100644 --- a/src/allmydata/web/introweb.py +++ b/src/allmydata/web/introweb.py @@ -3,8 +3,6 @@ import time, os from nevow import rend, inevow from nevow.static import File as nevow_File from nevow.util import resource_filename -from foolscap.api import SturdyRef -from twisted.internet import address import allmydata import simplejson from allmydata import get_package_versions_string @@ -36,18 +34,16 @@ class IntroducerRoot(rend.Page): res = {} counts = {} - subscribers = self.introducer_service.get_subscribers() - for (service_name, ign, ign, ign) in subscribers: - if service_name not in counts: - counts[service_name] = 0 - counts[service_name] += 1 + for s in self.introducer_service.get_subscribers(): + if s.service_name not in counts: + counts[s.service_name] = 0 + counts[s.service_name] += 1 res["subscription_summary"] = counts announcement_summary = {} service_hosts = {} - for a in self.introducer_service.get_announcements().values(): - (_, _, ann, when) = a - service_name = ann["service-name"] + for ad in self.introducer_service.get_announcements(): + service_name = ad.service_name if service_name not in announcement_summary: announcement_summary[service_name] = 0 announcement_summary[service_name] += 1 @@ -60,12 +56,7 @@ class IntroducerRoot(rend.Page): # enough: when multiple services are run on a single host, # they're usually either configured with the same addresses, # or setLocationAutomatically picks up the same interfaces. - furl = ann["anonymous-storage-FURL"] - locations = SturdyRef(furl).getTubRef().getLocations() - # list of tuples, ("ipv4", host, port) - host = frozenset([hint[1] - for hint in locations - if hint[0] == "ipv4"]) + host = frozenset(ad.advertised_addresses) service_hosts[service_name].add(host) res["announcement_summary"] = announcement_summary distinct_hosts = dict([(name, len(hosts)) @@ -85,12 +76,10 @@ class IntroducerRoot(rend.Page): def render_announcement_summary(self, ctx, data): services = {} - for a in self.introducer_service.get_announcements().values(): - (_, _, ann, when) = a - service_name = ann["service-name"] - if service_name not in services: - services[service_name] = 0 - services[service_name] += 1 + for ad in self.introducer_service.get_announcements(): + if ad.service_name not in services: + services[ad.service_name] = 0 + services[ad.service_name] += 1 service_names = services.keys() service_names.sort() return ", ".join(["%s: %d" % (service_name, services[service_name]) @@ -98,88 +87,40 @@ class IntroducerRoot(rend.Page): def render_client_summary(self, ctx, data): counts = {} - clients = self.introducer_service.get_subscribers() - for (service_name, ign, ign, ign) in clients: - if service_name not in counts: - counts[service_name] = 0 - counts[service_name] += 1 + for s in self.introducer_service.get_subscribers(): + if s.service_name not in counts: + counts[s.service_name] = 0 + counts[s.service_name] += 1 return ", ".join([ "%s: %d" % (name, counts[name]) for name in sorted(counts.keys()) ] ) def data_services(self, ctx, data): - introsvc = self.introducer_service - services = [] - for a in introsvc.get_announcements().values(): - (_, _, ann, when) = a - if ann["service-name"] == "stub_client": - continue - services.append( (when, ann) ) - services.sort(key=lambda x: (x[1]["service-name"], x[1]["nickname"])) - # this used to be: - #services.sort(lambda a,b: cmp( (a[1][1], a), (b[1][1], b) ) ) - # service_name was the primary key, then the whole tuple (starting - # with the furl) was the secondary key + services = self.introducer_service.get_announcements(False) + services.sort(key=lambda ad: (ad.service_name, ad.nickname)) return services - def render_service_row(self, ctx, (since,ann)): - sr = SturdyRef(ann["anonymous-storage-FURL"]) - nodeid = sr.tubID - advertised = self.show_location_hints(sr) - ctx.fillSlots("peerid", nodeid) - ctx.fillSlots("nickname", ann["nickname"]) - ctx.fillSlots("advertised", " ".join(advertised)) + def render_service_row(self, ctx, ad): + ctx.fillSlots("peerid", ad.tubid) + ctx.fillSlots("nickname", ad.nickname) + ctx.fillSlots("advertised", " ".join(ad.advertised_addresses)) ctx.fillSlots("connected", "?") - TIME_FORMAT = "%H:%M:%S %d-%b-%Y" - ctx.fillSlots("announced", - time.strftime(TIME_FORMAT, time.localtime(since))) - ctx.fillSlots("version", ann["my-version"]) - ctx.fillSlots("service_name", ann["service-name"]) + when_s = time.strftime("%H:%M:%S %d-%b-%Y", time.localtime(ad.when)) + ctx.fillSlots("announced", when_s) + ctx.fillSlots("version", ad.version) + ctx.fillSlots("service_name", ad.service_name) return ctx.tag def data_subscribers(self, ctx, data): return self.introducer_service.get_subscribers() def render_subscriber_row(self, ctx, s): - (service_name, since, info, rref) = s - nickname = info.get("nickname", "?") - version = info.get("my-version", "?") - - sr = rref.getSturdyRef() - # if the subscriber didn't do Tub.setLocation, nodeid will be None - nodeid = sr.tubID or "?" - ctx.fillSlots("peerid", nodeid) - ctx.fillSlots("nickname", nickname) - advertised = self.show_location_hints(sr) - ctx.fillSlots("advertised", " ".join(advertised)) - remote_host = rref.tracker.broker.transport.getPeer() - if isinstance(remote_host, address.IPv4Address): - remote_host_s = "%s:%d" % (remote_host.host, remote_host.port) - else: - # loopback is a non-IPv4Address - remote_host_s = str(remote_host) - ctx.fillSlots("connected", remote_host_s) - TIME_FORMAT = "%H:%M:%S %d-%b-%Y" - ctx.fillSlots("since", - time.strftime(TIME_FORMAT, time.localtime(since))) - ctx.fillSlots("version", version) - ctx.fillSlots("service_name", service_name) + ctx.fillSlots("nickname", s.nickname) + ctx.fillSlots("peerid", s.tubid) + ctx.fillSlots("advertised", " ".join(s.advertised_addresses)) + ctx.fillSlots("connected", s.remote_address) + since_s = time.strftime("%H:%M:%S %d-%b-%Y", time.localtime(s.when)) + ctx.fillSlots("since", since_s) + ctx.fillSlots("version", s.version) + ctx.fillSlots("service_name", s.service_name) return ctx.tag - def show_location_hints(self, sr, ignore_localhost=True): - advertised = [] - for hint in sr.locationHints: - if isinstance(hint, str): - # Foolscap-0.2.5 and earlier used strings in .locationHints - if ignore_localhost and hint.startswith("127.0.0.1"): - continue - advertised.append(hint.split(":")[0]) - else: - # Foolscap-0.2.6 and later use tuples of ("ipv4", host, port) - if hint[0] == "ipv4": - host = hint[1] - if ignore_localhost and host == "127.0.0.1": - continue - advertised.append(hint[1]) - return advertised - -