From 3cb2275954b0855cf7caff3a8899172401ad34ce Mon Sep 17 00:00:00 2001 From: vgorin Date: Thu, 3 Dec 2015 15:53:14 +0300 Subject: [PATCH] Introduction of new method of SSH connection - Create Singletone to use it as metaclass in ssh_manager. Singleton provides us posibility of having only one instance of class. - Create SSHManagaer, wich will manage SSH connections by itself. Almost SSHClient methods was duplicated in SSHManager. Now it used so: SSHManager().method(node_ip, *parameters) We will not get back SSHClient, we will just say 'Do IT on NODE with this PARAM' e.g SSHManager().execute_on_remote('127.0.0.1', 'bash_command') - Use ssh_manager in cli_cluster_deletion test Change-Id: I307d7d71e814b67d20cc0b4648cf6a7dac4a7829 Closes-Bug: #1503210 --- doc/helpers.rst | 5 + fuelweb_test/helpers/decorators.py | 29 ++-- fuelweb_test/helpers/fuel_actions.py | 172 ++++++++++++++------- fuelweb_test/helpers/regenerate_repo.py | 57 ++++--- fuelweb_test/helpers/ssh_manager.py | 197 ++++++++++++++++++++++++ fuelweb_test/helpers/utils.py | 29 ++++ fuelweb_test/models/environment.py | 39 ++--- fuelweb_test/models/fuel_web_client.py | 8 +- fuelweb_test/tests/base_test_case.py | 4 +- fuelweb_test/tests/test_cli.py | 29 ++-- 10 files changed, 436 insertions(+), 133 deletions(-) create mode 100644 fuelweb_test/helpers/ssh_manager.py diff --git a/doc/helpers.rst b/doc/helpers.rst index b8e7b2c70..f41ef2949 100644 --- a/doc/helpers.rst +++ b/doc/helpers.rst @@ -117,3 +117,8 @@ Utils ----- .. automodule:: fuelweb_test.helpers.utils :members: + +SSH Manager +----------- +.. automodule:: fuelweb_test.helpers.ssh_manager + :members: diff --git a/fuelweb_test/helpers/decorators.py b/fuelweb_test/helpers/decorators.py index d8da790a0..f59bb414d 100644 --- a/fuelweb_test/helpers/decorators.py +++ b/fuelweb_test/helpers/decorators.py @@ -375,23 +375,22 @@ def retry(count=3, delay=30): def custom_repo(func): @functools.wraps(func) def wrapper(*args, **kwargs): - with args[0].environment.d_env.get_admin_remote() as remote: - custom_pkgs = CustomRepo(remote) - try: - if settings.CUSTOM_PKGS_MIRROR: - custom_pkgs.prepare_repository() + custom_pkgs = CustomRepo() + try: + if settings.CUSTOM_PKGS_MIRROR: + custom_pkgs.prepare_repository() - except Exception: - logger.error("Unable to get custom packages from {0}\n{1}" - .format(settings.CUSTOM_PKGS_MIRROR, - traceback.format_exc())) - raise + except Exception: + logger.error("Unable to get custom packages from {0}\n{1}" + .format(settings.CUSTOM_PKGS_MIRROR, + traceback.format_exc())) + raise - try: - return func(*args, **kwargs) - except Exception: - custom_pkgs.check_puppet_logs() - raise + try: + return func(*args, **kwargs) + except Exception: + custom_pkgs.check_puppet_logs() + raise return wrapper diff --git a/fuelweb_test/helpers/fuel_actions.py b/fuelweb_test/helpers/fuel_actions.py index 5ea52a618..690dbe2f4 100644 --- a/fuelweb_test/helpers/fuel_actions.py +++ b/fuelweb_test/helpers/fuel_actions.py @@ -31,7 +31,7 @@ from fuelweb_test.helpers.decorators import retry from fuelweb_test.helpers.regenerate_repo import regenerate_centos_repo from fuelweb_test.helpers.regenerate_repo import regenerate_ubuntu_repo from fuelweb_test.helpers import replace_repos -from fuelweb_test.helpers.utils import cond_upload +from fuelweb_test.helpers.ssh_manager import SSHManager from fuelweb_test.settings import MASTER_IS_CENTOS7 from fuelweb_test.settings import FUEL_PLUGIN_BUILDER_REPO from fuelweb_test.settings import FUEL_USE_LOCAL_NTPD @@ -43,8 +43,9 @@ from fuelweb_test.settings import NESSUS_IMAGE_PATH class BaseActions(object): """BaseActions.""" # TODO documentation - def __init__(self, admin_remote): - self.admin_remote = admin_remote + def __init__(self): + self.ssh_manager = SSHManager() + self.admin_ip = self.ssh_manager.admin_ip self.container = None def __repr__(self): @@ -62,7 +63,11 @@ class BaseActions(object): cmd = 'dockerctl shell {0} {1}'.format(container, command) if stdin is not None: cmd = 'echo "{0}" | {1}'.format(stdin, cmd) - result = self.admin_remote.execute(cmd) + + result = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd=cmd + ) if exit_code is not None: assert_equal(exit_code, result['exit_code'], @@ -93,7 +98,10 @@ class BaseActions(object): Standard output from console """ cmd = 'dockerctl copy {0} {1}'.format(copy_from, copy_to) - result = self.admin_remote.execute(cmd) + result = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd=cmd + ) assert_equal(0, result['exit_code'], ('Command copy returned exit code "{e}", but ' 'expected "0". Output: {out}; {err} ').format( @@ -105,8 +113,10 @@ class BaseActions(object): @property def is_container_ready(self): - result = self.admin_remote.execute("timeout 5 dockerctl check {0}" - .format(self.container)) + result = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd="timeout 5 dockerctl check {0}".format(self.container) + ) return result['exit_code'] == 0 def wait_for_ready_container(self, timeout=300): @@ -179,9 +189,17 @@ class BaseActions(object): self.copy_between_node_and_container( '{0}:{1}'.format(container, path_to_file), old_file) - self.admin_remote.download(old_file, old_file) + self.ssh_manager.download_from_remote( + ip=self.admin_ip, + destination=old_file, + target=old_file + ) self.put_value_to_local_yaml(old_file, new_file, element, value) - self.admin_remote.upload(new_file, new_file) + self.ssh_manager.upload_to_remote( + ip=self.admin_ip, + source=new_file, + target=new_file + ) self.copy_between_node_and_container( new_file, '{0}:{1}'.format(container, path_to_file)) os.remove(old_file) @@ -204,8 +222,11 @@ class BaseActions(object): admin_tmp_file = path_to_file host_tmp_file = '/tmp/temp_file_{0}.yaml'.format(str(os.getpid())) - - self.admin_remote.download(admin_tmp_file, host_tmp_file) + self.ssh_manager.download_from_remote( + ip=self.admin_ip, + destination=admin_tmp_file, + target=host_tmp_file + ) value = self.get_value_from_local_yaml(host_tmp_file, element) os.remove(host_tmp_file) return value @@ -228,11 +249,18 @@ class BaseActions(object): admin_tmp_file = path_to_file host_tmp_file = '/tmp/temp_file_{0}.yaml'.format(str(os.getpid())) - - self.admin_remote.download(admin_tmp_file, host_tmp_file) + self.ssh_manager.download_from_remote( + ip=self.admin_ip, + destination=admin_tmp_file, + target=host_tmp_file + ) self.put_value_to_local_yaml(host_tmp_file, host_tmp_file, element, value) - self.admin_remote.upload(host_tmp_file, admin_tmp_file) + self.ssh_manager.upload_to_remote( + ip=self.admin_ip, + source=host_tmp_file, + target=admin_tmp_file + ) if self.container: self.copy_between_node_and_container( admin_tmp_file, '{0}:{1}'.format(self.container, path_to_file)) @@ -242,8 +270,8 @@ class BaseActions(object): class AdminActions(BaseActions): """ All actions relating to the admin node.""" - def __init__(self, admin_remote): - super(AdminActions, self).__init__(admin_remote) + def __init__(self): + super(AdminActions, self).__init__() @logwrap def modify_configs(self, router): @@ -258,7 +286,8 @@ class AdminActions(BaseActions): # for admin node cmd = 'ntpdate -p 4 -t 0.2 -ub {0}'.format(router) - if not self.admin_remote.execute(cmd)['exit_code']: + if not self.ssh_manager.execute_on_remote(ip=self.admin_ip, + cmd=cmd)['exit_code']: # Local ntpd on the host is alive, so # remove all NTP sources and add the host instead. logger.info("Switching NTPD on the Fuel admin node to use " @@ -290,43 +319,53 @@ class AdminActions(BaseActions): ubuntu_files_count = 0 if centos_repo_path: - centos_files_count = cond_upload( - self.admin_remote, local_packages_dir, - os.path.join(centos_repo_path, 'Packages'), - "(?i).*\.rpm$") + centos_files_count = self.ssh_manager.cond_upload( + ip=self.admin_ip, + source=local_packages_dir, + target=os.path.join(centos_repo_path, 'Packages'), + condition="(?i).*\.rpm$" + ) if centos_files_count > 0: - regenerate_centos_repo(self.admin_remote, centos_repo_path) + regenerate_centos_repo(centos_repo_path) if ubuntu_repo_path: - ubuntu_files_count = cond_upload( - self.admin_remote, local_packages_dir, - os.path.join(ubuntu_repo_path, 'pool/main'), - "(?i).*\.deb$") + ubuntu_files_count = self.ssh_manager.cond_upload( + ip=self.admin_ip, + source=local_packages_dir, + target=os.path.join(ubuntu_repo_path, 'pool/main'), + condition="(?i).*\.deb$" + ) if ubuntu_files_count > 0: - regenerate_ubuntu_repo(self.admin_remote, ubuntu_repo_path) + regenerate_ubuntu_repo(ubuntu_repo_path) return centos_files_count, ubuntu_files_count @logwrap def clean_generated_image(self, distro): - images = ''.join( - self.admin_remote.execute( - "find /var/www/nailgun/targetimages/ -name" - " 'env*{}*' -printf '%P\n'".format(distro.lower()))) + out = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd="find /var/www/nailgun/targetimages/ -name " + "'env*{}*' -printf '%P\n'".format(distro.lower()) + ) + images = ''.join(out) logger.debug("images are {}".format(images)) - self.admin_remote.execute( - "find /var/www/nailgun/targetimages/ -name 'env*{}*'" - " -delete".format(distro.lower())) + self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd="find /var/www/nailgun/targetimages/ -name 'env*{}*'" + " -delete".format(distro.lower()) + ) @logwrap @retry(2) - def untar(self, node_ssh, name, path): + def untar(self, node_ip, name, path): logger.info('Unpacking file') filename, ext = os.path.splitext(name) cmd = "tar -xpvf" if ext.endswith("tar") else "lrzuntar" - result = node_ssh.execute( - 'cd {0} && {2} {1}'.format(path, name, cmd)) + result = self.ssh_manager.execute_on_remote( + ip=node_ip, + cmd='cd {0} && {2} {1}'.format(path, name, cmd) + ) stdout, stderr = ''.join(result['stdout']), ''.join(result['stderr']) logger.debug('Result from tar command is {0}\n{1}'.format(stdout, stderr)) @@ -334,6 +373,7 @@ class AdminActions(BaseActions): def upgrade_master_node(self, rollback=False, file_upload=True): """This method upgrades master node with current state.""" + # TODO: It will be remooved or changed master = self.admin_remote if file_upload: @@ -367,8 +407,10 @@ class AdminActions(BaseActions): def get_fuel_settings(self): cmd = 'cat {cfg_file}'.format(cfg_file=hlp_data.FUEL_SETTINGS_YAML) - result = self.admin_remote.execute(cmd) - + result = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd=cmd + ) if result['exit_code'] == 0: fuel_settings = yaml.load(''.join(result['stdout'])) else: @@ -382,7 +424,10 @@ class AdminActions(BaseActions): default_style='"', default_flow_style=False), hlp_data.FUEL_SETTINGS_YAML) - result = self.admin_remote.execute(cmd) + result = self.ssh_manager.execute_on_remote( + ip=self.admin_ip, + cmd=cmd + ) assert_equal(result['exit_code'], 0, "Saving Fuel settings failed: {0}!".format(result)) @@ -390,8 +435,8 @@ class AdminActions(BaseActions): class NailgunActions(BaseActions): """NailgunActions.""" # TODO documentation - def __init__(self, admin_remote): - super(NailgunActions, self).__init__(admin_remote) + def __init__(self): + super(NailgunActions, self).__init__() self.container = 'nailgun' def update_nailgun_settings_once(self, settings): @@ -461,8 +506,8 @@ class NailgunActions(BaseActions): class PostgresActions(BaseActions): """PostgresActions.""" # TODO documentation - def __init__(self, admin_remote): - super(PostgresActions, self).__init__(admin_remote) + def __init__(self): + super(PostgresActions, self).__init__() self.container = 'postgres' def run_query(self, db, query): @@ -493,8 +538,8 @@ class FuelPluginBuilder(BaseActions): Initializes BaseActions. """ - def __init__(self, admin_remote): - super(FuelPluginBuilder, self).__init__(admin_remote) + def __init__(self): + super(FuelPluginBuilder, self).__init__() self.container = 'nailgun' def fpb_install(self): @@ -562,7 +607,11 @@ class FuelPluginBuilder(BaseActions): """ self.execute_in_container( "rm -rf {0}".format(remote_file), self.container) - self.admin_remote.upload(local_file, "/tmp/temp.file") + self.ssh_manager.upload_to_remote( + ip=self.admin_ip, + source=local_file, + target="/tmp/temp.file" + ) self.copy_between_node_and_container( '/tmp/temp.file', '{0}:{1}'.format(self.container, remote_file)) @@ -594,8 +643,8 @@ class FuelPluginBuilder(BaseActions): class CobblerActions(BaseActions): """CobblerActions.""" # TODO documentation - def __init__(self, admin_remote): - super(CobblerActions, self).__init__(admin_remote) + def __init__(self): + super(CobblerActions, self).__init__() self.container = 'cobbler' def add_dns_upstream_server(self, dns_server_ip): @@ -613,18 +662,22 @@ class CobblerActions(BaseActions): class DockerActions(object): """DockerActions.""" # TODO documentation - def __init__(self, admin_remote): - self.admin_remote = admin_remote + def __init__(self): + self.ssh_manager = SSHManager() def list_containers(self): - return self.admin_remote.execute('dockerctl list')['stdout'] + result = self.ssh_manager.execute_on_remote( + ip=self.ssh_manager.admin_ip, + cmd='dockerctl list' + ) + return result['stdout'] def wait_for_ready_containers(self, timeout=300): if MASTER_IS_CENTOS7: return cont_actions = [] for container in self.list_containers(): - cont_action = BaseActions(self.admin_remote) + cont_action = BaseActions(self.ssh_manager) cont_action.container = container cont_actions.append(cont_action) try: @@ -639,8 +692,11 @@ class DockerActions(object): .format(failed_containers, timeout)) def restart_container(self, container): - self.admin_remote.execute('dockerctl restart {0}'.format(container)) - cont_action = BaseActions(self.admin_remote) + self.ssh_manager.execute_on_remote( + ip=self.ssh_manager.admin_ip, + cmd='dockerctl restart {0}'.format(container) + ) + cont_action = BaseActions(self.ssh_manager) cont_action.container = container cont_action.wait_for_ready_container() @@ -650,8 +706,10 @@ class DockerActions(object): def execute_in_containers(self, cmd): for container in self.list_containers(): - self.admin_remote.execute( - "dockerctl shell {0} bash -c '{1}'".format(container, cmd)) + self.ssh_manager.execute_on_remote( + ip=self.ssh_manager.admin_ip, + cmd="dockerctl shell {0} bash -c '{1}'".format(container, cmd) + ) class NessusActions(object): diff --git a/fuelweb_test/helpers/regenerate_repo.py b/fuelweb_test/helpers/regenerate_repo.py index 3351cc097..eaa3dd641 100644 --- a/fuelweb_test/helpers/regenerate_repo.py +++ b/fuelweb_test/helpers/regenerate_repo.py @@ -23,19 +23,20 @@ from xml.etree import ElementTree from fuelweb_test import logger from fuelweb_test import settings -from fuelweb_test.helpers.utils import install_pkg +from fuelweb_test.helpers.utils import install_pkg_2 +from fuelweb_test.helpers.ssh_manager import SSHManager -def regenerate_ubuntu_repo(remote, path): +def regenerate_ubuntu_repo(path): # Ubuntu - cr = CustomRepo(remote) + cr = CustomRepo() cr.install_tools(['dpkg', 'dpkg-devel', 'dpkg-dev']) cr.regenerate_repo('regenerate_ubuntu_repo', path) -def regenerate_centos_repo(remote, path): +def regenerate_centos_repo(path): # CentOS - cr = CustomRepo(remote) + cr = CustomRepo() cr.install_tools(['createrepo']) cr.regenerate_repo('regenerate_centos_repo', path) @@ -43,8 +44,9 @@ def regenerate_centos_repo(remote, path): class CustomRepo(object): """CustomRepo.""" # TODO documentation - def __init__(self, remote): - self.remote = remote + def __init__(self): + self.ssh_manager = SSHManager() + self.ip = self.ssh_manager.admin_ip self.path_scripts = ('{0}/fuelweb_test/helpers/' .format(os.environ.get("WORKSPACE", "./"))) self.remote_path_scripts = '/tmp/' @@ -111,7 +113,10 @@ class CustomRepo(object): logger.info("Installing necessary tools for {0}" .format(settings.OPENSTACK_RELEASE)) for master_tool in master_tools: - exit_code = install_pkg(self.remote, master_tool) + exit_code = install_pkg_2( + ip=self.ip, + pkg_name=master_tool + ) assert_equal(0, exit_code, 'Cannot install package {0} ' 'on admin node.'.format(master_tool)) @@ -241,7 +246,10 @@ class CustomRepo(object): .format(pkgs_local_path + path_suff, self.custom_pkgs_mirror, pkg["filename:"]) - wget_result = self.remote.execute(wget_cmd) + wget_result = self.ssh_manager.execute_on_remote( + ip=self.ip, + cmd=wget_cmd + ) assert_equal(0, wget_result['exit_code'], self.assert_msg(wget_cmd, wget_result['stderr'])) @@ -250,12 +258,16 @@ class CustomRepo(object): # Uploading scripts that prepare local repositories: # 'regenerate_centos_repo' and 'regenerate_ubuntu_repo' try: - self.remote.upload('{0}/{1}'.format(self.path_scripts, - regenerate_script), - self.remote_path_scripts) - self.remote.execute('chmod 755 {0}/{1}' - .format(self.remote_path_scripts, - regenerate_script)) + self.ssh_manager.upload_to_remote( + ip=self.ip, + source='{0}/{1}'.format(self.path_scripts, regenerate_script), + target=self.remote_path_scripts + ) + self.ssh_manager.execute_on_remote( + ip=self.ip, + cmd='chmod 755 {0}/{1}'.format(self.remote_path_scripts, + regenerate_script) + ) except Exception: logger.error('Could not upload scripts for updating repositories.' '\n{0}'.format(traceback.format_exc())) @@ -266,7 +278,10 @@ class CustomRepo(object): regenerate_script, local_mirror_path, self.ubuntu_release) - script_result = self.remote.execute(script_cmd) + script_result = self.ssh_manager.execute_on_remote( + ip=self.ip, + cmd=script_cmd + ) assert_equal(0, script_result['exit_code'], self.assert_msg(script_cmd, script_result['stderr'])) @@ -301,7 +316,10 @@ class CustomRepo(object): cmd = ('fgrep -h -e " Depends: " -e "{0}" -e "{1}" ' '/var/log/docker-logs/remote/node-*/' 'puppet*.log'.format(err_start, err_end)) - result = self.remote.execute(cmd)['stdout'] + result = self.ssh_manager.execute_on_remote( + ip=self.ip, + cmd=cmd + )['stdout'] err_deps = {} err_deps_key = '' @@ -338,7 +356,10 @@ class CustomRepo(object): cmd = ('fgrep -h -e "Error: Package: " -e " Requires: " /var/log/' 'docker-logs/remote/node-*/puppet*.log') - result = self.remote.execute(cmd)['stdout'] + result = self.ssh_manager.execute_on_remote( + ip=self.ip, + cmd=cmd + )['stdout'] err_deps = {} err_deps_key = '' diff --git a/fuelweb_test/helpers/ssh_manager.py b/fuelweb_test/helpers/ssh_manager.py new file mode 100644 index 000000000..b4b0ad22e --- /dev/null +++ b/fuelweb_test/helpers/ssh_manager.py @@ -0,0 +1,197 @@ +# Copyright 2015 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import posixpath +import re + +from paramiko import RSAKey +from devops.models.node import SSHClient +from fuelweb_test import logger + + +class SingletonMeta(type): + def __init__(cls, name, bases, dict): + super(SingletonMeta, cls).__init__(name, bases, dict) + cls.instance = None + + def __call__(self, *args, **kw): + if self.instance is None: + self.instance = super(SingletonMeta, self).__call__(*args, **kw) + return self.instance + + def __getattr__(cls, name): + return getattr(cls(), name) + + +class SSHManager(object): + __metaclass__ = SingletonMeta + + def __init__(self): + logger.debug('SSH_MANAGER: Run constructor SSHManager') + self.connections = {} + self.admin_ip = None + self.admin_port = None + self.login = None + self.password = None + + def initialize(self, admin_ip, login, password): + """ It will be moved to __init__ + + :param admin_ip: ip address of admin node + :param login: user name + :param password: password for user + :return: None + """ + self.admin_ip = admin_ip + self.admin_port = 22 + self.login = login + self.password = password + + def _connect(self, remote): + """ Check if connection is stable and return this one + + :param remote: + :return: + """ + try: + remote.execute("cd ~") + except Exception: + remote.reconnect() + return remote + + def _get_keys(self): + keys = [] + admin_remote = self._get_remote(self.admin_ip) + for key_string in ['/root/.ssh/id_rsa', '/root/.ssh/bootstrap.rsa']: + with admin_remote.open(key_string) as f: + keys.append(RSAKey.from_private_key(f)) + return keys + + def _get_remote(self, ip, port=22): + """ Function returns remote SSH connection to node by ip address + + :param ip: IP of host + :param port: port for SSH + :return: SSHClient + """ + if (ip, port) not in self.connections: + logger.debug('SSH_MANAGER:Create new connection for ' + '{ip}:{port}'.format(ip=ip, port=port)) + + keys = self._get_keys() if ip != self.admin_ip else [] + + self.connections[(ip, port)] = SSHClient( + host=ip, + port=port, + username=self.login, + password=self.password, + private_keys=keys + ) + logger.debug('SSH_MANAGER:Return existed connection for ' + '{ip}:{port}'.format(ip=ip, port=port)) + logger.debug('SSH_MANAGER: Connections {0}'.format(self.connections)) + return self._connect(self.connections[(ip, port)]) + + def execute_on_remote(self, ip, cmd, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.execute(cmd) + + def open_on_remote(self, ip, path, mode='r', port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.open(path, mode) + + def upload_to_remote(self, ip, source, target, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.upload(source, target) + + def download_from_remote(self, ip, destination, target, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.download(destination, target) + + def exist_on_remote(self, ip, path, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.exist(path) + + def isdir_on_remote(self, ip, path, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.isdir(path) + + def isfile_on_remote(self, ip, path, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.isfile(path) + + def mkdir_on_remote(self, ip, path, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.mkdir(path) + + def rm_rf_on_remote(self, ip, path, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.rm_rf(path) + + def cond_upload(self, ip, source, target, port=22, condition=''): + """ Upload files only if condition in regexp matches filenames + + :param ip: host ip + :param source: source path + :param target: destination path + :param port: ssh port + :param condition: regexp condition + :return: count of files + """ + + # remote = self._get_remote(ip=ip, port=port) + # maybe we should use SSHClient function. e.g. remote.isdir(target) + # we can move this function to some *_actions class + if self.isdir_on_remote(ip=ip, port=port, path=target): + target = posixpath.join(target, os.path.basename(source)) + + source = os.path.expanduser(source) + if not os.path.isdir(source): + if re.match(condition, source): + self.upload_to_remote(ip=ip, port=port, + source=source, target=target) + logger.debug("File '{0}' uploaded to the remote folder" + " '{1}'".format(source, target)) + return 1 + else: + logger.debug("Pattern '{0}' doesn't match the file '{1}', " + "uploading skipped".format(condition, source)) + return 0 + + files_count = 0 + for rootdir, subdirs, files in os.walk(source): + targetdir = os.path.normpath( + os.path.join( + target, + os.path.relpath(rootdir, source))).replace("\\", "/") + + self.mkdir_on_remote(ip=ip, port=port, path=targetdir) + + for entry in files: + local_path = os.path.join(rootdir, entry) + remote_path = posixpath.join(targetdir, entry) + if re.match(condition, local_path): + self.upload_to_remote(ip=ip, + port=port, + source=local_path, + target=remote_path) + files_count += 1 + logger.debug("File '{0}' uploaded to the " + "remote folder '{1}'".format(source, target)) + else: + logger.debug("Pattern '{0}' doesn't match the file '{1}', " + "uploading skipped".format(condition, + local_path)) + return files_count diff --git a/fuelweb_test/helpers/utils.py b/fuelweb_test/helpers/utils.py index 505ff25cb..3adc6834f 100644 --- a/fuelweb_test/helpers/utils.py +++ b/fuelweb_test/helpers/utils.py @@ -28,6 +28,7 @@ from proboscis import asserts from fuelweb_test import logger from fuelweb_test import logwrap from fuelweb_test import settings +from fuelweb_test.helpers.ssh_manager import SSHManager from fuelweb_test.settings import MASTER_IS_CENTOS7 @@ -300,6 +301,34 @@ def install_pkg(remote, pkg_name): return remote_status['exit_code'] +def install_pkg_2(ip, pkg_name, port=22): + """Install a package on node + :param ip: ip of node + :param pkg_name: name of a package + :param port: ssh port + :return: exit code of installation + """ + ssh_manager = SSHManager() + remote_status = ssh_manager.execute_on_remote( + ip=ip, + port=port, + cmd="rpm -q '{0}'".format(pkg_name) + ) + if remote_status['exit_code'] == 0: + logger.info("Package '{0}' already installed.".format(pkg_name)) + else: + logger.info("Installing package '{0}' ...".format(pkg_name)) + remote_status = ssh_manager.execute_on_remote( + ip=ip, + port=port, + cmd="yum -y install {0}".format(pkg_name) + ) + logger.info("Installation of the package '{0}' has been" + " completed with exit code {1}" + .format(pkg_name, remote_status['exit_code'])) + return remote_status['exit_code'] + + def cond_upload(remote, source, target, condition=''): # Upload files only if condition in regexp matches filenames if remote.isdir(target): diff --git a/fuelweb_test/models/environment.py b/fuelweb_test/models/environment.py index 170967564..ca28eea6a 100644 --- a/fuelweb_test/models/environment.py +++ b/fuelweb_test/models/environment.py @@ -37,6 +37,7 @@ from fuelweb_test.helpers.fuel_actions import NailgunActions from fuelweb_test.helpers.fuel_actions import PostgresActions from fuelweb_test.helpers.fuel_actions import NessusActions from fuelweb_test.helpers.ntp import GroupNtpSync +from fuelweb_test.helpers.ssh_manager import SSHManager from fuelweb_test.helpers.utils import run_on_remote from fuelweb_test.helpers.utils import TimeStat from fuelweb_test.helpers import multiple_networks_hacks @@ -66,11 +67,23 @@ class EnvironmentModel(object): self._fuel_web = None if not hasattr(self, "_config"): self._config = None + self.ssh_manager = SSHManager() + self.ssh_manager.initialize( + self.get_admin_node_ip(), + login=settings.SSH_CREDENTIALS['login'], + password=settings.SSH_CREDENTIALS['password'] + ) + self.admin_actions = AdminActions() + self.base_actions = BaseActions() + self.cobbler_actions = CobblerActions() + self.docker_actions = DockerActions() + self.nailgun_actions = NailgunActions() + self.postgres_actions = PostgresActions() @property def fuel_web(self): if self._fuel_web is None: - self._fuel_web = FuelWebClient(self.get_admin_node_ip(), self) + self._fuel_web = FuelWebClient(self) return self._fuel_web def __repr__(self): @@ -83,30 +96,6 @@ class EnvironmentModel(object): obj_id=obj_id, ip=ip) - @property - def admin_actions(self): - return AdminActions(self.d_env.get_admin_remote()) - - @property - def base_actions(self): - return BaseActions(self.d_env.get_admin_remote()) - - @property - def nailgun_actions(self): - return NailgunActions(self.d_env.get_admin_remote()) - - @property - def postgres_actions(self): - return PostgresActions(self.d_env.get_admin_remote()) - - @property - def cobbler_actions(self): - return CobblerActions(self.d_env.get_admin_remote()) - - @property - def docker_actions(self): - return DockerActions(self.d_env.get_admin_remote()) - @property def admin_node_ip(self): return self.fuel_web.admin_node_ip diff --git a/fuelweb_test/models/fuel_web_client.py b/fuelweb_test/models/fuel_web_client.py index fcacc4440..f550ddda6 100644 --- a/fuelweb_test/models/fuel_web_client.py +++ b/fuelweb_test/models/fuel_web_client.py @@ -23,6 +23,7 @@ from devops.error import DevopsCalledProcessError from devops.error import TimeoutError from devops.helpers.helpers import _wait from devops.helpers.helpers import wait +from fuelweb_test.helpers.ssh_manager import SSHManager from fuelweb_test.helpers.ssl import copy_cert_from_master from fuelweb_test.helpers.ssl import change_cluster_ssl_config from ipaddr import IPNetwork @@ -86,9 +87,10 @@ from fuelweb_test.settings import iface_alias class FuelWebClient(object): """FuelWebClient.""" # TODO documentation - def __init__(self, admin_node_ip, environment): - self.admin_node_ip = admin_node_ip - self.client = NailgunClient(admin_node_ip) + def __init__(self, environment): + self.ssh_manager = SSHManager() + self.admin_node_ip = self.ssh_manager.admin_ip + self.client = NailgunClient(self.ssh_manager.admin_ip) self._environment = environment self.security = SecurityChecks(self.client, self._environment) super(FuelWebClient, self).__init__() diff --git a/fuelweb_test/tests/base_test_case.py b/fuelweb_test/tests/base_test_case.py index 21da4aad3..f5089d99f 100644 --- a/fuelweb_test/tests/base_test_case.py +++ b/fuelweb_test/tests/base_test_case.py @@ -20,6 +20,7 @@ from fuelweb_test import logger from fuelweb_test.helpers.decorators import log_snapshot_after_test from fuelweb_test.helpers.utils import get_test_method_name from fuelweb_test.helpers.utils import TimeStat +from fuelweb_test.helpers.ssh_manager import SSHManager from fuelweb_test.models.environment import EnvironmentModel from fuelweb_test.settings import REPLACE_DEFAULT_REPOS from fuelweb_test.settings import REPLACE_DEFAULT_REPOS_ONLY_ONCE @@ -32,8 +33,9 @@ class TestBasic(object): """ def __init__(self): - self.env = EnvironmentModel() self._current_log_step = 0 + self.ssh_manager = SSHManager() + self.env = EnvironmentModel() @property def test_program(self): diff --git a/fuelweb_test/tests/test_cli.py b/fuelweb_test/tests/test_cli.py index 7f151c580..7aaa8c118 100644 --- a/fuelweb_test/tests/test_cli.py +++ b/fuelweb_test/tests/test_cli.py @@ -265,21 +265,22 @@ class CommandLineTest(test_cli_base.CommandLine): 'Some slaves do not become online after revert!!' ' Expected {0} Actual {1}'.format(nodes, online_nodes)) - with self.env.d_env.get_admin_remote() as remote: - res = remote.execute('fuel --env {0} env delete' - .format(cluster_id)) - assert_true( - res['exit_code'] == 0) + res = self.ssh_manager.execute_on_remote( + ip=self.ssh_manager.admin_ip, + cmd='fuel --env {0} env delete'.format(cluster_id) + ) + assert_true(res['exit_code'] == 0) - with self.env.d_env.get_admin_remote() as remote: - try: - wait(lambda: - remote.execute("fuel env | awk '{print $1}'" - " | tail -n 1 | grep '^.$'") - ['exit_code'] == 1, timeout=60 * 10) - except TimeoutError: - raise TimeoutError( - "cluster {0} was not deleted".format(cluster_id)) + try: + wait(lambda: + self.ssh_manager.execute_on_remote( + ip=self.ssh_manager.admin_ip, + cmd="fuel env | awk '{print $1}' | tail -n 1 | " + "grep '^.$'" + )['exit_code'] == 1, timeout=60 * 10) + except TimeoutError: + raise TimeoutError( + "cluster {0} was not deleted".format(cluster_id)) assert_false( check_cluster_presence(cluster_id, self.env.postgres_actions),