Пример #1
0
def ssh_hosts():
    paths = []
    configs = {}

    try:
        import pwd
        for pw in pwd.getpwall():
            config_path = path.join(pw.pw_dir, '.ssh', 'config')
            if path.isfile(config_path):
                paths.append((pw.pw_name, config_path))

    except ImportError:
        config_path = path.expanduser(path.join('~', '.ssh', 'config'))
        if path.isfile(config_path):
            import getpass
            paths = [(getpass.getuser(), config_path)]

    for user, config_path in paths:
        ssh_config = SSHConfig()
        try:
            with open(config_path) as config:
                ssh_config.parse(config)

        except OSError:
            continue

        configs[user] = {
            host: ssh_config.lookup(host)
            for host in ssh_config.get_hostnames()
        }

    configs.update(ssh_putty_hosts())
    return configs
Пример #2
0
 def hosts(self):
     """Retrieve host instances from ssh config."""
     config = SSHConfig()
     with Path(self.user_config).open() as fileobj:
         config.parse(fileobj)
     hostnames = [i for i in config.get_hostnames() if self.is_endhost(i)]
     return [self.transform_to_instances(i, config.lookup(i)) for i in hostnames]
Пример #3
0
 def hosts(self):
     """Retrieve host instances from ssh config."""
     config = SSHConfig()
     with Path(self.user_config).open() as fileobj:
         config.parse(fileobj)
     hostnames = [i for i in config.get_hostnames() if self.is_endhost(i)]
     return [self.transform_to_instances(i, config.lookup(i))
             for i in hostnames]
Пример #4
0
def _annotate_hosts_with_ssh_config_info(path):
    from os.path import expanduser
    from paramiko.config import SSHConfig

    def hostinfo(host, config):
        hive = config.lookup(host)
        # if 'hostname' in hive:
           # host = hive['hostname']
        if 'user' in hive:
            host = '%s@%s' % (hive['user'], host)
        if 'port' in hive:
            host = '%s:%s' % (host, hive['port'])
        #print 'hive',hive
        #print 'host',host
        return host

    try:
        config_file = file(expanduser(path))
    except IOError:
        pass
    else:
        config = SSHConfig()
        config.parse(config_file)

        # add hosts from ssh config to env.host & sort + unique
        env.hosts.extend([h for h in config.get_hostnames() if len(h) > 1])
        env.hosts = sorted(set(env.hosts))

        keys = [config.lookup(host).get('identityfile', None) for host in env.hosts]
        # flatten
        keys = [item for sublist in keys  if sublist is not None for item in sublist]
        env.key_filename = [expanduser(key) for key in keys if key is not None]
        env.hosts = [hostinfo(host, config) for host in env.hosts]

        for role, rolehosts in env.roledefs.items():
            env.roledefs[role] = [hostinfo(host, config) for host in rolehosts]
