2 # This contains a test harness that creates a full Tahoe grid in a single
3 # process (actually in a single MultiService) which does not use the network.
4 # It does not use an Introducer, and there are no foolscap Tubs. Each storage
5 # server puts real shares on disk, but is accessed through loopback
6 # RemoteReferences instead of over serialized SSL. It is not as complete as
7 # the common.SystemTestMixin framework (which does use the network), but
8 # should be considerably faster: on my laptop, it takes 50-80ms to start up,
9 # whereas SystemTestMixin takes close to 2s.
11 # This should be useful for tests which want to examine and/or manipulate the
12 # uploaded shares, checker/verifier/repairer tests, etc. The clients have no
13 # Tubs, so it is not useful for tests that involve a Helper, a KeyGenerator,
14 # or the control.furl .
18 from twisted.application import service
19 from foolscap import Referenceable
20 from foolscap.eventual import fireEventually
21 from base64 import b32encode
22 from allmydata.client import Client
23 from allmydata.storage import StorageServer
24 from allmydata.util import fileutil, idlib, hashutil, rrefutil
25 from allmydata.introducer.client import RemoteServiceConnector
27 class IntentionalError(Exception):
34 def __init__(self, original):
35 self.original = original
37 self.post_call_notifier = None
38 self.disconnectors = {}
40 def callRemoteOnly(self, methname, *args, **kwargs):
41 d = self.callRemote(methname, *args, **kwargs)
44 def callRemote(self, methname, *args, **kwargs):
45 # this is ideally a Membrane, but that's too hard. We do a shallow
46 # wrapping of inbound arguments, and per-methodname wrapping of
47 # selected return values.
49 if isinstance(a, Referenceable):
50 return LocalWrapper(a)
53 args = tuple([wrap(a) for a in args])
54 kwargs = dict([(k,wrap(kwargs[k])) for k in kwargs])
57 raise IntentionalError("I was asked to break")
58 meth = getattr(self.original, "remote_" + methname)
59 return meth(*args, **kwargs)
61 d.addCallback(lambda res: _call())
62 def _return_membrane(res):
63 # rather than complete the difficult task of building a
64 # fully-general Membrane (which would locate all Referenceable
65 # objects that cross the simulated wire and replace them with
66 # wrappers), we special-case certain methods that we happen to
67 # know will return Referenceables.
68 if methname == "allocate_buckets":
69 (alreadygot, allocated) = res
70 for shnum in allocated:
71 allocated[shnum] = LocalWrapper(allocated[shnum])
72 if methname == "get_buckets":
74 res[shnum] = LocalWrapper(res[shnum])
76 d.addCallback(_return_membrane)
77 if self.post_call_notifier:
78 d.addCallback(self.post_call_notifier, methname)
81 def notifyOnDisconnect(self, f, *args, **kwargs):
83 self.disconnectors[m] = (f, args, kwargs)
85 def dontNotifyOnDisconnect(self, marker):
86 del self.disconnectors[marker]
88 def wrap(original, service_name):
89 # The code in immutable.checker insists upon asserting the truth of
90 # isinstance(rref, rrefutil.WrappedRemoteReference). Much of the
91 # upload/download code uses rref.version (which normally comes from
92 # rrefutil.VersionedRemoteReference). To avoid using a network, we want a
93 # LocalWrapper here. Try to satisfy all these constraints at the same
95 local = LocalWrapper(original)
96 wrapped = rrefutil.WrappedRemoteReference(local)
98 version = original.remote_get_version()
99 except AttributeError:
100 version = RemoteServiceConnector.VERSION_DEFAULTS[service_name]
101 wrapped.version = version
104 class NoNetworkClient(Client):
106 def create_tub(self):
108 def init_introducer_client(self):
110 def setup_logging(self):
112 def startService(self):
113 service.MultiService.startService(self)
114 def stopService(self):
115 service.MultiService.stopService(self)
116 def when_tub_ready(self):
117 raise RuntimeError("NoNetworkClient has no Tub")
118 def init_control(self):
120 def init_helper(self):
122 def init_key_gen(self):
124 def init_storage(self):
126 def init_stub_client(self):
129 def get_servers(self, service_name):
132 def get_permuted_peers(self, service_name, key):
133 return sorted(self._servers, key=lambda x: sha.new(key+x[0]).digest())
136 class NoNetworkGrid(service.MultiService):
137 def __init__(self, basedir, num_clients=1, num_servers=10,
138 client_config_hooks={}):
139 service.MultiService.__init__(self)
140 self.basedir = basedir
141 fileutil.make_dirs(basedir)
143 self.servers_by_number = {}
144 self.servers_by_id = {}
147 for i in range(num_servers):
148 serverid = hashutil.tagged_hash("serverid", str(i))[:20]
149 serverdir = os.path.join(basedir, "servers",
150 idlib.shortnodeid_b2a(serverid))
151 fileutil.make_dirs(serverdir)
152 ss = StorageServer(serverdir)
153 self.add_server(i, serverid, ss)
155 for i in range(num_clients):
156 clientid = hashutil.tagged_hash("clientid", str(i))[:20]
157 clientdir = os.path.join(basedir, "clients",
158 idlib.shortnodeid_b2a(clientid))
159 fileutil.make_dirs(clientdir)
160 f = open(os.path.join(clientdir, "tahoe.cfg"), "w")
162 f.write("nickname = client-%d\n" % i)
163 f.write("web.port = tcp:0:interface=127.0.0.1\n")
164 f.write("[storage]\n")
165 f.write("enabled = false\n")
168 if i in client_config_hooks:
169 # this hook can either modify tahoe.cfg, or return an
170 # entirely new Client instance
171 c = client_config_hooks[i](clientdir)
173 c = NoNetworkClient(clientdir)
175 c.short_nodeid = b32encode(clientid).lower()[:8]
176 c._servers = self.all_servers # can be updated later
177 c.setServiceParent(self)
178 self.clients.append(c)
180 def add_server(self, i, serverid, ss):
181 # TODO: ss.setServiceParent(self), but first remove the goofy
182 # self.parent.nodeid from Storage.startService . At the moment,
183 # Storage doesn't really need to be startService'd, but it will in
185 ss.setNodeID(serverid)
186 self.servers_by_number[i] = ss
187 self.servers_by_id[serverid] = wrap(ss, "storage")
188 self.all_servers = frozenset(self.servers_by_id.items())
189 for c in self.clients:
190 c._servers = self.all_servers
194 self.s = service.MultiService()
195 self.s.startService()
198 return self.s.stopService()
200 def set_up_grid(self, client_config_hooks={}):
201 # self.basedir must be set
202 self.g = NoNetworkGrid(self.basedir,
203 client_config_hooks=client_config_hooks)
204 self.g.setServiceParent(self.s)
206 def get_clientdir(self, i=0):
207 return self.g.clients[i].basedir
209 def get_serverdir(self, i):
210 return self.g.servers_by_number[i].storedir
212 def iterate_servers(self):
213 for i in sorted(self.g.servers_by_number.keys()):
214 ss = self.g.servers_by_number[i]
215 yield (i, ss, ss.storedir)