[Freeipa-devel] [python-pytest-multihost]-Ticket-6 run_command produces exit code 1 on windows
Niranjan
mrniranjan at fedoraproject.org
Mon Apr 18 16:27:12 UTC 2016
Petr Viktorin wrote:
> On 04/18/2016 12:39 PM, Niranjan wrote:
> > Niranjan wrote:
> >> Niranjan wrote:
> >>> Petr Viktorin wrote:
> >>>> On 04/06/2016 10:55 AM, Niranjan wrote:
> >>>>> Greetings,
> >>>>>
> >>>>> For https://fedorahosted.org/python-pytest-multihost/ticket/6 , i have proposed
> >>>>> a patch, I think this patch is more of a workaround , than a solution. I would
> >>>>> like to get more inputs on how to use pytest-multihost to execute commands
> >>>>> on Windows. The method i am using requires cygwin with openssh/bash to be
> >>>>> installed.
> >>>>
> >>>> Wow. I'm surprised the only problem on Windows is the "set -e".
> >>>>
> >>>> Regarding the patch:
> >>>>
> >>>> - Why is get_winhost_class needed? I don't see it called anywhere.
> >>>> - Similarly, why is host_type needed? Your patch only sets it.
> >>>>
> >>>> If run_command of WinHost and Unix hosts are this similar, I would put
> >>>> the 'set -e\n' for Unix (and an empty string for Windows) in a class
> >>>> attribute named e.g. "command_prelude", use it in run_command, and move
> >>>> run_command from Host to BaseHost.
> >>>> Would that work, or am I missing something?
> >>> How do we detect the host is windows/unix then , we still need host_type
> >>> to know what the type of host is (unix/windows)?
> >>>>
> >>>>
> >>>> --
> >>>> Petr Viktorin
> >> Please review the attached patch.
>
> Sorry for the delay.
>
> The information about whether the host is Unix or Windows is available:
> the class is either Host or WinHost, so I don't think an extra host_type
> is necessary.
> I'd be open to adding host_type as *configuration* (and corresponding
> attribute) if it also selected the actual Host class. Currently to use
> WinHost you need to write custom code.
>
> I modified the patch so BaseHost takes test_dir as an argument, and sets
> it as self.test_dir, so that users don't need to choose between
> self.config.windows_test_dir/self.config.test_dir themselves.
> (Unfortunately I don't have Windows to test on; I hope the change is
> correct.) Would this approach work for you?
>
> --
> Petr Viktorin
>
> From f4e8290beb21eeee7aba60f1caf7e69b472e1e8e Mon Sep 17 00:00:00 2001
> From: Niranjan MR <mrniranjan at fedoraproject.org>
> Date: Tue, 12 Apr 2016 17:18:10 +0530
> Subject: [PATCH] modify run_command to execute on Windows
>
> Add global parameter windows_test_dir to specify test directory
> for Windows
> Windows hosts (WinHost) use this directory by default.
> test_dir can now be set individually for each host.
> Move run_command from Host class to BaseHost class
>
> Signed-off-by: Petr Viktorin <pviktroi at redhat.com>
> ---
> pytest_multihost/config.py | 3 +++
> pytest_multihost/host.py | 41 ++++++++++++++++++++++-------------------
> 2 files changed, 25 insertions(+), 19 deletions(-)
>
> diff --git a/pytest_multihost/config.py b/pytest_multihost/config.py
> index 31045c2..58a3a5f 100644
> --- a/pytest_multihost/config.py
> +++ b/pytest_multihost/config.py
> @@ -45,6 +45,7 @@ class Config(object):
> self.ssh_password = kwargs.get('ssh_password')
> self.ssh_username = kwargs.get('ssh_username', 'root')
> self.ipv6 = bool(kwargs.get('ipv6', False))
> + self.windows_test_dir = kwargs.get('windows_test_dir', '/home/Administrator')
>
> if not self.ssh_password and not self.ssh_key_filename:
> self.ssh_key_filename = '~/.ssh/id_rsa'
> @@ -80,6 +81,8 @@ class Config(object):
> dct['ssh_key_filename'] = dct.pop('root_ssh_key_filename')
> if 'root_password' in dct:
> dct['ssh_password'] = dct.pop('root_password')
> + if 'windows_test_dir' in dct:
> + dct['windows_test_dir'] = dct.pop('windows_test_dir')
>
> all_init_args = set(init_args) | set(cls.extra_init_args)
> extra_args = set(dct) - all_init_args
> diff --git a/pytest_multihost/host.py b/pytest_multihost/host.py
> index e6c0db5..f82ff33 100644
> --- a/pytest_multihost/host.py
> +++ b/pytest_multihost/host.py
> @@ -25,9 +25,11 @@ class BaseHost(object):
> See README for an overview of the core classes.
> """
> transport_class = None
> + command_prelude = ''
>
> def __init__(self, domain, hostname, role, ip=None,
> - external_hostname=None, username=None, password=None):
> + external_hostname=None, username=None, password=None,
> + test_dir=None):
> self.domain = domain
> self.role = str(role)
> if username is None:
> @@ -40,6 +42,10 @@ class BaseHost(object):
> else:
> self.ssh_key_filename = None
> self.ssh_password = password
> + if test_dir is None:
> + self.test_dir = domain.config.test_dir
> + else:
> + self.test_dir = test_dir
>
> shortname, dot, ext_domain = hostname.partition('.')
> self.shortname = shortname
> @@ -78,7 +84,7 @@ class BaseHost(object):
> self.host_key = None
> self.ssh_port = 22
>
> - self.env_sh_path = os.path.join(domain.config.test_dir, 'env.sh')
> + self.env_sh_path = os.path.join(self.test_dir, 'env.sh')
>
> self.log_collectors = []
>
> @@ -204,28 +210,18 @@ class BaseHost(object):
> does not exit with return code 0
> :param cwd: The working directory for the command
> """
> - raise NotImplementedError()
> -
> -
> -class Host(BaseHost):
> - """A Unix host"""
> - transport_class = transport.SSHTransport
> -
> - def run_command(self, argv, set_env=True, stdin_text=None,
> - log_stdout=True, raiseonerr=True,
> - cwd=None):
> - # This will give us a Bash shell
> command = self.transport.start_shell(argv, log_stdout=log_stdout)
> -
> # Set working directory
> if cwd is None:
> - cwd = self.config.test_dir
> + cwd = self.test_dir
> command.stdin.write('cd %s\n' % shell_quote(cwd))
>
> # Set the environment
> if set_env:
> command.stdin.write('. %s\n' % shell_quote(self.env_sh_path))
> - command.stdin.write('set -e\n')
> +
> + if self.command_prelude:
> + command.stdin.write(self.command_prelude)
>
> if isinstance(argv, basestring):
> # Run a shell command given as a string
> @@ -247,11 +243,18 @@ class Host(BaseHost):
> return command
>
>
> +class Host(BaseHost):
> + """A Unix host"""
> + transport_class = transport.SSHTransport
> + command_prelude = 'set -e\n'
> +
> +
> class WinHost(BaseHost):
> """
> Representation of a remote Windows host.
> -
> - This is a stub; Windows hosts can't currently be interacted with.
> """
>
> - pass
> + def __init__(self, domain, hostname, role, **kwargs):
> + # Set test_dir to the Windows directory, if not given explicitly
> + kwargs.setdefault('test_dir', domain.config.windows_test_dir)
> + super(WinHost, self).__init__(domain, hostname, role, **kwargs)
> --
> 2.5.5
>
I tried with the following multihost config:
mhc2.yaml:
windows_test_dir: '/home/Administrator'
domains:
- name: winpki1.testpki.test
type: sssd
hosts:
- name: WIN-Q8VKBEJ7H39
ip: 192.168.122.62
role: master
username: 'Administrator'
password: 'helloworld'
- name: client1
ip: 192.168.122.61
external_hostname: client1.example.test
role: client
username: 'root'
password: 'helloworld'
test1.py:
from pytest_multihost import make_multihost_fixture
from sssd.testlib.common.qe_class import QeConfig
import pytest
@pytest.fixture(scope='session')
def session_multihost(request):
mh = make_multihost_fixture(
request,
descriptions=
[
{
'type': 'sssd',
'hosts':
{
'master':1,
'client':1
}
},
],
config_class=QeConfig)
mh.domain = mh.config.domains[0]
[mh.master] = mh.domain.hosts_by_role('master')
[mh.client] = mh.domain.hosts_by_role('client')
return mh
@pytest.fixture()
def multihost(session_multihost, request):
return session_multihost
def test_1(multihost):
print("i am in test1")
print(dir(multihost.master))
print("test_dir =", multihost.master.test_dir)
multihost.master.command_prelude = ''
output = multihost.master.run_command('date', set_env=False,raiseonerr=False)
print(output.stdout_text)
def test_2(multihost):
print("i am in test2")
print(dir(multihost.master))
output = multihost.client.run_command('date',raiseonerr=False)
The output is as follows:
<snip>
'__weakref__', 'add_log_collector', 'collect_log', 'command_prelude', 'config', 'dnf_install', 'dnf_uninstall', 'domain', 'env_sh_path', 'external_hostname', 'from_dict', 'get_distro', 'get_file_contents', 'gethostname', 'host_key', 'hostname', 'ip', 'log', 'log_collectors', 'logger_name', 'netbios', 'put_file_contents', 'remove_log_collector', 'reset_connection', 'role', 'run_command', 'shortname', 'ssh_key_filename', 'ssh_password', 'ssh_port', 'ssh_username', 'test_dir', 'to_dict', 'transport', 'transport_class', 'yum_install', 'yum_uninstall']
('test_dir =', '/root/multihost_tests')
2016-04-18 21:50:10,406 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.ParamikoTransport - DEBUG - Authenticating with password using user Administrator
2016-04-18 21:50:13,779 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.ParamikoTransport - INFO - RUN date
2016-04-18 21:50:13,779 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.cmd1 - DEBUG - RUN date
2016-04-18 21:50:14,917 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.cmd1 - DEBUG - -bash: line 1: cd: /root/multihost_tests: No such file or directory
2016-04-18 21:50:15,507 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.cmd1 - DEBUG - Mon Apr 18 21:46:33 IST 2016
2016-04-18 21:50:15,541 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.cmd1 - DEBUG - TERM environment variable not set.
2016-04-18 21:50:15,543 - sssd.testlib.common.qe_class.QeHost.WIN-Q8VKBEJ7H39.cmd1 - DEBUG - Exit code: 0
Mon Apr 18 21:46:33 IST 2016
PASSED
test1.py::test_2 i am in test2
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_transport', 'add_log_collector', 'collect_log', 'command_prelude', 'config', 'dnf_install', 'dnf_uninstall', 'domain', 'env_sh_path', 'external_hostname', 'from_dict', 'get_distro', 'get_file_contents', 'gethostname', 'host_key', 'hostname', 'ip', 'log', 'log_collectors', 'logger_name', 'netbios', 'put_file_contents', 'remove_log_collector', 'reset_connection', 'role', 'run_command', 'shortname', 'ssh_key_filename', 'ssh_password', 'ssh_port', 'ssh_username', 'test_dir', 'to_dict', 'transport', 'transport_class', 'yum_install', 'yum_uninstall']
2016-04-18 21:50:15,611 - sssd.testlib.common.qe_class.QeHost.client1.ParamikoTransport - DEBUG - Authenticating with password using user root
2016-04-18 21:50:15,776 - sssd.testlib.common.qe_class.QeHost.client1.ParamikoTransport - INFO - RUN date
2016-04-18 21:50:15,776 - sssd.testlib.common.qe_class.QeHost.client1.cmd1 - DEBUG - RUN date
2016-04-18 21:50:15,796 - sssd.testlib.common.qe_class.QeHost.client1.cmd1 - DEBUG - -bash: line 1: cd: /root/multihost_tests: No such file or directory
2016-04-18 21:50:15,796 - sssd.testlib.common.qe_class.QeHost.client1.cmd1 - DEBUG - -bash: line 2: /root/multihost_tests/env.sh: No such file or directory
2016-04-18 21:50:15,797 - sssd.testlib.common.qe_class.QeHost.client1.cmd1 - DEBUG - Mon Apr 18 21:50:15 IST 2016
2016-04-18 21:50:15,798 - sssd.testlib.common.qe_class.QeHost.client1.cmd1 - DEBUG - Exit code: 0
</snip>
test_1 is where i execute "date" command on windows. I see that test_dir still points to /root/multihost_tests,
i am wondering should it point to /home/Administrator.
Do we need to override WinHost ?, We override Host class using qe_class.py . i am attaching the qe_class.py
Could you have a look at the above.
Regards
Niranjan
-------------- next part --------------
from pytest_multihost import make_multihost_fixture
import pytest_multihost.config
import pytest_multihost.host
import logging
import pytest
class QeConfig(pytest_multihost.config.Config):
"""
QeConfig subclass of multihost plugin to extend functionality
"""
extra_init_args = {
'directory_manager',
'directory_password',
'rootdn',
'rootdn_pwd',}
def __init__(self, **kwargs):
self.log = self.get_logger('%s.%s' % (__name__, type(self).__name__))
pytest_multihost.config.Config.__init__(self, **kwargs)
def get_domain_class(self):
"""
return custom domain class. This is needed to fully extend the config for
custom multihost plugin extensions.
:param None:
:return None:
"""
return QeDomain
def get_logger(self, name):
"""
Override get_logger to set logging level
:param str name:
:return obj log:
"""
log = logging.getLogger(name)
log.propagate = False
if not log.handlers:
# set log Level
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
# set formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
return log
class QeDomain(pytest_multihost.config.Domain):
"""
QeDomain subclass of multihost plugin domain class.
"""
def __init__(self, config, name, domain_type):
"""
Subclass of pytest_multihost.config.Domain
:param obj config: config config
:param str name: Name
:param str domain_type:
:return None:
"""
self.type = str(domain_type)
self.config = config
self.name = str(name)
self.hosts = []
def get_host_class(self, host_dict):
"""
return custom host class. This is needed to fully extend the config for
custom multihost plugin extensions.
"""
return QeHost
class QeHost(pytest_multihost.host.Host):
"""
QeHost subclass of multihost plugin host class. This extends functionality
of the host class for SSSD QE purposes. Here we add support functions that
will be very widely used across tests and must be run on any or all hosts
in the environment.
"""
@classmethod
def get_distro(self):
""" Return contents of /etc/redhatrelease """
cmd = self.run_command(
['cat', '/etc/redhat-release'], raiseonerr=False)
if cmd.returncode != 0:
distro = 'unknown Distro'
else:
distro = cmd.stdout_text.strip()
return distro
@classmethod
def gethostname(self):
""" Return system hostname """
if self.host_type != 'windows':
cmd = self.run_command(['hostname'], raiseonerr=False)
else:
cmd = self.run_command(['hostname', '-A'], set_env=False, raiseonerr=False)
return cmd.stdout_text.strip()
@classmethod
def yum_install(self, package):
""" Install packages through yum """
cmd = self.run_command(['yum', '-y', 'install', package], raiseonerr=False)
return cmd
@classmethod
def dnf_install(self, package):
""" Install packges through dnf """
cmd = self.run_command(['dnf', '-y', 'install', package], raiseonerr=False)
return cmd
@classmethod
def yum_uninstall(self, package):
""" Uninstall packages through yum """
cmd = self.run_command(['yum', '-y', 'remove', package], raiseonerr=False)
return cmd
@classmethod
def dnf_uninstall(self, package):
""" Uninstall packages through dnf """
cmd = self.run_command(['dnf', '-y', 'remove', package], raiseonerr=False)
return cmd
@pytest.yield_fixture(scope="session", autouse=True)
def session_multihost(request):
""" Mulithost plugin fixture for session scope """
mh = make_multihost_fixture(request,
descriptions=[
{
'type': 'sssd',
'hosts':
{
'master': pytest.num_masters,
'replica': pytest.num_replicas,
'client': pytest.num_clients,
'other': pytest.num_others,
}
},
],
config_class=QeConfig)
mh.domain = mh.config.domains[0]
mh.master = mh.domain.hosts_by_role('master')
mh.replicas = mh.domain.hosts_by_role('replica')
mh.clients = mh.domain.hosts_by_role('client')
mh.others = mh.domain.hosts_by_role('other')
yield mh
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 328 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20160418/29f52f7a/attachment.sig>
More information about the Freeipa-devel
mailing list