Browse Source

Add support for per-relay network directories

Steven Engler 2 years ago
parent
commit
66e1adc6f3
1 changed files with 121 additions and 82 deletions
  1. 121 82
      lib/chutney/TorNet.py

+ 121 - 82
lib/chutney/TorNet.py

@@ -664,7 +664,7 @@ class LocalNodeBuilder(NodeBuilder):
     def _makeDataDir(self):
         """Create the data directory (with keys subdirectory) for this node.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         make_datadir_subdirectory(datadir, "keys")
 
     def _makeHiddenServiceDir(self):
@@ -674,13 +674,13 @@ class LocalNodeBuilder(NodeBuilder):
           key. It is combined with the 'dir' data directory key to yield the
           path to the hidden service directory.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         make_datadir_subdirectory(datadir, self._env['hs_directory'])
 
     def _genAuthorityKey(self):
         """Generate an authority identity and signing key for this authority,
            if they do not already exist."""
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         tor_gencert = self._env['tor_gencert']
         lifetime = self._env['auth_cert_lifetime']
         idfile = os.path.join(datadir, 'keys', "authority_identity_key")
@@ -711,7 +711,7 @@ class LocalNodeBuilder(NodeBuilder):
         """Generate an identity key for this router, unless we already have,
            and set up the 'fingerprint' entry in the Environ.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         tor = self._env['tor']
         torrc = self._getTorrcFname()
         cmdline = [
@@ -737,7 +737,7 @@ class LocalNodeBuilder(NodeBuilder):
         if not self._env['authority']:
             return ""
 
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         certfile = os.path.join(datadir, 'keys', "authority_certificate")
         v3id = None
         with open(certfile, 'r') as f:
@@ -812,56 +812,58 @@ class LocalNodeBuilder(NodeBuilder):
         return bridgelines
 
 
-def scp_file(abs_filepath, host):
-    if not os.path.isabs(abs_filepath) or abs_filepath[0:5] != '/tmp/':
-        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
-        # myself in the foot
-        raise Exception('SCP path must be absolute and must be in /tmp')
-    assert(':' not in host)
-    assert(':' not in abs_filepath)
-    remote_filepath = os.path.dirname(abs_filepath)
-    cmd = ['scp', abs_filepath, ':'.join([host, remote_filepath])]
-    print('Transferring file: {}'.format(cmd))
-    subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-
-def scp_dir(abs_dirpath, host):
-    if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
-        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
-        # myself in the foot
-        raise Exception('SCP path must be absolute and must be in /tmp')
-    assert(':' not in host)
-    assert(':' not in abs_dirpath)
-    remote_dirpath = os.path.dirname(abs_dirpath)
+#def scp_file(abs_filepath, host):
+#    if not os.path.isabs(abs_filepath) or abs_filepath[0:5] != '/tmp/':
+#        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
+#        # myself in the foot
+#        raise Exception('SCP path must be absolute and must be in /tmp')
+#    assert ':' not in host
+#    assert ':' not in abs_filepath
+#    remote_filepath = os.path.dirname(abs_filepath)
+#    cmd = ['scp', abs_filepath, ':'.join([host, remote_filepath])]
+#    print('Transferring file: {}'.format(cmd))
+#    subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
+def scp_dir(abs_dirpath, abs_remote_dirpath, host):
+    #if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
+    #    # this check for '/tmp' is in no way secure, but helps prevent me from shooting
+    #    # myself in the foot
+    #    raise Exception('SCP path must be absolute and must be in /tmp')
+    assert ':' not in host
+    assert ':' not in abs_dirpath
+    assert ':' not in abs_remote_dirpath
+    remote_dirpath = os.path.dirname(abs_remote_dirpath)
     cmd = ['scp', '-r', abs_dirpath, ':'.join([host, remote_dirpath])]
     print('Transferring files: {}'.format(cmd))
     subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 
-def scp_dir_backwards(abs_dirpath, host):
-    if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
-        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
-        # myself in the foot
-        raise Exception('SCP path must be absolute and must be in /tmp')
-    assert(':' not in host)
-    assert(':' not in abs_dirpath)
-    remote_dirpath = os.path.dirname(abs_dirpath)
-    cmd = ['scp', '-r', ':'.join([host, abs_dirpath]), remote_dirpath]
+def scp_dir_backwards(abs_remote_dirpath, abs_dirpath, host):
+    #if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
+    #    # this check for '/tmp' is in no way secure, but helps prevent me from shooting
+    #    # myself in the foot
+    #    raise Exception('SCP path must be absolute and must be in /tmp')
+    assert ':' not in host
+    assert ':' not in abs_dirpath
+    assert ':' not in abs_remote_dirpath
+    local_dirpath = os.path.dirname(abs_dirpath)
+    cmd = ['scp', '-r', ':'.join([host, abs_remote_dirpath]), local_dirpath]
     print('Transferring files backwards: {}'.format(cmd))
     subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 
 def ssh_mkdir_p(abs_dirpath, remote_hostname):
-    if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
-        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
-        # myself in the foot
-        raise Exception('Path must be absolute and must be in /tmp')
+    #if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
+    #    # this check for '/tmp' is in no way secure, but helps prevent me from shooting
+    #    # myself in the foot
+    #    raise Exception('Path must be absolute and must be in /tmp')
     cmd = ['ssh', remote_hostname, 'mkdir', '-p', abs_dirpath]
     print('Making directory: {}'.format(cmd))
     subprocess.check_output(cmd, stderr=subprocess.STDOUT)
 
 def ssh_rm_if_exists(abs_dirpath, remote_hostname):
-    if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
-        # this check for '/tmp' is in no way secure, but helps prevent me from shooting
-        # myself in the foot
-        raise Exception('Path must be absolute and must be in /tmp')
+    #if not os.path.isabs(abs_dirpath) or abs_dirpath[0:5] != '/tmp/':
+    #    # this check for '/tmp' is in no way secure, but helps prevent me from shooting
+    #    # myself in the foot
+    #    raise Exception('Path must be absolute and must be in /tmp')
     cmd = ['ssh', remote_hostname, 'rm', '-f', abs_dirpath]
     print('Removing: {}'.format(cmd))
     subprocess.check_output(cmd, stderr=subprocess.STDOUT)
@@ -1056,8 +1058,8 @@ class RemoteNodeBuilder(NodeBuilder):
     def postConfig(self, net):
         """Called on each nodes after all nodes configure."""
         # self.net.addNode(self)
-        scp_dir(os.path.abspath(self._env['dir']), self._env['remote_hostname'])
-        shutil.rmtree(self._env['dir'])
+        scp_dir(os.path.abspath(self._env['local_dir']), os.path.abspath(self._env['remote_dir']), self._env['remote_hostname'])
+        shutil.rmtree(self._env['local_dir'])
 
     def isSupported(self, net):
         """Return true if this node appears to have everything it needs;
@@ -1077,7 +1079,7 @@ class RemoteNodeBuilder(NodeBuilder):
     def _makeDataDir(self):
         """Create the data directory (with keys subdirectory) for this node.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         make_datadir_subdirectory(datadir, "keys")
 
     def _makeHiddenServiceDir(self):
@@ -1087,13 +1089,13 @@ class RemoteNodeBuilder(NodeBuilder):
           key. It is combined with the 'dir' data directory key to yield the
           path to the hidden service directory.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         make_datadir_subdirectory(datadir, self._env['hs_directory'])
 
     def _genAuthorityKey(self):
         """Generate an authority identity and signing key for this authority,
            if they do not already exist."""
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         tor_gencert = self._env['tor_gencert']
         lifetime = self._env['auth_cert_lifetime']
         idfile = os.path.join(datadir, 'keys', "authority_identity_key")
@@ -1124,7 +1126,7 @@ class RemoteNodeBuilder(NodeBuilder):
         """Generate an identity key for this router, unless we already have,
            and set up the 'fingerprint' entry in the Environ.
         """
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         tor = self._env['tor']
         torrc = self._getTorrcFname()
         cmdline = [
@@ -1150,7 +1152,7 @@ class RemoteNodeBuilder(NodeBuilder):
         if not self._env['authority']:
             return ""
 
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         certfile = os.path.join(datadir, 'keys', "authority_certificate")
         v3id = None
         with open(certfile, 'r') as f:
@@ -1240,7 +1242,7 @@ class LocalNodeController(NodeController):
            the pid of the running process, or None if there is no pid in the
            file.
         """
-        pidfile = os.path.join(self._env['dir'], 'pid')
+        pidfile = os.path.join(self._env['local_dir'], 'pid')
         if not os.path.exists(pidfile):
             return None
 
@@ -1281,7 +1283,7 @@ class LocalNodeController(NodeController):
         # XXX Split this into "check" and "print" parts.
         pid = self.getPid()
         nick = self._env['nick']
-        datadir = self._env['dir']
+        datadir = self._env['local_dir']
         corefile = "core.%s" % pid
         tor_version = get_tor_version(self._env['tor'])
         if self.isRunning(pid):
@@ -1343,7 +1345,7 @@ class LocalNodeController(NodeController):
         if self._env['google_cpu_profiler'] is True:
             if add_environ_vars is None:
                 add_environ_vars = {}
-            add_environ_vars['CPUPROFILE'] = os.path.join(self._env['dir'], 'cpu-prof.out')
+            add_environ_vars['CPUPROFILE'] = os.path.join(self._env['local_dir'], 'cpu-prof.out')
         #
         cmdline.extend([
             tor_path,
@@ -1431,13 +1433,14 @@ class LocalNodeController(NodeController):
                     self._env['poll_launch_time_default']
             return False
 
-    def getLogfile(self, info=False):
+    def getLogfile(self):
         """Return the expected path to the logfile for this instance."""
-        datadir = self._env['dir']
-        if info:
-            logname = "info.log"
-        else:
-            logname = "notice.log"
+        datadir = self._env['local_dir']
+        logfile_priority = ['notice', 'info', 'debug']
+        for p in logfile_priority:
+            if p in self._env['log_files']:
+                logname = p + '.log'
+                break
         return os.path.join(datadir, logname)
 
     def getLastBootstrapStatus(self):
@@ -1482,8 +1485,8 @@ class RemoteNodeController(NodeController):
            the pid of the running process, or None if there is no pid in the
            file.
         """
-        pidfile = os.path.join(self._env['dir'], 'pid')
         if self._env['remote_hostname'] is None:
+            pidfile = os.path.join(self._env['local_dir'], 'pid')
             if not os.path.exists(pidfile):
                 return None
             with open(pidfile, 'r') as f:
@@ -1492,6 +1495,7 @@ class RemoteNodeController(NodeController):
                 except ValueError:
                     return None
         else:
+            pidfile = os.path.join(self._env['remote_dir'], 'pid')
             pid = ssh_read_file(pidfile, self._env['remote_hostname'])
             if pid is None:
                 return None
@@ -1533,7 +1537,10 @@ class RemoteNodeController(NodeController):
         # XXX Split this into "check" and "print" parts.
         pid = self.getPid()
         nick = self._env['nick']
-        datadir = self._env['dir']
+        if self._env['remote_hostname'] is None:
+            datadir = self._env['local_dir']
+        else:
+            datadir = self._env['remote_dir']
         corefile = os.path.join(datadir, "core.%s" % pid)
         tor_version = get_tor_version(self._env['tor'], remote_hostname=self._env['remote_hostname'])
 
@@ -1586,7 +1593,10 @@ class RemoteNodeController(NodeController):
             print("{:12} is already running".format(self._env['nick']))
             return True
         tor_path = self._env['tor']
-        torrc = self._getTorrcFname()
+        if self._env['remote_hostname'] is None:
+            torrc = self._getTorrcFname()
+        else:
+            torrc = os.path.join(self._env['remote_dir'], 'torrc')
         #
         add_environ_vars = self._env['add_environ_vars']
         if add_environ_vars is not None:
@@ -1595,7 +1605,10 @@ class RemoteNodeController(NodeController):
         if self._env['google_cpu_profiler'] is True:
             if add_environ_vars is None:
                 add_environ_vars = {}
-            add_environ_vars['CPUPROFILE'] = os.path.join(self._env['dir'], 'cpu-prof.out')
+            if self._env['remote_hostname'] is None:
+                add_environ_vars['CPUPROFILE'] = os.path.join(self._env['local_dir'], 'cpu-prof.out')
+            else:
+                add_environ_vars['CPUPROFILE'] = os.path.join(self._env['remote_dir'], 'cpu-prof.out')
         #
         cmdline = []
         if self._env['remote_hostname'] is not None:
@@ -1712,13 +1725,17 @@ class RemoteNodeController(NodeController):
                     self._env['poll_launch_time_default']
             return False
 
-    def getLogfile(self, info=False):
+    def getLogfile(self):
         """Return the expected path to the logfile for this instance."""
-        datadir = self._env['dir']
-        if info:
-            logname = "info.log"
+        if self._env['remote_hostname'] is None:
+            datadir = self._env['local_dir']
         else:
-            logname = "notice.log"
+            datadir = self._env['remote_dir']
+        logfile_priority = ['notice', 'info', 'debug']
+        for p in logfile_priority:
+            if p in self._env['log_files']:
+                logname = p + '.log'
+                break
         return os.path.join(datadir, logname)
 
     def getLastBootstrapStatus(self):
@@ -1757,9 +1774,10 @@ class RemoteNodeController(NodeController):
 
     def getRemoteFiles(self):
         if self._env['remote_hostname'] is not None:
-            path = os.path.abspath(self._env['dir'])
-            ssh_rm_if_exists(os.path.join(path, 'control'), self._env['remote_hostname'])
-            scp_dir_backwards(path, self._env['remote_hostname'])
+            local_path = os.path.abspath(self._env['local_dir'])
+            remote_path = os.path.abspath(self._env['remote_dir'])
+            ssh_rm_if_exists(os.path.join(remote_path, 'control'), self._env['remote_hostname'])
+            scp_dir_backwards(remote_path, local_path, self._env['remote_hostname'])
 
 # XXX: document these options
 DEFAULTS = {
@@ -1786,7 +1804,7 @@ DEFAULTS = {
     'ipv6_addr': os.environ.get('CHUTNEY_LISTEN_ADDRESS_V6', None),
     'dirserver_flags': 'no-v2',
     'chutney_dir': get_absolute_chutney_path(),
-    'torrc_fname': '${dir}/torrc',
+    'torrc_fname': '${local_dir}/torrc',
     'orport_base': 5000,
     'dirport_base': 7000,
     'controlport_base': 8000,
@@ -1846,6 +1864,7 @@ DEFAULTS = {
     'log_files': ['notice', 'info', 'debug'],
     'google_cpu_profiler': False,
     'remote_hostname': None,
+    'remote_net_dir': None,
     'num_additional_eventloops': None,
     'log_throughput': False,
 }
@@ -1925,12 +1944,25 @@ class TorEnviron(chutney.Templating.Environ):
     def _get_ptport(self, my):
         return my['ptport_base'] + my['nodenum']
 
-    def _get_dir(self, my):
+    def _get_local_dir(self, my):
         return os.path.abspath(os.path.join(my['net_base_dir'],
                                             "nodes",
                                             "%03d%s" % (
                                                 my['nodenum'], my['tag'])))
 
+    def _get_remote_dir(self, my):
+        if my['remote_net_dir'] is None or my['remote_hostname'] is None:
+            return None
+        return os.path.abspath(os.path.join(my['remote_net_dir'],
+                                            "nodes",
+                                            "%03d%s" % (
+                                                my['nodenum'], my['tag'])))
+
+    def _get_dir(self, my):
+        if self['remote_dir'] is not None:
+            return self['remote_dir']
+        return self['local_dir']
+
     def _get_nick(self, my):
         return "%s%03d%s" % (my['nick_base'], my['nodenum'], my['tag'])
 
@@ -2104,10 +2136,12 @@ class Network(object):
         print("NOTE: creating %r, linking to %r" % (newnodesdir, nodeslink))
         # this gets created with mode 0700, that's probably ok
         mkdir_p(newnodesdir)
-        remote_hostnames = list(set([x._env['remote_hostname'] for x in self._nodes if x._env['remote_hostname'] is not None]))
-        if len(remote_hostnames) != 0:
-            for x in remote_hostnames:
-                ssh_mkdir_p(newnodesdir, x)
+        remotes = list(set([(x._env['remote_hostname'],x._env['remote_net_dir']) for x in self._nodes if x._env['remote_hostname'] is not None]))
+        if len(remotes) != 0:
+            for (hostname, remote_net_dir) in remotes:
+                assert remote_net_dir is not None
+                remote_newnodesdir = os.path.join(remote_net_dir, os.path.basename(newnodesdir))
+                ssh_mkdir_p(remote_newnodesdir, hostname)
         try:
             os.unlink(nodeslink)
         except OSError as e:
@@ -2116,13 +2150,18 @@ class Network(object):
                 pass
             else:
                 raise
-        if len(remote_hostnames) != 0:
-            for x in remote_hostnames:
-                ssh_rm_if_exists(nodeslink, x)
+        if len(remotes) != 0:
+            for (hostname, remote_net_dir) in remotes:
+                assert remote_net_dir is not None
+                remote_nodeslink = os.path.join(remote_net_dir, 'nodes')
+                ssh_rm_if_exists(remote_nodeslink, hostname)
         os.symlink(newnodesdir, nodeslink)
-        if len(remote_hostnames) != 0:
-            for x in remote_hostnames:
-                ssh_symlink(newnodesdir, nodeslink, x)
+        if len(remotes) != 0:
+            for (hostname, remote_net_dir) in remotes:
+                assert remote_net_dir is not None
+                remote_newnodesdir = os.path.join(remote_net_dir, os.path.basename(newnodesdir))
+                remote_nodeslink = os.path.join(remote_net_dir, 'nodes')
+                ssh_symlink(remote_newnodesdir, remote_nodeslink, hostname)
 
     def _checkConfig(self):
         for n in self._nodes: