2006-11-30 22:14:47 +00:00
|
|
|
|
2007-07-04 00:27:07 +00:00
|
|
|
import os, sha, stat, time, re
|
2007-06-07 22:32:21 +00:00
|
|
|
from foolscap import Referenceable, SturdyRef
|
2006-12-02 02:17:50 +00:00
|
|
|
from zope.interface import implements
|
2007-06-28 00:11:06 +00:00
|
|
|
from allmydata.interfaces import RIClient
|
|
|
|
from allmydata import node
|
2006-11-30 23:23:39 +00:00
|
|
|
|
2007-06-29 01:01:00 +00:00
|
|
|
from twisted.internet import reactor
|
2007-05-25 00:34:42 +00:00
|
|
|
from twisted.application.internet import TimerService
|
2007-07-04 00:27:07 +00:00
|
|
|
from twisted.python import log
|
2006-11-30 22:14:47 +00:00
|
|
|
|
2007-04-26 19:01:25 +00:00
|
|
|
import allmydata
|
2007-07-14 00:25:45 +00:00
|
|
|
from allmydata.storage import StorageServer
|
2006-12-03 01:27:18 +00:00
|
|
|
from allmydata.upload import Uploader
|
2006-12-03 10:01:43 +00:00
|
|
|
from allmydata.download import Downloader
|
2007-10-15 23:16:39 +00:00
|
|
|
from allmydata.checker import Checker
|
2008-01-10 03:25:05 +00:00
|
|
|
from allmydata.offloaded import Helper
|
2007-03-08 02:16:06 +00:00
|
|
|
from allmydata.control import ControlServer
|
2007-03-27 23:12:11 +00:00
|
|
|
from allmydata.introducer import IntroducerClient
|
2008-01-04 00:02:05 +00:00
|
|
|
from allmydata.util import hashutil, idlib, testutil
|
2007-12-03 21:52:42 +00:00
|
|
|
from allmydata.filenode import FileNode
|
2007-12-04 18:45:20 +00:00
|
|
|
from allmydata.dirnode import NewDirectoryNode
|
2007-11-09 09:54:51 +00:00
|
|
|
from allmydata.mutable import MutableFileNode
|
2007-12-03 21:52:42 +00:00
|
|
|
from allmydata.interfaces import IURI, INewDirectoryURI, \
|
|
|
|
IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI
|
2007-11-09 09:54:51 +00:00
|
|
|
|
2007-09-20 22:33:58 +00:00
|
|
|
class Client(node.Node, Referenceable, testutil.PollMixin):
|
2006-12-02 02:17:50 +00:00
|
|
|
implements(RIClient)
|
2006-12-03 01:27:18 +00:00
|
|
|
PORTNUMFILE = "client.port"
|
2006-12-01 03:14:23 +00:00
|
|
|
STOREDIR = 'storage'
|
2006-12-03 01:27:18 +00:00
|
|
|
NODETYPE = "client"
|
2007-05-25 00:34:42 +00:00
|
|
|
SUICIDE_PREVENTION_HOTLINE_FILE = "suicide_prevention_hotline"
|
2006-11-30 22:27:06 +00:00
|
|
|
|
2007-04-26 19:01:25 +00:00
|
|
|
# we're pretty narrow-minded right now
|
|
|
|
OLDEST_SUPPORTED_VERSION = allmydata.__version__
|
|
|
|
|
2006-12-03 01:27:18 +00:00
|
|
|
def __init__(self, basedir="."):
|
|
|
|
node.Node.__init__(self, basedir)
|
2007-08-11 21:52:37 +00:00
|
|
|
self.logSource="Client"
|
2007-05-22 21:08:30 +00:00
|
|
|
self.my_furl = None
|
2007-03-23 23:20:26 +00:00
|
|
|
self.introducer_client = None
|
2007-12-18 01:34:11 +00:00
|
|
|
self.init_lease_secret()
|
2007-07-04 00:27:07 +00:00
|
|
|
self.init_storage()
|
2007-08-10 01:30:24 +00:00
|
|
|
self.init_options()
|
2008-01-09 04:18:54 +00:00
|
|
|
helper_furl = self.get_config("helper.furl")
|
|
|
|
self.add_service(Uploader(helper_furl))
|
2006-12-03 10:01:43 +00:00
|
|
|
self.add_service(Downloader())
|
2007-10-15 23:16:39 +00:00
|
|
|
self.add_service(Checker())
|
2008-01-10 03:25:05 +00:00
|
|
|
# ControlServer and Helper are attached after Tub startup
|
2007-08-28 01:58:39 +00:00
|
|
|
|
|
|
|
self.introducer_furl = self.get_config("introducer.furl", required=True)
|
2007-03-27 23:12:11 +00:00
|
|
|
|
2007-05-25 00:34:42 +00:00
|
|
|
hotline_file = os.path.join(self.basedir,
|
|
|
|
self.SUICIDE_PREVENTION_HOTLINE_FILE)
|
|
|
|
if os.path.exists(hotline_file):
|
2007-09-19 20:56:00 +00:00
|
|
|
age = time.time() - os.stat(hotline_file)[stat.ST_MTIME]
|
|
|
|
self.log("hotline file noticed (%ds old), starting timer" % age)
|
2007-05-30 00:39:39 +00:00
|
|
|
hotline = TimerService(1.0, self._check_hotline, hotline_file)
|
2007-05-25 00:34:42 +00:00
|
|
|
hotline.setServiceParent(self)
|
|
|
|
|
2007-12-03 21:52:42 +00:00
|
|
|
webport = self.get_config("webport")
|
|
|
|
if webport:
|
|
|
|
self.init_web(webport) # strports string
|
|
|
|
|
2007-12-18 01:34:11 +00:00
|
|
|
def init_lease_secret(self):
|
2007-08-28 02:30:26 +00:00
|
|
|
def make_secret():
|
2007-12-18 01:34:11 +00:00
|
|
|
return idlib.b2a(os.urandom(hashutil.CRYPTO_VAL_SIZE)) + "\n"
|
2007-12-17 23:39:54 +00:00
|
|
|
secret_s = self.get_or_create_private_config("secret", make_secret)
|
2007-12-18 01:34:11 +00:00
|
|
|
self._lease_secret = idlib.a2b(secret_s)
|
2007-08-28 02:30:26 +00:00
|
|
|
|
2007-07-04 00:27:07 +00:00
|
|
|
def init_storage(self):
|
|
|
|
storedir = os.path.join(self.basedir, self.STOREDIR)
|
|
|
|
sizelimit = None
|
2007-08-22 17:29:57 +00:00
|
|
|
|
2007-08-28 01:58:39 +00:00
|
|
|
data = self.get_config("sizelimit")
|
|
|
|
if data:
|
2007-07-04 00:27:07 +00:00
|
|
|
m = re.match(r"^(\d+)([kKmMgG]?[bB]?)$", data)
|
|
|
|
if not m:
|
|
|
|
log.msg("SIZELIMIT_FILE contains unparseable value %s" % data)
|
|
|
|
else:
|
|
|
|
number, suffix = m.groups()
|
|
|
|
suffix = suffix.upper()
|
|
|
|
if suffix.endswith("B"):
|
|
|
|
suffix = suffix[:-1]
|
|
|
|
multiplier = {"": 1,
|
|
|
|
"K": 1000,
|
|
|
|
"M": 1000 * 1000,
|
|
|
|
"G": 1000 * 1000 * 1000,
|
|
|
|
}[suffix]
|
|
|
|
sizelimit = int(number) * multiplier
|
2007-08-28 01:58:39 +00:00
|
|
|
no_storage = self.get_config("debug_no_storage") is not None
|
2007-07-17 01:07:03 +00:00
|
|
|
self.add_service(StorageServer(storedir, sizelimit, no_storage))
|
2007-07-04 00:27:07 +00:00
|
|
|
|
2007-08-10 01:30:24 +00:00
|
|
|
def init_options(self):
|
|
|
|
self.push_to_ourselves = None
|
2007-08-28 01:58:39 +00:00
|
|
|
if self.get_config("push_to_ourselves") is not None:
|
2007-08-10 01:30:24 +00:00
|
|
|
self.push_to_ourselves = True
|
|
|
|
|
2007-08-22 21:54:34 +00:00
|
|
|
def init_web(self, webport):
|
2007-12-03 21:52:42 +00:00
|
|
|
self.log("init_web(webport=%s)", args=(webport,))
|
|
|
|
|
2007-09-04 23:33:06 +00:00
|
|
|
from allmydata.webish import WebishServer
|
2008-01-08 01:04:56 +00:00
|
|
|
nodeurl_path = os.path.join(self.basedir, "node.url")
|
|
|
|
ws = WebishServer(webport, nodeurl_path)
|
2007-08-28 01:58:39 +00:00
|
|
|
if self.get_config("webport_allow_localfile") is not None:
|
|
|
|
ws.allow_local_access(True)
|
2007-08-22 21:54:34 +00:00
|
|
|
self.add_service(ws)
|
|
|
|
|
2007-05-25 00:34:42 +00:00
|
|
|
def _check_hotline(self, hotline_file):
|
|
|
|
if os.path.exists(hotline_file):
|
|
|
|
mtime = os.stat(hotline_file)[stat.ST_MTIME]
|
2007-09-26 02:22:33 +00:00
|
|
|
if mtime > time.time() - 20.0:
|
2007-05-25 00:34:42 +00:00
|
|
|
return
|
2007-09-19 20:56:00 +00:00
|
|
|
else:
|
|
|
|
self.log("hotline file too old, shutting down")
|
|
|
|
else:
|
|
|
|
self.log("hotline file missing, shutting down")
|
2007-05-25 00:34:42 +00:00
|
|
|
reactor.stop()
|
|
|
|
|
2006-12-03 02:37:31 +00:00
|
|
|
def tub_ready(self):
|
2007-03-27 23:12:11 +00:00
|
|
|
self.log("tub_ready")
|
2007-11-02 00:27:12 +00:00
|
|
|
node.Node.tub_ready(self)
|
2007-06-07 22:32:21 +00:00
|
|
|
|
2007-08-28 02:07:12 +00:00
|
|
|
# we use separate get_config/write_config here because we want to
|
|
|
|
# update the connection hints each time.
|
2007-06-07 22:32:21 +00:00
|
|
|
my_old_name = None
|
2007-08-28 01:58:39 +00:00
|
|
|
my_old_furl = self.get_config("myself.furl")
|
|
|
|
if my_old_furl is not None:
|
2007-06-07 22:32:21 +00:00
|
|
|
sturdy = SturdyRef(my_old_furl)
|
|
|
|
my_old_name = sturdy.name
|
|
|
|
|
|
|
|
self.my_furl = self.tub.registerReference(self, my_old_name)
|
2007-08-28 01:58:39 +00:00
|
|
|
self.write_config("myself.furl", self.my_furl + "\n")
|
2007-03-27 23:12:11 +00:00
|
|
|
|
2007-05-22 21:08:30 +00:00
|
|
|
ic = IntroducerClient(self.tub, self.introducer_furl, self.my_furl)
|
2007-03-27 23:12:11 +00:00
|
|
|
self.introducer_client = ic
|
|
|
|
ic.setServiceParent(self)
|
|
|
|
|
2007-03-23 23:20:26 +00:00
|
|
|
self.register_control()
|
2008-01-10 03:25:05 +00:00
|
|
|
self.register_helper()
|
2007-03-27 23:12:11 +00:00
|
|
|
|
2007-03-23 23:20:26 +00:00
|
|
|
def register_control(self):
|
|
|
|
c = ControlServer()
|
|
|
|
c.setServiceParent(self)
|
|
|
|
control_url = self.tub.registerReference(c)
|
2007-12-17 23:39:54 +00:00
|
|
|
self.write_private_config("control.furl", control_url + "\n")
|
2007-06-15 08:33:24 +00:00
|
|
|
|
2008-01-10 03:25:05 +00:00
|
|
|
def register_helper(self):
|
|
|
|
run_helper = self.get_config("run_helper")
|
|
|
|
if not run_helper:
|
|
|
|
return
|
|
|
|
h = Helper(os.path.join(self.basedir, "helper"))
|
|
|
|
h.setServiceParent(self)
|
|
|
|
# TODO: this is confusing. BASEDIR/private/helper.furl is created by
|
|
|
|
# the helper. BASEDIR/helper.furl is consumed by the client who wants
|
|
|
|
# to use the helper. I like having the filename be the same, since
|
|
|
|
# that makes 'cp' work smoothly, but the difference between config
|
|
|
|
# inputs and generated outputs is hard to see.
|
2008-01-11 12:42:55 +00:00
|
|
|
helper_furlfile = os.path.join(self.basedir, "private", "helper.furl")
|
|
|
|
self.tub.registerReference(h, furlFile=helper_furlfile)
|
2008-01-10 03:25:05 +00:00
|
|
|
|
2007-04-26 19:01:25 +00:00
|
|
|
def remote_get_versions(self):
|
|
|
|
return str(allmydata.__version__), str(self.OLDEST_SUPPORTED_VERSION)
|
|
|
|
|
2006-12-01 01:09:57 +00:00
|
|
|
def remote_get_service(self, name):
|
2007-06-15 03:14:34 +00:00
|
|
|
if name in ("storageserver",):
|
|
|
|
return self.getServiceNamed(name)
|
|
|
|
raise RuntimeError("I am unwilling to give you service %s" % name)
|
2006-12-01 01:09:57 +00:00
|
|
|
|
2007-09-26 19:20:48 +00:00
|
|
|
def remote_get_nodeid(self):
|
|
|
|
return self.nodeid
|
2006-12-01 03:18:51 +00:00
|
|
|
|
2007-03-27 23:12:11 +00:00
|
|
|
def get_all_peerids(self):
|
2007-06-10 04:03:57 +00:00
|
|
|
if not self.introducer_client:
|
|
|
|
return []
|
2007-07-17 02:47:42 +00:00
|
|
|
return self.introducer_client.get_all_peerids()
|
2007-03-27 23:12:11 +00:00
|
|
|
|
2007-08-10 01:30:24 +00:00
|
|
|
def get_permuted_peers(self, key, include_myself=True):
|
2007-03-30 03:19:52 +00:00
|
|
|
"""
|
|
|
|
@return: list of (permuted-peerid, peerid, connection,)
|
|
|
|
"""
|
2006-12-01 03:18:51 +00:00
|
|
|
results = []
|
2007-07-17 02:47:42 +00:00
|
|
|
for peerid, connection in self.introducer_client.get_all_peers():
|
2007-03-30 03:19:52 +00:00
|
|
|
assert isinstance(peerid, str)
|
2007-08-12 23:24:51 +00:00
|
|
|
if not include_myself and peerid == self.nodeid:
|
2007-08-10 01:30:24 +00:00
|
|
|
self.log("get_permuted_peers: removing myself from the list")
|
|
|
|
continue
|
2007-12-04 01:10:01 +00:00
|
|
|
permuted = sha.new(key + peerid).digest()
|
2007-03-30 03:19:52 +00:00
|
|
|
results.append((permuted, peerid, connection))
|
2006-12-01 03:18:51 +00:00
|
|
|
results.sort()
|
2007-03-30 03:19:52 +00:00
|
|
|
return results
|
2007-06-10 04:03:57 +00:00
|
|
|
|
2007-08-10 01:30:24 +00:00
|
|
|
def get_push_to_ourselves(self):
|
|
|
|
return self.push_to_ourselves
|
|
|
|
|
2007-07-12 22:33:30 +00:00
|
|
|
def get_encoding_parameters(self):
|
|
|
|
if not self.introducer_client:
|
|
|
|
return None
|
|
|
|
return self.introducer_client.encoding_parameters
|
|
|
|
|
2007-06-10 04:03:57 +00:00
|
|
|
def connected_to_introducer(self):
|
|
|
|
if self.introducer_client:
|
|
|
|
return self.introducer_client.connected_to_introducer()
|
|
|
|
return False
|
2007-08-28 02:00:18 +00:00
|
|
|
|
|
|
|
def get_renewal_secret(self):
|
2007-12-18 01:34:11 +00:00
|
|
|
return hashutil.my_renewal_secret_hash(self._lease_secret)
|
2007-08-28 02:30:26 +00:00
|
|
|
|
2007-08-28 02:00:18 +00:00
|
|
|
def get_cancel_secret(self):
|
2007-12-18 01:34:11 +00:00
|
|
|
return hashutil.my_cancel_secret_hash(self._lease_secret)
|
2007-09-20 22:33:58 +00:00
|
|
|
|
|
|
|
def debug_wait_for_client_connections(self, num_clients):
|
|
|
|
"""Return a Deferred that fires (with None) when we have connections
|
|
|
|
to the given number of peers. Useful for tests that set up a
|
|
|
|
temporary test network and need to know when it is safe to proceed
|
|
|
|
with an upload or download."""
|
|
|
|
def _check():
|
|
|
|
current_clients = list(self.get_all_peerids())
|
|
|
|
return len(current_clients) >= num_clients
|
|
|
|
d = self.poll(_check, 0.5)
|
|
|
|
d.addCallback(lambda res: None)
|
|
|
|
return d
|
|
|
|
|
2007-11-01 22:15:29 +00:00
|
|
|
|
2007-11-09 09:54:51 +00:00
|
|
|
# these four methods are the primitives for creating filenodes and
|
|
|
|
# dirnodes. The first takes a URI and produces a filenode or (new-style)
|
|
|
|
# dirnode. The other three create brand-new filenodes/dirnodes.
|
|
|
|
|
|
|
|
def create_node_from_uri(self, u):
|
2007-12-03 21:52:42 +00:00
|
|
|
# this returns synchronously.
|
2007-11-09 09:54:51 +00:00
|
|
|
u = IURI(u)
|
2007-12-03 21:52:42 +00:00
|
|
|
if IReadonlyNewDirectoryURI.providedBy(u):
|
|
|
|
# new-style read-only dirnodes
|
|
|
|
return NewDirectoryNode(self).init_from_uri(u)
|
2007-11-09 09:54:51 +00:00
|
|
|
if INewDirectoryURI.providedBy(u):
|
|
|
|
# new-style dirnodes
|
|
|
|
return NewDirectoryNode(self).init_from_uri(u)
|
|
|
|
if IFileURI.providedBy(u):
|
|
|
|
# CHK
|
|
|
|
return FileNode(u, self)
|
2007-12-03 21:52:42 +00:00
|
|
|
assert IMutableFileURI.providedBy(u), u
|
2007-11-09 09:54:51 +00:00
|
|
|
return MutableFileNode(self).init_from_uri(u)
|
|
|
|
|
2008-01-14 21:55:59 +00:00
|
|
|
def create_empty_dirnode(self):
|
2007-11-01 22:15:29 +00:00
|
|
|
n = NewDirectoryNode(self)
|
2008-01-14 21:55:59 +00:00
|
|
|
d = n.create()
|
2007-11-01 22:15:29 +00:00
|
|
|
d.addCallback(lambda res: n)
|
|
|
|
return d
|
|
|
|
|
2008-01-14 21:55:59 +00:00
|
|
|
def create_mutable_file(self, contents=""):
|
2007-11-01 22:15:29 +00:00
|
|
|
n = MutableFileNode(self)
|
2008-01-14 21:55:59 +00:00
|
|
|
d = n.create(contents)
|
2007-11-01 22:15:29 +00:00
|
|
|
d.addCallback(lambda res: n)
|
|
|
|
return d
|
|
|
|
|
2008-01-15 04:17:32 +00:00
|
|
|
def upload(self, uploadable, options={}):
|
2007-11-09 09:54:51 +00:00
|
|
|
uploader = self.getServiceNamed("uploader")
|
2008-01-15 04:17:32 +00:00
|
|
|
return uploader.upload(uploadable, options)
|
2007-11-01 22:15:29 +00:00
|
|
|
|