Source code for util.netutil

# Copyright (c) 2015 Intracom S.A. Telecom Solutions. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License v1.0 which accompanies this distribution,
# and is available at http://www.eclipse.org/legal/epl-v10.html

""" General network utilities """

import gevent
import gevent.queue
import logging
import os
import paramiko
import stat
import time
import errno


[docs]def copy_dir_local_to_remote(ip, ssh_port, username, password, local_path, remote_path): """Copy a local directory on a remote machine. :param connection: A named tuple with all the connection information. \ It must have the following elements: \ ['name', 'ip', 'ssh_port', 'username', 'password'] \ :param local_path: directory path from local machine to be copied, full \ location required :param remote_path: directory path on the remote node, full location \ required :type connection: namedtuple<> :type local_path: str :type remote_path: str """ if local_path.endswith('/'): local_path = local_path[:-1] (sftp, transport_layer) = \ ssh_connection_open(ip, int(ssh_port), username, password) os.chdir(os.path.split(local_path)[0]) parent = os.path.split(local_path)[1] for walker in os.walk(parent): try: folder_to_make = os.path.join(remote_path, walker[0]) sftp.mkdir(folder_to_make) except: pass for curr_file in walker[2]: local_file = os.path.join(walker[0], curr_file) remote_file = os.path.join(remote_path, walker[0], curr_file) sftp.put(local_file, remote_file) ssh_connection_close(sftp, transport_layer)
[docs]def copy_dir_remote_to_local(ip, ssh_port, username, password, remote_path, local_path): """ Copy recursively remote directories (Copies all files and other \ sub-directories). :param connection: named tuple with connection information: \ ['name', 'ip', 'ssh_port', 'username', 'password'] :param remote_path: full remote path we want to copy :param local_path: full local path we want to copy :type connection: namedtuple<> :type remote_path: str :type local_path: str """ (sftp, transport_layer) = ssh_connection_open(ip, int(ssh_port), username, password) if not isdir(remote_path, sftp): ssh_connection_close(sftp, transport_layer) raise(IOError('[copy_dir_remote_to_local] The remote path {0} does ' 'not exist.'.format(remote_path))) if not os.path.exists(local_path): os.makedirs(local_path) files = sftp.listdir(path=remote_path) for file_item in files: if file_item is not None: remote_filepath = os.path.join(remote_path, file_item) if isdir(remote_filepath, sftp): if not os.path.exists(os.path.join(local_path, file_item)): os.makedirs(os.path.join(local_path, file_item)) copy_dir_remote_to_local(ip, ssh_port, username, password, remote_filepath, os.path.join(local_path, file_item)) else: sftp.get(remote_filepath, os.path.join(local_path, file_item)) ssh_connection_close(sftp, transport_layer)
[docs]def create_dir_remote(ip, ssh_port, username, password, remote_path): """Opens an ssh connection to a remote machine and creates a new directory. :param connection: named tuple with connection information: \ ['name', 'ip', 'ssh_port', 'username', 'password'] :param remote_path: :type connection: namedtuple<> :type remote_path: str """ (sftp, transport_layer) = \ ssh_connection_open(ip, int(ssh_port), username, password) try: # Test if remote_path exists sftp.chdir(remote_path) except IOError: # Create remote_path sftp.mkdir(remote_path) sftp.chdir(remote_path) ssh_connection_close(sftp, transport_layer)
[docs]def isdir(path, sftp): """Checks if a given remote path is a directory :param path: A string with the full path we want to check :param sftp: An sftp connection object (paramiko) :returns: True if the given path is a directory false otherwise. :rtype: bool :type path: str :type sftp: paramiko.SFTPClient """ try: return stat.S_ISDIR(sftp.stat(path).st_mode) except IOError: return False
[docs]def make_remote_file_executable(ip, port, username, password, remote_file): """Makes the remote file executable. :param connection: named tuple with connection information: \ ['name', 'ip', 'ssh_port', 'username', 'password'] :param remote_file: remote file to make executable :type connection: namedtuple<> :type remote_file: str """ (sftp, transport_layer) = ssh_connection_open(ip, int(port), username, password) try: sftp.chmod(remote_file, stat.S_IEXEC | stat.S_IREAD | stat.S_IWRITE) except: raise(IOError('[make_remote_file_executable] Fail to make remote file ' '{0} on {1} node executable.'.format(remote_file, ip))) ssh_connection_close(sftp, transport_layer)
[docs]def remove_remote_directory(ip, ssh_port, username, password, path): """ Removes recursively remote directories (removes all files and other \ sub-directories). :param connection: named tuple with connection information: \ ['name', 'ip', 'ssh_port', 'username', 'password'] :param path: A string with the full path we want to remove :type connection: namedtuple<> :type path: str """ (sftp, transport_layer) = \ ssh_connection_open(ip, int(ssh_port), username, password) files = sftp.listdir(path=path) for file_item in files: filepath = os.path.join(path, file_item) if isdir(filepath, sftp): remove_remote_directory(ip, ssh_port, username, password, filepath) else: sftp.remove(filepath) sftp.rmdir(path) ssh_connection_close(sftp, transport_layer)
[docs]def isfile(ip, port, username, password, file_list): """ Checks if all files in a given list exist. All files are located \ remotely :param ip: ip address of the remote host :param port: port number of the remote host :param username: username of the remote host :param password: password of the remote host :param file_list: list of filenames to be checked :returns: True if the given path is a directory false otherwise. :rtype: bool :type ip: str :type port: int :type username: str :type password: str :type file_list: list<str> """ sftp = ssh_connection_open(ip, int(port), username, password)[0] for filename in file_list: try: sftp.stat(filename) except IOError as e: if e.errno == errno.EEXIST: return False raise IOError('[utils.netutils.isfile] Other error number') else: return True
[docs]def ssh_connect_or_return(ip, ssh_port, username, password, maxretries): """ Opens a connection and returns a connection object. If it fails to open \ a connection after a specified number of tries, it returns -1. :param ip: controller IP address :param ssh_port: controller port :param username: username of the remote user :param password: password of the remote user :param maxretries: maximum number of times to connect :returns: an ssh connection handle or -1 on failure :rtype: paramiko.SSHClient (or -1 when failure) :type ip: str :type ssh_port: int :type username: str :type password: str :type maxretries: int """ retries = 1 while retries <= maxretries: logging.info( '[utils.netutils.ssh_connect_or_return] Trying to ' 'connect to {0}:{1} ({2}/{3})'. format(ip, ssh_port, retries, maxretries)) try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts"))) ssh.connect(hostname=ip, port=ssh_port, username=username, password=password) logging.info('[utils.netutils.ssh_connect_or_return] ' 'connected to {0} '.format(ip)) print('Connected OK') return ssh except paramiko.AuthenticationException: logging.error( '[utils.netutils.ssh_connect_or_return] authentication ' 'failed when connecting to {0}'. format(ip)) retries += 1 time.sleep(2) # If we exit while without ssh object been returned, then return -1 raise Exception('[netutil] could not connect to {0}.' ' Returning'.format(ip))
[docs]def ssh_connection_close(sftp, transport_layer): """ Closes an ssh connection with a remote node :param sftp: :param transport_layer: :type sftp: paramiko.SFTPClient :type transport_layer: paramiko.Transport """ try: sftp.close() transport_layer.close() except: pass
[docs]def ssh_connection_open(ip, ssh_port, username, password): """ Opens an ssh connection on a remote node :param ip: ip address of the remote host :param ssh_port: port number of the remote host :param username: username of the remote host :param password: password of the remote host :returns: sftp, transport_layer :rtype: tuple<paramiko.SFTPClient, paramiko.Transport> :type ip: str :type ssh_port: int :type username: str :type password: str """ try: transport_layer = paramiko.Transport((ip, ssh_port)) transport_layer.connect(username=username, password=password) sftp = paramiko.SFTPClient.from_transport(transport_layer) return (sftp, transport_layer) except: logging.error('[ssh_connection_open] error: check connection object')
[docs]def ssh_copy_file_to_target(ip, ssh_port, username, password, local_file, remote_file): """ Copies local file on a remote machine target. :param ip: ip address of the remote host :param ssh_port: port number of the remote host :param username: username of the remote host :param password: password of the remote host :param local_file: file from local machine to copy,full location required :param remote_file: remote destination, full location required \ i.e /tmp/foo.txt :type ip: str :type ssh_port: int :type username: str :type password: str :type local_file: str :type remote_file: str """ (sftp, transport_layer) = ssh_connection_open(ip, int(ssh_port), username, password) sftp.put(local_file, remote_file) ssh_connection_close(sftp, transport_layer)
[docs]def ssh_delete_file_if_exists(ip, ssh_port, username, password, remote_file): """ Deletes the file on a remote machine, if exists :param connection: named tuple with connection information: \ ['name', 'ip', 'ssh_port', 'username', 'password'] :param remote_file: remote file to remove, full path must be used. :type connection: collections.namedtuple :type remote_file: str """ (sftp, transport_layer) = \ ssh_connection_open(ip, int(ssh_port), username, password) try: sftp.remove(remote_file) logging.info('[delete_file_if_exists]: file {0} removed'. format(remote_file)) except IOError: logging.error( '[delete_file_if_exists] IOError: The given remote_file ' 'is not valid. Error message: {0}'.format(IOError.strerror)) except: logging.error( '[delete_file_if_exists] Error: unknown error occurred ' 'removing remote file.') ssh_connection_close(sftp, transport_layer)
[docs]def ssh_run_command(ssh_client, command_to_run, prefix='', lines_queue=None, print_flag=True, block_flag=True, getpty_flag=False): """ Runs the specified command on a remote machine :param ssh_client: SSH client provided by paramiko to run the command :param command_to_run: Command to execute :param prefix: prefix of log message :param lines_queue: Queue datastructure to buffer the result of execution :param print_flag: Flag that defines if the output of the command will be \ printed on screen :param block_flag: Defines if we block execution waiting for the running \ command to return its exit status :param getpty_flag: add a pseudo-terminal console (pty console) to the \ channel :returns: the exit code of the command to be executed remotely and the \ combined stdout - stderr of the executed command :rtype: tuple :type ssh_client: paramiko.SSHClient :type command_to_run: str :type prefix: str :type lines_queue: queue<str> :type print_flag: bool :type block_flag: bool :type getpty_flag: bool """ channel = ssh_client.get_transport().open_session() bufferSize = 8*1024 channel_timeout = None channel.setblocking(1) channel.set_combine_stderr(True) channel.settimeout(channel_timeout) if getpty_flag: channel.get_pty() channel.exec_command(command_to_run) if not block_flag: # Carefull!! Do not close channel here if it is closed the running # process will be terminated and we do not want this to happen in case # of non blocking execution. return (0, '') channel_output = '' while not channel.exit_status_ready(): data = '' data = channel.recv(bufferSize).decode('utf-8') while data is not '': channel_output += data if print_flag: logging.debug('{0} {1}'.format(prefix, data).strip()) if lines_queue is not None: for line in data.splitlines(): lines_queue.put(line) if type(lines_queue) is type(gevent.queue.Queue()): gevent.sleep(0.01) data = channel.recv(bufferSize).decode('utf-8') channel_exit_status = channel.recv_exit_status() channel.close() return (channel_exit_status, channel_output)