[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