def test_key_material_argument(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa') with open(path, 'r') as fp: private_key = fp.read() conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 60, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_create_with_key_via_bastion(self): conn_params = {'hostname': 'dummy.host.org', 'bastion_host': 'bastion.host.org', 'username': '******', 'key_files': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_bastion_conn = {'username': '******', 'allow_agent': False, 'hostname': 'bastion.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22} mock.bastion_client.connect.assert_called_once_with(**expected_bastion_conn) expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22, 'sock': mock.bastion_socket} mock.client.connect.assert_called_once_with(**expected_conn)
def _connect(self, host, results, raise_on_any_error=False): (hostname, port) = self._get_host_port_info(host) extra = {'host': host, 'port': port, 'user': self._ssh_user} if self._ssh_password: extra['password'] = '******' elif self._ssh_key_file: extra['key_file_path'] = self._ssh_key_file else: extra['private_key'] = '<redacted>' LOG.debug('Connecting to host.', extra=extra) client = ParamikoSSHClient(hostname, username=self._ssh_user, password=self._ssh_password, bastion_host=self._bastion_host, key_files=self._ssh_key_file, key_material=self._ssh_key_material, passphrase=self._passphrase, port=port) try: client.connect() except Exception as ex: error = 'Failed connecting to host %s.' % hostname LOG.exception(error) if raise_on_any_error: raise error_dict = self._generate_error_result(exc=ex, message=error) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict else: self._successful_connects += 1 self._hosts_client[hostname] = client results[hostname] = {'message': 'Connected to host.'}
def run(self, hostname, port, username, password=None, keyfile=None, ssh_timeout=5, sleep_delay=20, retries=10): # Note: If neither password nor key file is provided, we try to use system user # key file if not password and not keyfile: keyfile = cfg.CONF.system_user.ssh_key_file self.logger.info( 'Neither "password" nor "keyfile" parameter provided, ' 'defaulting to using "%s" key file' % (keyfile) ) client = ParamikoSSHClient( hostname=hostname, port=port, username=username, password=password, key_files=keyfile, timeout=ssh_timeout ) for index in range(retries): attempt = index + 1 try: self.logger.debug("SSH connection attempt: %s" % (attempt)) client.connect() return True except Exception as e: self.logger.info("Attempt %s failed (%s), sleeping for %s seconds..." % (attempt, str(e), sleep_delay)) time.sleep(sleep_delay) raise Exception("Exceeded max retries (%s)" % (retries))
def test_create_with_key_via_bastion(self): conn_params = { 'hostname': 'dummy.host.org', 'bastion_host': 'bastion.host.org', 'username': '******', 'key_files': 'id_rsa' } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_bastion_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'bastion.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22 } mock.bastion_client.connect.assert_called_once_with( **expected_bastion_conn) expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22, 'sock': mock.bastion_socket } mock.client.connect.assert_called_once_with(**expected_conn)
def test_socket_closed(self): conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'timeout': '600' } ssh_client = ParamikoSSHClient(**conn_params) # Make sure .close() doesn't actually call anything real ssh_client.client = Mock() ssh_client.sftp_client = Mock() ssh_client.bastion_client = Mock() ssh_client.socket = Mock() ssh_client.bastion_socket = Mock() # Make sure we havent called any close methods at this point # TODO: Replace these with .assert_not_called() once it's Python 3.6+ only self.assertEqual(ssh_client.socket.close.call_count, 0) self.assertEqual(ssh_client.client.close.call_count, 0) self.assertEqual(ssh_client.sftp_client.close.call_count, 0) self.assertEqual(ssh_client.bastion_socket.close.call_count, 0) self.assertEqual(ssh_client.bastion_client.close.call_count, 0) # Call the function that has changed ssh_client.close() # TODO: Replace these with .assert_called_once() once it's Python 3.6+ only self.assertEqual(ssh_client.socket.close.call_count, 1) self.assertEqual(ssh_client.client.close.call_count, 1) self.assertEqual(ssh_client.sftp_client.close.call_count, 1) self.assertEqual(ssh_client.bastion_socket.close.call_count, 1) self.assertEqual(ssh_client.bastion_client.close.call_count, 1)
def test_key_with_passphrase_success(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa_passphrase") with open(path, "r") as fp: private_key = fp.read() # Key material provided conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": private_key, "passphrase": "testphrase", } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), "testphrase") expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "pkey": pkey, "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn) # Path to private key file provided conn_params = { "hostname": "dummy.host.org", "username": "******", "key_files": path, "passphrase": "testphrase", } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "key_filename": path, "password": "******", "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_with_passphrase_success(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') with open(path, 'r') as fp: private_key = fp.read() # Key material provided conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key, 'passphrase': 'testphrase' } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), 'testphrase') expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 30, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn) # Path to private key file provided conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_files': path, 'passphrase': 'testphrase' } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': path, 'password': '******', 'timeout': 30, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_create_with_password(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'password': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_deprecated_key_argument(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def _connect(self, host, results, raise_on_any_error=False): (hostname, port) = self._get_host_port_info(host) extra = {"host": host, "port": port, "user": self._ssh_user} if self._ssh_password: extra["password"] = "******" elif self._ssh_key_file: extra["key_file_path"] = self._ssh_key_file else: extra["private_key"] = "<redacted>" LOG.debug("Connecting to host.", extra=extra) client = ParamikoSSHClient( hostname=hostname, port=port, username=self._ssh_user, password=self._ssh_password, bastion_host=self._bastion_host, key_files=self._ssh_key_file, key_material=self._ssh_key_material, passphrase=self._passphrase, handle_stdout_line_func=self._handle_stdout_line_func, handle_stderr_line_func=self._handle_stderr_line_func, ) try: client.connect() except SSHException as ex: LOG.exception(ex) if raise_on_any_error: raise error_dict = self._generate_error_result( exc=ex, message="Connection error." ) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict except Exception as ex: error = "Failed connecting to host %s." % hostname LOG.exception(error) if raise_on_any_error: raise error_dict = self._generate_error_result(exc=ex, message=error) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict else: self._successful_connects += 1 self._hosts_client[hostname] = client results[hostname] = {"message": "Connected to host."}
def test_create_with_key_via_bastion(self): conn_params = { "hostname": "dummy.host.org", "bastion_host": "bastion.host.org", "username": "******", "key_files": "id_rsa", } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_bastion_conn = { "username": "******", "allow_agent": False, "hostname": "bastion.host.org", "look_for_keys": False, "key_filename": "id_rsa", "timeout": 30, "port": 22, } mock.bastion_client.connect.assert_called_once_with( **expected_bastion_conn) expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "key_filename": "id_rsa", "timeout": 30, "port": 22, "sock": mock.bastion_socket, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_set_proxycommand(self, mock_ProxyCommand): """ Loads proxy commands from ssh config file """ ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_ssh_config') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') cfg.CONF.set_override(name='use_ssh_config', override=True, group='ssh_runner') conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() mock_ProxyCommand.assert_called_once_with('ssh -q -W dummy.host.org:22 dummy_bastion')
def test_key_material_argument(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa") with open(path, "r") as fp: private_key = fp.read() conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": private_key, } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "pkey": pkey, "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_passphrase_not_provided_for_encrypted_key_file(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': path} mock = ParamikoSSHClient(**conn_params) self.assertRaises(paramiko.ssh_exception.PasswordRequiredException, mock.connect)
def test_passphrase_no_key_provided(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'passphrase': 'testphrase'} expected_msg = 'passphrase should accompany private key material' client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_create_without_credentials_use_default_key(self): # No credentials are provided by default stanley ssh key exists so it should use that cfg.CONF.set_override(name='ssh_key_file', override='stanley_rsa', group='system_user') conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'hostname': 'dummy.host.org', 'key_filename': 'stanley_rsa', 'allow_agent': False, 'look_for_keys': False, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_consume_stderr(self): # Test utf-8 decoding of ``stderr`` still works fine when reading CHUNK_SIZE splits a # multi-byte utf-8 character in the middle. We should wait to collect all bytes # and finally decode. conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.CHUNK_SIZE = 1 chan = Mock() chan.recv_stderr_ready.side_effect = [True, True, True, True, False] chan.recv_stderr.side_effect = [b'\xF0', b'\x90', b'\x8D', b'\x88'] try: b'\xF0'.decode('utf-8') self.fail('Test fixture is not right.') except UnicodeDecodeError: pass stderr = mock._consume_stderr(chan) self.assertEqual(u'\U00010348', stderr.getvalue())
def test_consume_stderr(self): # Test utf-8 decoding of ``stderr`` still works fine when reading CHUNK_SIZE splits a # multi-byte utf-8 character in the middle. We should wait to collect all bytes # and finally decode. conn_params = {"hostname": "dummy.host.org", "username": "******"} mock = ParamikoSSHClient(**conn_params) mock.CHUNK_SIZE = 1 chan = Mock() chan.recv_stderr_ready.side_effect = [True, True, True, True, False] chan.recv_stderr.side_effect = [b"\xF0", b"\x90", b"\x8D", b"\x88"] try: b"\xF0".decode("utf-8") self.fail("Test fixture is not right.") except UnicodeDecodeError: pass stderr = mock._consume_stderr(chan) self.assertEqual("\U00010348", stderr.getvalue())
def test_consume_stderr(self): # Test utf-8 decoding of ``stderr`` still works fine when reading CHUNK_SIZE splits a # multi-byte utf-8 character in the middle. We should wait to collect all bytes # and finally decode. conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.CHUNK_SIZE = 1 chan = Mock() chan.recv_stderr_ready.side_effect = [True, True, True, True, False] chan.recv_stderr.side_effect = ['\xF0', '\x90', '\x8D', '\x88'] try: '\xF0'.decode('utf-8') self.fail('Test fixture is not right.') except UnicodeDecodeError: pass stderr = mock._consume_stderr(chan) self.assertEqual(u'\U00010348', stderr.getvalue())
def test_key_material_argument_invalid_key(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_material': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) expected_msg = 'Invalid or unsupported key type' self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_create_with_password(self): conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******' } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'password': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'timeout': 60, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_deprecated_key_argument(self): conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_files': 'id_rsa' } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_passphrase_no_key_provided(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "passphrase": "testphrase", } expected_msg = "passphrase should accompany private key material" client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_create_without_credentials(self): """ Initialize object with no credentials. Just to have better coverage, initialize the object without 'password' neither 'key'. """ conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'hostname': 'dummy.host.org', 'allow_agent': True, 'look_for_keys': True, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def run( self, hostname, port, username, password=None, keyfile=None, ssh_timeout=5, sleep_delay=20, retries=10, ): # Note: If neither password nor key file is provided, we try to use system user # key file if not password and not keyfile: keyfile = cfg.CONF.system_user.ssh_key_file self.logger.info( 'Neither "password" nor "keyfile" parameter provided, ' 'defaulting to using "%s" key file' % (keyfile)) client = ParamikoSSHClient( hostname=hostname, port=port, username=username, password=password, key_files=keyfile, timeout=ssh_timeout, ) for index in range(retries): attempt = index + 1 try: self.logger.debug("SSH connection attempt: %s" % (attempt)) client.connect() return True except Exception as e: self.logger.info( "Attempt %s failed (%s), sleeping for %s seconds..." % (attempt, six.text_type(e), sleep_delay)) time.sleep(sleep_delay) raise Exception("Exceeded max retries (%s)" % (retries))
def test_passphrase_not_provided_for_encrypted_key_file(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa_passphrase") conn_params = { "hostname": "dummy.host.org", "username": "******", "key_files": path, } mock = ParamikoSSHClient(**conn_params) self.assertRaises(paramiko.ssh_exception.PasswordRequiredException, mock.connect)
def test_create_without_credentials_use_default_key(self): # No credentials are provided by default stanley ssh key exists so it should use that cfg.CONF.set_override(name='ssh_key_file', override='stanley_rsa', group='system_user') conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'hostname': 'dummy.host.org', 'key_filename': 'stanley_rsa', 'allow_agent': False, 'look_for_keys': False, 'timeout': 60, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_create_without_credentials(self): """ Initialize object with no credentials. Just to have better coverage, initialize the object without 'password' neither 'key'. """ conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'hostname': 'dummy.host.org', 'allow_agent': True, 'look_for_keys': True, 'timeout': 60, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_material_argument_invalid_key(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": "id_rsa", } mock = ParamikoSSHClient(**conn_params) expected_msg = "Invalid or unsupported key type" self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_key_files_and_key_material_arguments_are_mutual_exclusive(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': 'id_rsa', 'key_material': 'key'} expected_msg = ('key_files and key_material arguments are mutually exclusive. ' 'Supply only one.') client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_set_proxycommand(self, mock_ProxyCommand): """ Loads proxy commands from ssh config file """ ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_ssh_config') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') cfg.CONF.set_override(name='use_ssh_config', override=True, group='ssh_runner') conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******' } mock = ParamikoSSHClient(**conn_params) mock.connect() mock_ProxyCommand.assert_called_once_with( 'ssh -q -W dummy.host.org:22 dummy_bastion')
def setUp(self): """ Creates the object patching the actual connection. """ cfg.CONF.set_override(name='ssh_key_file', override=None, group='system_user') cfg.CONF.set_override(name='use_ssh_config', override=False, group='ssh_runner') conn_params = {'hostname': 'dummy.host.org', 'port': 8822, 'username': '******', 'key_files': '~/.ssh/ubuntu_ssh', 'timeout': '600'} self.ssh_cli = ParamikoSSHClient(**conn_params)
def test_key_material_argument(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa') with open(path, 'r') as fp: private_key = fp.read() conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key} mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_socket_not_closed_if_none(self): conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'timeout': '600' } ssh_client = ParamikoSSHClient(**conn_params) # Make sure .close() doesn't actually call anything real ssh_client.client = None ssh_client.sftp_client = None ssh_client.bastion_client = None ssh_client.socket = None ssh_client.bastion_socket = None # Call the function, this should not throw an exception ssh_client.close()
def test_socket_not_closed_if_none(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "password": "******", "timeout": "600", } ssh_client = ParamikoSSHClient(**conn_params) # Make sure .close() doesn't actually call anything real ssh_client.client = None ssh_client.sftp_client = None ssh_client.bastion_client = None ssh_client.socket = None ssh_client.bastion_socket = None # Call the function, this should not throw an exception ssh_client.close()
def test_create_without_credentials(self): """ Initialize object with no credentials. Just to have better coverage, initialize the object without 'password' neither 'key'. Now that we only reconcile the final parameters at the last moment when we explicitly try to connect, all the credentials should be set to None. """ conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) self.assertEqual(mock.password, None) self.assertEqual(mock.key_material, None) self.assertEqual(mock.key_files, None)
def test_key_material_contains_path_not_contents(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******'} key_materials = ['~/.ssh/id_rsa', '/tmp/id_rsa', 'C:\\id_rsa'] expected_msg = ( '"private_key" parameter needs to contain private key data / content and ' 'not a path') for key_material in key_materials: conn_params = conn_params.copy() conn_params['key_material'] = key_material mock = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_key_material_contains_path_not_contents(self): conn_params = {"hostname": "dummy.host.org", "username": "******"} key_materials = ["~/.ssh/id_rsa", "/tmp/id_rsa", "C:\\id_rsa"] expected_msg = ( '"private_key" parameter needs to contain private key data / content and ' "not a path") for key_material in key_materials: conn_params = conn_params.copy() conn_params["key_material"] = key_material mock = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_key_with_passphrase_success(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') with open(path, 'r') as fp: private_key = fp.read() # Key material provided conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key, 'passphrase': 'testphrase'} mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), 'testphrase') expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn) # Path to private key file provided conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': path, 'passphrase': 'testphrase'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': path, 'password': '******', 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_use_ssh_config_port_value_provided_in_the_config(self, mock_sshclient): cfg.CONF.set_override(name='use_ssh_config', override=True, group='ssh_runner') ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'empty_config') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') # 1. Default port is used (not explicitly provided) mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'timeout': '600'} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 22) mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'port': None, 'timeout': '600'} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 22) # 2. Default port is used (explicitly provided) mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'port': DEFAULT_SSH_PORT, 'timeout': '600'} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], DEFAULT_SSH_PORT) self.assertEqual(call_kwargs['port'], 22) # 3. Custom port is used (explicitly provided) mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'port': 5555, 'timeout': '600'} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 5555) # 4. Custom port is specified in the ssh config (it has precedence over default port) ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'ssh_config_custom_port') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******'} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 6677) mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'port': DEFAULT_SSH_PORT} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 6677) # 5. Custom port is specified in ssh config, but one is also provided via runner parameter # (runner parameter one has precedence) ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'ssh_config_custom_port') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') mock_client = mock.Mock() mock_sshclient.return_value = mock_client conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'port': 9999} ssh_client = ParamikoSSHClient(**conn_params) ssh_client.connect() call_kwargs = mock_client.connect.call_args[1] self.assertEqual(call_kwargs['port'], 9999)
def test_sftp_connection_is_only_established_if_required(self): # Verify that SFTP connection is lazily established only if and when needed. conn_params = {'hostname': 'dummy.host.org', 'username': '******'} # Verify sftp connection and client hasn't been established yet client = ParamikoSSHClient(**conn_params) client.connect() self.assertTrue(client.sftp_client is None) # run method doesn't require sftp access so it shouldn't establish connection client = ParamikoSSHClient(**conn_params) client.connect() client.run(cmd='whoami') self.assertTrue(client.sftp_client is None) # Methods bellow require SFTP access so they should cause SFTP connection to be established # put client = ParamikoSSHClient(**conn_params) client.connect() path = '/root/random_script.sh' client.put(path, path, mirror_local_mode=False) self.assertTrue(client.sftp_client is not None) # exists client = ParamikoSSHClient(**conn_params) client.connect() client.exists('/root/somepath.txt') self.assertTrue(client.sftp_client is not None) # mkdir client = ParamikoSSHClient(**conn_params) client.connect() client.mkdir('/root/somedirfoo') self.assertTrue(client.sftp_client is not None) # Verify close doesn't throw if SFTP connection is not established client = ParamikoSSHClient(**conn_params) client.connect() self.assertTrue(client.sftp_client is None) client.close() # Verify SFTP connection is closed if it's opened client = ParamikoSSHClient(**conn_params) client.connect() client.mkdir('/root/somedirfoo') self.assertTrue(client.sftp_client is not None) client.close() self.assertEqual(client.sftp_client.close.call_count, 1)
def test_handle_stdout_and_stderr_line_funcs(self): mock_handle_stdout_line_func = mock.Mock() mock_handle_stderr_line_func = mock.Mock() conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'handle_stdout_line_func': mock_handle_stdout_line_func, 'handle_stderr_line_func': mock_handle_stderr_line_func } client = ParamikoSSHClient(**conn_params) client.connect() mock_get_transport = mock.Mock() mock_chan = mock.Mock() client.client.get_transport = mock.Mock() client.client.get_transport.return_value = mock_get_transport mock_get_transport.open_session.return_value = mock_chan def mock_recv_ready_factory(chan): chan.recv_counter = 0 def mock_recv_ready(): chan.recv_counter += 1 if chan.recv_counter < 2: return True return False return mock_recv_ready def mock_recv_stderr_ready_factory(chan): chan.recv_stderr_counter = 0 def mock_recv_stderr_ready(): chan.recv_stderr_counter += 1 if chan.recv_stderr_counter < 2: return True return False return mock_recv_stderr_ready mock_chan.recv_ready = mock_recv_ready_factory(mock_chan) mock_chan.recv_stderr_ready = mock_recv_stderr_ready_factory(mock_chan) mock_chan.recv.return_value = 'stdout 1\nstdout 2\nstdout 3' mock_chan.recv_stderr.return_value = 'stderr 1\nstderr 2\nstderr 3' # call_line_handler_func is False so handler functions shouldn't be called client.run(cmd='echo "test"', call_line_handler_func=False) self.assertEqual(mock_handle_stdout_line_func.call_count, 0) self.assertEqual(mock_handle_stderr_line_func.call_count, 0) # Reset counters mock_chan.recv_counter = 0 mock_chan.recv_stderr_counter = 0 # call_line_handler_func is True so handler functions should be called for each line client.run(cmd='echo "test"', call_line_handler_func=True) self.assertEqual(mock_handle_stdout_line_func.call_count, 3) self.assertEqual(mock_handle_stdout_line_func.call_args_list[0][1]['line'], 'stdout 1\n') self.assertEqual(mock_handle_stdout_line_func.call_args_list[1][1]['line'], 'stdout 2\n') self.assertEqual(mock_handle_stdout_line_func.call_args_list[2][1]['line'], 'stdout 3\n') self.assertEqual(mock_handle_stderr_line_func.call_count, 3) self.assertEqual(mock_handle_stdout_line_func.call_args_list[0][1]['line'], 'stdout 1\n') self.assertEqual(mock_handle_stdout_line_func.call_args_list[1][1]['line'], 'stdout 2\n') self.assertEqual(mock_handle_stdout_line_func.call_args_list[2][1]['line'], 'stdout 3\n')