Пример #5
0
class WorkerInterface(object):
    """An interface to perform tasks on the DAQ worker nodes.

    This is used perform tasks on the computers running the data router and the ECC server. This includes things
    like cleaning up the data files at the end of each run.

    The connection is made using SSH, and the SSH config file at ``config_path`` is honored in making the connection.
    Additionally, the server *must* accept connections authenticated using a public key, and this public key must
    be available in your ``.ssh`` directory.

    Parameters
    ----------
    hostname : str
        The hostname to connect to.
    port : int, optional
        The port that the SSH server is listening on. The default is 22.
    username : str, optional
        The username to use. If it isn't provided, a username will be read from the SSH config file. If no username
        is listed there, the name of the user running the code will be used.
    config_path : str, optional
        The path to the SSH config file. The default is ``~/.ssh/config``.

    """
    def __init__(self, hostname, port=22, username=None, config_path=None):
        self.hostname = hostname
        self.client = SSHClient()

        self.client.load_system_host_keys()
        self.client.set_missing_host_key_policy(AutoAddPolicy())

        if config_path is None:
            config_path = os.path.join(os.path.expanduser('~'), '.ssh', 'config')
        self.config = SSHConfig()
        with open(config_path) as config_file:
            self.config.parse(config_file)

        if hostname in self.config.get_hostnames():
            host_cfg = self.config.lookup(hostname)
            full_hostname = host_cfg.get('hostname', hostname)
            if username is None:
                username = host_cfg.get('user', None)  # If none, it will try the user running the server.
        else:
            full_hostname = hostname

        self.client.connect(full_hostname, port, username=username)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.client.close()

    def find_data_router(self):
        """Find the working directory of the data router process.

        The directory is found using ``lsof``, which must be available on the remote system.

        Returns
        -------
        str
            The directory where the data router is running, and therefore writing data.

        Raises
        ------
        RuntimeError
            If ``lsof`` finds something strange instead of a process called ``dataRouter``.

        """
        stdin, stdout, stderr = self.client.exec_command('lsof -a -d cwd -c dataRouter -Fcn')
        for line in stdout:
            if line[0] == 'c' and not re.match('cdataRouter', line):
                raise RuntimeError("lsof found {} instead of dataRouter".format(line[1:].strip()))
            elif line[0] == 'n':
                return line[1:].strip()
        else:
            raise RuntimeError("lsof didn't find dataRouter")

    def get_graw_list(self):
        """Get a list of GRAW files in the data router's working directory.

        Returns
        -------
        list[str]
            A list of the full paths to the GRAW files.

        """
        data_dir = self.find_data_router()

        with self.client.open_sftp() as sftp:
            full_list = sftp.listdir(data_dir)

        graws = filter(lambda s: re.match(r'.*\.graw$', s), full_list)
        full_graw_paths = (os.path.join(data_dir, filename) for filename in graws)

        return list(full_graw_paths)

    def working_dir_is_clean(self):
        """Check if there are GRAW files in the data router's working directory.

        Returns
        -------
        bool
            True if there are files in the working directory, False otherwise.
        """
        return len(self.get_graw_list()) == 0

    def _check_process_status(self, process_name):
        """Checks if the given process is running.

        Parameters
        ----------
        process_name : str
            The name of the process to look for

        Returns
        -------
        bool
            True if the process is running.
        """

        _, stdout, _ = self.client.exec_command('ps -e')

        for line in stdout:
            if re.search(process_name, line):
                return True
        else:
            return False

    def check_ecc_server_status(self):
        """Checks if the ECC server is running.

        Returns
        -------
        bool
            True if ``getEccSoapServer`` is running.
        """
        return self._check_process_status(r'getEccSoapServer')

    def check_data_router_status(self):
        """Checks if the data router is running.

        Returns
        -------
        bool
            True if ``dataRouter`` is running.
        """
        return self._check_process_status(r'dataRouter')

    def build_run_dir_path(self, experiment_name, run_number):
        """Get the path to the directory for a given run.

        This returns a path of the format ``experiment_name/run_name`` under the directory where the data router
        is running. The ``run_name``, in this case, has the format ``run_NNNN``.

        Parameters
        ----------
        experiment_name : str
            The name of the experiment directory.
        run_number : int
            The run number.

        Returns
        -------
        run_dir : str
            The full path to the run directory. *This should be escaped before passing it to a shell command.*

        """
        pwd = self.find_data_router()
        run_name = 'run_{:04d}'.format(run_number)  # run_0001, run_0002, etc.
        run_dir = os.path.join(pwd, experiment_name, run_name)
        return run_dir

    def organize_files(self, experiment_name, run_number):
        """Organize the GRAW files at the end of a run.

        This will get a list of the files written in the working directory of the data router and move them to
        the directory ``./experiment_name/run_name``, which will be created if necessary. For example, if
        the ``experiment_name`` is "test" and the ``run_number`` is 4, the files will be placed in ``./test/run_0004``.

        Parameters
        ----------
        experiment_name : str
            A name for the experiment directory.
        run_number : int
            The current run number.

        """
        run_dir = self.build_run_dir_path(experiment_name, run_number)

        graws = self.get_graw_list()

        with self.client.open_sftp() as sftp:
            mkdir_recursive(sftp, run_dir)
            for srcpath in graws:
                _, srcfile = os.path.split(srcpath)
                destpath = os.path.join(run_dir, srcfile)
                sftp.rename(srcpath, destpath)

    def backup_config_files(self, experiment_name, run_number, file_paths, backup_root):
        """Makes a copy of the config files on the remote computer.

        The files are copied to a subdirectory ``experiment_name/run_name`` of ``backup_root``.

        Parameters
        ----------
        experiment_name : str
            The name of the experiment.
        run_number : int
            The run number.
        file_paths : iterable of str
            The *full* paths to the config files.
        backup_root : str
            Where the backups should be written.

        """
        run_name = 'run_{:04d}'.format(run_number)
        backup_dest = os.path.join(backup_root, experiment_name, run_name)

        with self.client.open_sftp() as sftp:
            mkdir_recursive(sftp, backup_dest)
            for source_path in file_paths:
                dest_path = os.path.join(backup_dest, os.path.basename(source_path))
                with sftp.open(source_path, 'r') as src, sftp.open(dest_path, 'w') as dest:
                    buffer = src.read()
                    dest.write(buffer)

    def tail_file(self, path, num_lines=50):
        """Retrieve the tail of a text file on the remote host.

        Note that this assumes the file is ASCII-encoded plain text.

        Parameters
        ----------
        path : str
            Path to the file.
        num_lines : int
            The number of lines to include.

        Returns
        -------
        str
            The tail of the file's contents.
        """
        # Based on https://gist.github.com/volker48/3437288
        with self.client.open_sftp() as sftp:
            with sftp.open(path, 'r') as f:
                f.seek(-1, SFTPFile.SEEK_END)
                lines = 0
                while lines < num_lines and f.tell() > 0:
                    char = f.read(1)
                    if char == b'\n':
                        lines += 1
                        if lines == num_lines:
                            break
                    f.seek(-2, SFTPFile.SEEK_CUR)

                return f.read().decode('ascii')