mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-31 08:25:35 +00:00
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.
This commit is contained in:
parent
080c136daf
commit
84c9f3bfb4
@ -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 = ""
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user