From 810ba6834352b8aea1c0ef05e733085ecd0c8670 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 11 Mar 2008 17:36:25 -0700 Subject: [PATCH] add a webserver for the Introducer, showing service announcements and subscriber lists --- src/allmydata/introducer.py | 18 ++++++- src/allmydata/test/test_system.py | 27 ++++++++++ src/allmydata/web/introweb.py | 85 +++++++++++++++++++++++++++++++ src/allmydata/webish.py | 7 ++- 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/allmydata/web/introweb.py diff --git a/src/allmydata/introducer.py b/src/allmydata/introducer.py index b1b58345..5c9d210a 100644 --- a/src/allmydata/introducer.py +++ b/src/allmydata/introducer.py @@ -1,5 +1,5 @@ -import re, time, sha +import re, time, sha, os.path from base64 import b32decode from zope.interface import implements from twisted.application import service @@ -16,6 +16,9 @@ class IntroducerNode(node.Node): def __init__(self, basedir="."): node.Node.__init__(self, basedir) self.init_introducer() + webport = self.get_config("webport") + if webport: + self.init_web(webport) # strports string def init_introducer(self): introducerservice = IntroducerService(self.basedir) @@ -30,6 +33,14 @@ class IntroducerNode(node.Node): d.addCallback(_publish) d.addErrback(log.err, facility="tahoe.init", level=log.BAD) + def init_web(self, webport): + self.log("init_web(webport=%s)", args=(webport,)) + + from allmydata.webish import IntroducerWebishServer + nodeurl_path = os.path.join(self.basedir, "node.url") + ws = IntroducerWebishServer(webport, nodeurl_path) + self.add_service(ws) + class IntroducerService(service.MultiService, Referenceable): implements(RIIntroducerPublisherAndSubscriberService) name = "introducer" @@ -45,6 +56,11 @@ class IntroducerService(service.MultiService, Referenceable): kwargs["facility"] = "tahoe.introducer" return log.msg(*args, **kwargs) + def get_announcements(self): + return frozenset(self._announcements) + def get_subscribers(self): + return self._subscribers + def remote_publish(self, announcement): self.log("introducer: announcement published: %s" % (announcement,) ) (furl, service_name, ri_name, nickname, ver, oldest) = announcement diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index aae0180e..b943d2d6 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -7,6 +7,7 @@ from twisted.internet import defer, reactor from twisted.internet import threads # CLI tests use deferToThread from twisted.internet.error import ConnectionDone, ConnectionLost from twisted.application import service +import allmydata from allmydata import client, uri, download, upload, storage, mutable, offloaded from allmydata.introducer import IntroducerNode from allmydata.util import deferredutil, fileutil, idlib, mathutil, testutil @@ -71,14 +72,23 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): iv_dir = self.getdir("introducer") if not os.path.isdir(iv_dir): fileutil.make_dirs(iv_dir) + f = open(os.path.join(iv_dir, "webport"), "w") + f.write("tcp:0:interface=127.0.0.1\n") + f.close() iv = IntroducerNode(basedir=iv_dir) self.introducer = self.add_service(iv) d = self.introducer.when_tub_ready() + d.addCallback(self._get_introducer_web) d.addCallback(self._set_up_stats_gatherer) d.addCallback(self._set_up_nodes_2) d.addCallback(self._grab_stats) return d + def _get_introducer_web(self, res): + f = open(os.path.join(self.getdir("introducer"), "node.url"), "r") + self.introweb_url = f.read().strip() + f.close() + def _set_up_stats_gatherer(self, res): statsdir = self.getdir("stats_gatherer") fileutil.make_dirs(statsdir) @@ -266,6 +276,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): permuted_peers = list(c.get_permuted_peers("storage", "a")) self.failUnlessEqual(len(permuted_peers), self.numclients) d.addCallback(_check_connections) + def _do_upload(res): log.msg("UPLOADING") u = self.clients[0].getServiceNamed("uploader") @@ -821,6 +832,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): self.basedir = "system/SystemTest/test_vdrive" self.data = LARGE_DATA d = self.set_up_nodes(createprivdir=True) + d.addCallback(self._test_introweb) d.addCallback(self.log, "starting publish") d.addCallback(self._do_publish1) d.addCallback(self._test_runner) @@ -862,6 +874,21 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase): return d test_vdrive.timeout = 1100 + def _test_introweb(self, res): + d = getPage(self.introweb_url, method="GET", followRedirect=True) + def _check(res): + try: + self.failUnless("allmydata: %s" % str(allmydata.__version__) + in res) + self.failUnless("Clients:" in res) + except unittest.FailTest: + print + print "GET %s output was:" % self.introweb_url + print res + raise + d.addCallback(_check) + return d + def _do_publish1(self, res): ut = upload.Data(self.data) c0 = self.clients[0] diff --git a/src/allmydata/web/introweb.py b/src/allmydata/web/introweb.py new file mode 100644 index 00000000..5a58ca5d --- /dev/null +++ b/src/allmydata/web/introweb.py @@ -0,0 +1,85 @@ + +from nevow import rend +from foolscap.referenceable import SturdyRef +from twisted.internet import address +import allmydata +from allmydata import get_package_versions_string +from allmydata.util import idlib +from common import getxmlfile, IClient + +class IntroducerRoot(rend.Page): + + addSlash = True + docFactory = getxmlfile("introducer.xhtml") + + def data_version(self, ctx, data): + return get_package_versions_string() + def data_import_path(self, ctx, data): + return str(allmydata) + def data_my_nodeid(self, ctx, data): + return idlib.nodeid_b2a(IClient(ctx).nodeid) + + def data_known_storage_servers(self, ctx, data): + i = IClient(ctx).getServiceNamed("introducer") + storage = [1 + for (furl, service_name, ri_name, nickname, ver, oldest) + in i.get_announcements() + if service_name == "storage"] + return len(storage) + + def data_num_clients(self, ctx, data): + i = IClient(ctx).getServiceNamed("introducer") + num_clients = 0 + subscribers = i.get_subscribers() + for service_name,who in subscribers.items(): + num_clients += len(who) + return num_clients + + def data_services(self, ctx, data): + i = IClient(ctx).getServiceNamed("introducer") + ann = list(i.get_announcements()) + ann.sort(lambda a,b: cmp( (a[1], a), (b[1], b) ) ) + return ann + + def render_service_row(self, ctx, announcement): + (furl, service_name, ri_name, nickname, ver, oldest) = announcement + sr = SturdyRef(furl) + nodeid = sr.tubID + advertised = [loc.split(":")[0] for loc in sr.locationHints] + ctx.fillSlots("peerid", "%s %s" % (idlib.nodeid_b2a(nodeid), nickname)) + ctx.fillSlots("advertised", " ".join(advertised)) + ctx.fillSlots("connected", "?") + ctx.fillSlots("since", "?") + ctx.fillSlots("announced", "?") + ctx.fillSlots("version", ver) + ctx.fillSlots("service_name", service_name) + return ctx.tag + + def data_subscribers(self, ctx, data): + i = IClient(ctx).getServiceNamed("introducer") + s = [] + for service_name, subscribers in i.get_subscribers().items(): + for rref in subscribers: + s.append( (service_name, rref) ) + s.sort() + return s + + def render_subscriber_row(self, ctx, s): + (service_name, rref) = s + sr = rref.getSturdyRef() + nodeid = sr.tubID + ctx.fillSlots("peerid", "%s" % idlib.nodeid_b2a(nodeid)) + advertised = [loc.split(":")[0] for loc in sr.locationHints] + 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) + ctx.fillSlots("since", "?") + ctx.fillSlots("service_name", service_name) + return ctx.tag + + diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index 89935fb1..cc26579c 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -22,7 +22,7 @@ from foolscap.eventual import fireEventually from nevow.util import resource_filename -from allmydata.web import status, unlinked +from allmydata.web import status, unlinked, introweb from allmydata.web.common import IClient, getxmlfile, get_arg, \ boolean_of_arg, abbreviate_size @@ -1544,11 +1544,12 @@ class LocalAccess: class WebishServer(service.MultiService): name = "webish" + root_class = Root def __init__(self, webport, nodeurl_path=None): service.MultiService.__init__(self) self.webport = webport - self.root = Root() + self.root = self.root_class() self.site = site = appserver.NevowSite(self.root) self.site.requestFactory = MyRequest self.allow_local = LocalAccess() @@ -1590,3 +1591,5 @@ class WebishServer(service.MultiService): f.write(base_url + "\n") f.close() +class IntroducerWebishServer(WebishServer): + root_class = introweb.IntroducerRoot -- 2.45.2