Add IntroducerClient write-only yaml cache file

this change also includes unit tests
This commit is contained in:
David Stainton
2016-05-10 20:19:35 +00:00
parent e3aef2b966
commit ae2b82a1f0
4 changed files with 125 additions and 17 deletions

View File

@ -87,6 +87,7 @@ install_requires = [
# * pyOpenSSL >= 0.14 is needed in order to avoid # * pyOpenSSL >= 0.14 is needed in order to avoid
# <https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2474>. # <https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2474>.
"pyOpenSSL >= 0.14", "pyOpenSSL >= 0.14",
"PyYAML >= 3.11",
] ]
# Includes some indirect dependencies, but does not include allmydata. # Includes some indirect dependencies, but does not include allmydata.
@ -114,6 +115,7 @@ package_imports = [
('six', 'six'), ('six', 'six'),
('enum34', 'enum'), ('enum34', 'enum'),
('pycparser', 'pycparser'), ('pycparser', 'pycparser'),
('PyYAML', 'yaml'),
] ]
# Dependencies for which we don't know how to get a version number at run-time. # Dependencies for which we don't know how to get a version number at run-time.

View File

@ -6,6 +6,7 @@ from zope.interface import implements
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
from twisted.application import service from twisted.application import service
from twisted.application.internet import TimerService from twisted.application.internet import TimerService
from twisted.python.filepath import FilePath
from pycryptopp.publickey import rsa from pycryptopp.publickey import rsa
import allmydata import allmydata
@ -171,12 +172,13 @@ class Client(node.Node, pollmixin.PollMixin):
def init_introducer_client(self): def init_introducer_client(self):
self.introducer_furl = self.get_config("client", "introducer.furl") self.introducer_furl = self.get_config("client", "introducer.furl")
introducer_cache_filepath = FilePath(os.path.join(self.basedir, "private", "introducer_cache.yaml"))
ic = IntroducerClient(self.tub, self.introducer_furl, ic = IntroducerClient(self.tub, self.introducer_furl,
self.nickname, self.nickname,
str(allmydata.__full_version__), str(allmydata.__full_version__),
str(self.OLDEST_SUPPORTED_VERSION), str(self.OLDEST_SUPPORTED_VERSION),
self.get_app_versions(), self.get_app_versions(),
self._sequencer) self._sequencer, introducer_cache_filepath)
self.introducer_client = ic self.introducer_client = ic
ic.setServiceParent(self) ic.setServiceParent(self)

View File

