This FURL tells the client how to connect to the introducer. Each
Tahoe-LAFS grid is defined by an introducer. The introducer's FURL is
- created by the introducer node and written into its base directory when
- it starts, whereupon it should be published to everyone who wishes to
- attach a client to that grid
+ created by the introducer node and written into its private base
+ directory when it starts, whereupon it should be published to everyone
+ who wishes to attach a client to that grid
``helper.furl = (FURL string, optional)``
The Introducer node maintains some different state than regular client nodes.
-``BASEDIR/introducer.furl``
+``BASEDIR/private/introducer.furl``
This is generated the first time the introducer node is started, and used
again on subsequent runs, to give the introduction service a persistent
"``tahoe create-introducer [NODEDIR]``" is used to create the Introducer node.
This node provides introduction services and nothing else. When started, this
-node will produce an ``introducer.furl`` file, which should be published to all
-clients.
+node will produce a ``private/introducer.furl`` file, which should be
+published to all clients.
"``tahoe create-key-generator [NODEDIR]``" is used to create a special
"key-generation" service, which allows a client to offload their RSA key
name of the directory is up to you), ``cd`` into it, and run
"``tahoe create-introducer .``". Now run the introducer using
"``tahoe start .``". After it starts, it will write a file named
-``introducer.furl`` in that base directory. This file contains the URL
-the other nodes must use in order to connect to this introducer. (Note
-that "``tahoe run .``" doesn't work for introducers, this is a known
-issue: `#937 <http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
+``introducer.furl`` into the ``private/`` subdirectory of that base
+directory. This file contains the URL the other nodes must use in order
+to connect to this introducer. (Note that "``tahoe run .``" doesn't
+work for introducers, this is a known issue: `#937
+<http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
The "``tahoe run``" command above will run the node in the foreground.
On Unix, you can run it in the background instead by using the
-import time, os.path
+import time, os.path, textwrap
from zope.interface import implements
from twisted.application import service
from foolscap.api import Referenceable
import allmydata
from allmydata import node
from allmydata.util import log, rrefutil
+from allmydata.util.encodingutil import get_filesystem_encoding
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, SubscriberDescriptor, AnnouncementDescriptor
+class FurlFileConflictError(Exception):
+ pass
+
class IntroducerNode(node.Node):
PORTNUMFILE = "introducer.port"
NODETYPE = "introducer"
introducerservice = IntroducerService(self.basedir)
self.add_service(introducerservice)
+ old_public_fn = os.path.join(self.basedir, "introducer.furl").encode(get_filesystem_encoding())
+ private_fn = os.path.join(self.basedir, "private", "introducer.furl").encode(get_filesystem_encoding())
+
+ if os.path.exists(old_public_fn):
+ if os.path.exists(private_fn):
+ msg = """This directory (%s) contains both an old public
+ 'introducer.furl' file, and a new-style
+ 'private/introducer.furl', so I cannot safely remove the old
+ one. Please make sure your desired FURL is in
+ private/introducer.furl, and remove the public file. If this
+ causes your Introducer's FURL to change, you need to inform
+ all grid members so they can update their tahoe.cfg.
+ """
+ raise FurlFileConflictError(textwrap.dedent(msg))
+ os.rename(old_public_fn, private_fn)
d = self.when_tub_ready()
def _publish(res):
- self.introducer_url = self.tub.registerReference(introducerservice,
- "introducer")
- self.log(" introducer is at %s" % self.introducer_url,
- umid="qF2L9A")
- self.write_config("introducer.furl", self.introducer_url + "\n")
+ furl = self.tub.registerReference(introducerservice,
+ furlFile=private_fn)
+ self.log(" introducer is at %s" % furl, umid="qF2L9A")
+ self.introducer_url = furl # for tests
d.addCallback(_publish)
d.addErrback(log.err, facility="tahoe.init",
level=log.BAD, umid="UaNs9A")
from allmydata.interfaces import InsufficientVersionError
from allmydata.introducer.client import IntroducerClient, \
WrapV2ClientInV1Interface
-from allmydata.introducer.server import IntroducerService
+from allmydata.introducer.server import IntroducerService, FurlFileConflictError
from allmydata.introducer.common import get_tubid_string_from_ann, \
get_tubid_string, sign_to_foolscap, unsign_from_foolscap, \
UnknownKeyError
log.msg(msg, **kw)
class Node(testutil.SignalMixin, unittest.TestCase):
- def test_loadable(self):
- basedir = "introducer.IntroducerNode.test_loadable"
+ def test_furl(self):
+ basedir = "introducer.IntroducerNode.test_furl"
os.mkdir(basedir)
- q = IntroducerNode(basedir)
+ public_fn = os.path.join(basedir, "introducer.furl")
+ private_fn = os.path.join(basedir, "private", "introducer.furl")
+ q1 = IntroducerNode(basedir)
d = fireEventually(None)
- d.addCallback(lambda res: q.startService())
- d.addCallback(lambda res: q.when_tub_ready())
- d.addCallback(lambda res: q.stopService())
+ d.addCallback(lambda res: q1.startService())
+ d.addCallback(lambda res: q1.when_tub_ready())
+ d.addCallback(lambda res: q1.stopService())
d.addCallback(flushEventualQueue)
+ def _check_furl(res):
+ # new nodes create unguessable furls in private/introducer.furl
+ ifurl = fileutil.read(private_fn)
+ self.failUnless(ifurl)
+ ifurl = ifurl.strip()
+ self.failIf(ifurl.endswith("/introducer"), ifurl)
+
+ # old nodes created guessable furls in BASEDIR/introducer.furl
+ guessable = ifurl[:ifurl.rfind("/")] + "/introducer"
+ fileutil.write(public_fn, guessable+"\n", mode="w") # text
+
+ # if we see both files, throw an error
+ self.failUnlessRaises(FurlFileConflictError,
+ IntroducerNode, basedir)
+
+ # when we see only the public one, move it to private/ and use
+ # the existing furl instead of creating a new one
+ os.unlink(private_fn)
+ q2 = IntroducerNode(basedir)
+ d2 = fireEventually(None)
+ d2.addCallback(lambda res: q2.startService())
+ d2.addCallback(lambda res: q2.when_tub_ready())
+ d2.addCallback(lambda res: q2.stopService())
+ d2.addCallback(flushEventualQueue)
+ def _check_furl2(res):
+ self.failIf(os.path.exists(public_fn))
+ ifurl2 = fileutil.read(private_fn)
+ self.failUnless(ifurl2)
+ self.failUnlessEqual(ifurl2.strip(), guessable)
+ d2.addCallback(_check_furl2)
+ return d2
+ d.addCallback(_check_furl)
return d
class ServiceMixin:
c1 = os.path.join(basedir, "c1")
HOTLINE_FILE = os.path.join(c1, "suicide_prevention_hotline")
TWISTD_PID_FILE = os.path.join(c1, "twistd.pid")
- INTRODUCER_FURL_FILE = os.path.join(c1, "introducer.furl")
+ INTRODUCER_FURL_FILE = os.path.join(c1, "private", "introducer.furl")
PORTNUM_FILE = os.path.join(c1, "introducer.port")
NODE_URL_FILE = os.path.join(c1, "node.url")
CONFIG_FILE = os.path.join(c1, "tahoe.cfg")
d.addCallback(lambda res: self.poll(_node_has_started))
def _started(res):
- # read the introducer.furl and introducer.port files so we can check that their
- # contents don't change on restart
+ # read the introducer.furl and introducer.port files so we can
+ # check that their contents don't change on restart
self.furl = fileutil.read(INTRODUCER_FURL_FILE)
self.failUnless(os.path.exists(PORTNUM_FILE))
self.portnum = fileutil.read(PORTNUM_FILE)