@ -1,5 +1,5 @@
import time import time, yaml
from zope.interface import implements from zope.interface import implements
from twisted.application import service from twisted.application import service
from foolscap.api import Referenceable, eventually, RemoteInterface from foolscap.api import Referenceable, eventually, RemoteInterface
@ -47,7 +47,7 @@ class IntroducerClient(service.Service, Referenceable):
def __init__(self, tub, introducer_furl, def __init__(self, tub, introducer_furl,
nickname, my_version, oldest_supported, nickname, my_version, oldest_supported,
app_versions, sequencer): app_versions, sequencer, cache_filepath):
self._tub = tub self._tub = tub
self.introducer_furl = introducer_furl self.introducer_furl = introducer_furl
@ -57,6 +57,7 @@ class IntroducerClient(service.Service, Referenceable):
self._oldest_supported = oldest_supported self._oldest_supported = oldest_supported
self._app_versions = app_versions self._app_versions = app_versions
self._sequencer = sequencer self._sequencer = sequencer
self._cache_filepath = cache_filepath
self._my_subscriber_info = { "version": 0, self._my_subscriber_info = { "version": 0,
"nickname": self._nickname, "nickname": self._nickname,
@ -112,6 +113,25 @@ class IntroducerClient(service.Service, Referenceable):
level=log.WEIRD, failure=failure, umid="c5MqUQ") level=log.WEIRD, failure=failure, umid="c5MqUQ")
d = self._tub.getReference(self.introducer_furl) d = self._tub.getReference(self.introducer_furl)
d.addErrback(connect_failed) d.addErrback(connect_failed)
def remove_cache(result):
try:
self._cache_filepath.remove()
except OSError, e:
pass
return result
d.addCallback(remove_cache)
def _save_announcements(self):
announcements = []
for _, value in self._inbound_announcements.items():
ann, key_s, time_stamp = value
server_params = {
"ann" : ann,
"key_s" : key_s,
}
announcements.append(server_params)
announcement_cache_yaml = yaml.dump(announcements)
self._cache_filepath.setContent(announcement_cache_yaml)
def _got_introducer(self, publisher): def _got_introducer(self, publisher):
self.log("connected to introducer, getting versions") self.log("connected to introducer, getting versions")
@ -148,6 +168,7 @@ class IntroducerClient(service.Service, Referenceable):
return log.msg(*args, **kwargs) return log.msg(*args, **kwargs)
def subscribe_to(self, service_name, cb, *args, **kwargs): def subscribe_to(self, service_name, cb, *args, **kwargs):
self._got_announcement_cb = cb
self._local_subscribers.append( (service_name,cb,args,kwargs) ) self._local_subscribers.append( (service_name,cb,args,kwargs) )
self._subscribed_service_names.add(service_name) self._subscribed_service_names.add(service_name)
self._maybe_subscribe() self._maybe_subscribe()
@ -349,5 +370,7 @@ class IntroducerClient(service.Service, Referenceable):
if service_name2 == service_name: if service_name2 == service_name:
eventually(cb, key_s, ann, *args, **kwargs) eventually(cb, key_s, ann, *args, **kwargs)
self._save_announcements()
def connected_to_introducer(self): def connected_to_introducer(self):
return bool(self._publisher) return bool(self._publisher)

View File

@ -1,11 +1,12 @@
import os, re, itertools import os, re, itertools, yaml
from base64 import b32decode from base64 import b32decode
import simplejson import simplejson
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer, address from twisted.internet import defer, address
from twisted.python import log from twisted.python import log
from twisted.python.filepath import FilePath
from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue
from twisted.application import service from twisted.application import service
@ -91,7 +92,7 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
def test_create(self): def test_create(self):
ic = IntroducerClient(None, "introducer.furl", u"my_nickname", ic = IntroducerClient(None, "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq) "my_version", "oldest_version", {}, fakeseq, FilePath("notexist.introduced.yaml"))
self.failUnless(isinstance(ic, IntroducerClient)) self.failUnless(isinstance(ic, IntroducerClient))
def test_listen(self): def test_listen(self):
@ -123,7 +124,8 @@ class Introducer(ServiceMixin, unittest.TestCase, pollmixin.PollMixin):
i = IntroducerService() i = IntroducerService()
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq) "my_version", "oldest_version", {}, fakeseq,
FilePath("notexist.introduced.yaml"))
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
keyid = keyutil.remove_prefix(vk_s, "pub-v0-") keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
@ -172,7 +174,8 @@ class Client(unittest.TestCase):
def test_duplicate_receive_v1(self): def test_duplicate_receive_v1(self):
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq) "my_version", "oldest_version", {}, fakeseq,
FilePath("notexist.introduced.yaml"))
announcements = [] announcements = []
ic.subscribe_to("storage", ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann)) lambda key_s,ann: announcements.append(ann))
@ -221,12 +224,14 @@ class Client(unittest.TestCase):
def test_duplicate_receive_v2(self): def test_duplicate_receive_v2(self):
ic1 = IntroducerClient(None, ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}, fakeseq) "ver23", "oldest_version", {}, fakeseq,
FilePath("notexist.introduced.yaml"))
# we use a second client just to create a different-looking # we use a second client just to create a different-looking
# announcement # announcement
ic2 = IntroducerClient(None, ic2 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver24","oldest_version",{}, fakeseq) "ver24","oldest_version",{}, fakeseq,
FilePath("notexist.introduced.yaml"))
announcements = [] announcements = []
def _received(key_s, ann): def _received(key_s, ann):
announcements.append( (key_s, ann) ) announcements.append( (key_s, ann) )
@ -329,7 +334,8 @@ class Client(unittest.TestCase):
# not replace the other) # not replace the other)
ic = IntroducerClient(None, ic = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"my_version", "oldest_version", {}, fakeseq) "my_version", "oldest_version", {}, fakeseq,
FilePath("notexist.introduced.yaml"))
announcements = [] announcements = []
ic.subscribe_to("storage", ic.subscribe_to("storage",
lambda key_s,ann: announcements.append(ann)) lambda key_s,ann: announcements.append(ann))
@ -367,7 +373,8 @@ class Server(unittest.TestCase):
i = IntroducerService() i = IntroducerService()
ic1 = IntroducerClient(None, ic1 = IntroducerClient(None,
"introducer.furl", u"my_nickname", "introducer.furl", u"my_nickname",
"ver23", "oldest_version", {}, realseq) "ver23", "oldest_version", {}, realseq,
FilePath("notexist.introduced.yaml"))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
privkey_s, _ = keyutil.make_keypair() privkey_s, _ = keyutil.make_keypair()
@ -469,7 +476,8 @@ class Queue(SystemTestMixin, unittest.TestCase):
tub2 = Tub() tub2 = Tub()
tub2.setServiceParent(self.parent) tub2.setServiceParent(self.parent)
c = IntroducerClient(tub2, ifurl, c = IntroducerClient(tub2, ifurl,
u"nickname", "version", "oldest", {}, fakeseq) u"nickname", "version", "oldest", {}, fakeseq,
FilePath("notexist.introduced.yaml"))
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
@ -557,7 +565,8 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
c = IntroducerClient(tub, self.introducer_furl, c = IntroducerClient(tub, self.introducer_furl,
NICKNAME % str(i), NICKNAME % str(i),
"version", "oldest", "version", "oldest",
{"component": "component-v1"}, fakeseq) {"component": "component-v1"}, fakeseq,
FilePath("notexist.introduced.yaml"))
received_announcements[c] = {} received_announcements[c] = {}
def got(key_s_or_tubid, ann, announcements, i): def got(key_s_or_tubid, ann, announcements, i):
if i == 0: if i == 0:
@ -877,7 +886,7 @@ class ClientInfo(unittest.TestCase):
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2", client_v2 = IntroducerClient(tub, introducer_furl, NICKNAME % u"v2",
"my_version", "oldest", app_versions, "my_version", "oldest", app_versions,
fakeseq) fakeseq, FilePath("notexist.introduced.yaml"))
#furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" #furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
#ann_s = make_ann_t(client_v2, furl1, None, 10) #ann_s = make_ann_t(client_v2, furl1, None, 10)
#introducer.remote_publish_v2(ann_s, Referenceable()) #introducer.remote_publish_v2(ann_s, Referenceable())
@ -939,7 +948,7 @@ class Announcements(unittest.TestCase):
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2", client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
"my_version", "oldest", app_versions, "my_version", "oldest", app_versions,
fakeseq) fakeseq, FilePath("notexist.introduced.yaml"))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y" tubid = "62ubehyunnyhzs7r6vdonnm2hpi52w6y"
ann_s0 = make_ann_t(client_v2, furl1, None, 10) ann_s0 = make_ann_t(client_v2, furl1, None, 10)
@ -961,7 +970,7 @@ class Announcements(unittest.TestCase):
app_versions = {"whizzy": "fizzy"} app_versions = {"whizzy": "fizzy"}
client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2", client_v2 = IntroducerClient(tub, introducer_furl, u"nick-v2",
"my_version", "oldest", app_versions, "my_version", "oldest", app_versions,
fakeseq) fakeseq, FilePath("notexist.introduced.yaml"))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
sk_s, vk_s = keyutil.make_keypair() sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s) sk, _ignored = keyutil.parse_privkey(sk_s)
@ -998,6 +1007,78 @@ class Announcements(unittest.TestCase):
self.failUnlessEqual(a[0].version, "my_version") self.failUnlessEqual(a[0].version, "my_version")
self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1) self.failUnlessEqual(a[0].announcement["anonymous-storage-FURL"], furl1)
def test_client_cache_1(self):
basedir = "introducer/ClientSeqnums/test_client_cache_1"
fileutil.make_dirs(basedir)
cache_filepath = FilePath(os.path.join(basedir, "private", "introducer_cache.yaml"))
# if storage is enabled, the Client will publish its storage server
# during startup (although the announcement will wait in a queue
# until the introducer connection is established). To avoid getting
# confused by this, disable storage.
f = open(os.path.join(basedir, "tahoe.cfg"), "w")
f.write("[client]\n")
f.write("introducer.furl = nope\n")
f.write("[storage]\n")
f.write("enabled = false\n")
f.close()
c = TahoeClient(basedir)
ic = c.introducer_client
sk_s, vk_s = keyutil.make_keypair()
sk, _ignored = keyutil.parse_privkey(sk_s)
keyid = keyutil.remove_prefix(vk_s, "pub-v0-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
ann_t = make_ann_t(ic, furl1, sk, 1)
ic.got_announcements([ann_t])
# check the cache for the announcement
with cache_filepath.open() as f:
def constructor(loader, node):
return node.value
yaml.SafeLoader.add_constructor("tag:yaml.org,2002:python/unicode", constructor)
announcements = yaml.safe_load(f)
f.close()
self.failUnlessEqual(len(announcements), 1)
self.failUnlessEqual("pub-" + announcements[0]['key_s'], vk_s)
def test_client_cache_2(self):
basedir = "introducer/ClientSeqnums/test_client_cache_2"
fileutil.make_dirs(basedir)
cache_filepath = FilePath(os.path.join(basedir, "private", "introducer_cache.yaml"))
# if storage is enabled, the Client will publish its storage server
# during startup (although the announcement will wait in a queue
# until the introducer connection is established). To avoid getting
# confused by this, disable storage.
f = open(os.path.join(basedir, "tahoe.cfg"), "w")
f.write("[client]\n")
f.write("introducer.furl = nope\n")
f.write("[storage]\n")
f.write("enabled = false\n")
f.close()
c = TahoeClient(basedir)
ic = c.introducer_client
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@192.168.69.247:36106,127.0.0.1:36106/gydnpigj2ja2qr2srq4ikjwnl7xfgbra"
ann1 = (furl1, "storage", "RIStorage", "nick1", "ver23", "ver0")
ann = make_ann_t(ic, furl1, '', 2)
ic.got_announcements([ann])
# check the cache for the announcement
with cache_filepath.open() as f:
def constructor(loader, node):
return node.value
yaml.SafeLoader.add_constructor("tag:yaml.org,2002:python/unicode", constructor)
announcements = yaml.safe_load(f)
f.close()
self.failUnlessEqual(len(announcements), 1)
self.failUnlessEqual(announcements[0]['key_s'], None)
class ClientSeqnums(unittest.TestCase): class ClientSeqnums(unittest.TestCase):
def test_client(self): def test_client(self):
basedir = "introducer/ClientSeqnums/test_client" basedir = "introducer/ClientSeqnums/test_client"
@ -1079,7 +1160,7 @@ class NonV1Server(SystemTestMixin, unittest.TestCase):
c = IntroducerClient(tub, self.introducer_furl, c = IntroducerClient(tub, self.introducer_furl,
u"nickname-client", "version", "oldest", {}, u"nickname-client", "version", "oldest", {},
fakeseq) fakeseq, FilePath("notexist.introduced.yaml"))
announcements = {} announcements = {}
def got(key_s, ann): def got(key_s, ann):
announcements[key_s] = ann announcements[key_s] = ann