Hello community,

here is the log from the commit of package python-shaptools for 
openSUSE:Factory checked in at 2019-07-23 22:40:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-shaptools (Old)
 and      /work/SRC/openSUSE:Factory/.python-shaptools.new.4126 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-shaptools"

Tue Jul 23 22:40:21 2019 rev:5 rq:717873 version:0.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-shaptools/python-shaptools.changes        
2019-07-21 11:33:40.348784109 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-shaptools.new.4126/python-shaptools.changes  
    2019-07-23 22:40:26.514933434 +0200
@@ -1,0 +2,11 @@
+Tue Jul 23 11:04:25 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.3.1
+- Add support for Power machines 
+
+-------------------------------------------------------------------
+Thu Jul 18 08:46:15 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Add an option to run the commands in remote nodes to shapcli
+
+-------------------------------------------------------------------

Old:
----
  shaptools-0.3.0.tar.gz

New:
----
  shaptools-0.3.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-shaptools.spec ++++++
--- /var/tmp/diff_new_pack.5aSiMD/_old  2019-07-23 22:40:26.926933349 +0200
+++ /var/tmp/diff_new_pack.5aSiMD/_new  2019-07-23 22:40:26.930933348 +0200
@@ -22,7 +22,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-shaptools
-Version:        0.3.0
+Version:        0.3.1
 Release:        0
 Summary:        Python tools to interact with SAP HANA utilities
 License:        Apache-2.0

++++++ shaptools-0.3.0.tar.gz -> shaptools-0.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/docs/SHAPCLI.md 
new/shaptools-0.3.1/docs/SHAPCLI.md
--- old/shaptools-0.3.0/docs/SHAPCLI.md 2019-07-18 11:03:12.820176367 +0200
+++ new/shaptools-0.3.1/docs/SHAPCLI.md 2019-07-23 13:12:24.071282674 +0200
@@ -49,3 +49,17 @@
 ```
 shapcli hana -h
 ```
+
+### Running commands in remote nodes
+
+The commands can be executed in remote nodes too. For that the `-r` or 
`--remote` flag have to be
+used (or adding the `remote` entry in the configuration file [the `-r` flag 
has priority over the configuration file entry]).
+
+```
+shapcli -c config.json -r remotehost hana version
+```
+
+If the ssh keys of the current node is not installed in the remote host, the 
password must be
+provided after the command. To avoid this, the ssh key of the current node can 
be authorized in the
+remote node. By default, the ssh public key must be added in: 
`/usr/sap/PRD/home/.ssh/authorized_keys`
+(where `PRD` is the SAP HANA instanse sid in uppercase)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/python-shaptools.changes 
new/shaptools-0.3.1/python-shaptools.changes
--- old/shaptools-0.3.0/python-shaptools.changes        2019-07-18 
11:03:12.820176367 +0200
+++ new/shaptools-0.3.1/python-shaptools.changes        2019-07-23 
13:12:24.071282674 +0200
@@ -1,8 +1,19 @@
 -------------------------------------------------------------------
+Tue Jul 23 11:04:25 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.3.1
+- Add support for Power machines 
+
+-------------------------------------------------------------------
+Thu Jul 18 08:46:15 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Add an option to run the commands in remote nodes to shapcli
+
+-------------------------------------------------------------------
 Wed Jul 17 09:34:22 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
 
 - Create package version 0.3.0
-- shapcli is provided to expose shaptools api methods as command line tool 
+- shapcli is provided to expose shaptools api methods as command line tool
 
 -------------------------------------------------------------------
 Tue Jun 11 11:29:44 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/python-shaptools.spec 
new/shaptools-0.3.1/python-shaptools.spec
--- old/shaptools-0.3.0/python-shaptools.spec   2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/python-shaptools.spec   2019-07-23 13:12:24.071282674 
+0200
@@ -22,7 +22,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-shaptools
-Version:        0.3.0
+Version:        0.3.1
 Release:        0
 Summary:        Python tools to interact with SAP HANA utilities
 License:        Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/shaptools/__init__.py 
new/shaptools-0.3.1/shaptools/__init__.py
--- old/shaptools-0.3.0/shaptools/__init__.py   2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/shaptools/__init__.py   2019-07-23 13:12:24.071282674 
+0200
@@ -6,4 +6,4 @@
 :since: 2018-11-15
 """
 
-__version__ = "0.3.0"
+__version__ = "0.3.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/shaptools/hana.py 
new/shaptools-0.3.1/shaptools/hana.py
--- old/shaptools-0.3.0/shaptools/hana.py       2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/shaptools/hana.py       2019-07-23 13:12:24.071282674 
+0200
@@ -18,6 +18,7 @@
 import fileinput
 import re
 import time
+import platform
 
 from shaptools import shell
 
@@ -55,7 +56,7 @@
 }
 
 
-class HanaInstance:
+class HanaInstance(object):
     """
     SAP HANA instance implementation
 
@@ -63,17 +64,21 @@
         sid (str): SAP HANA sid to enable
         inst (str): SAP HANA instance number
         password (str): HANA instance password
+        remote_host (str, opt): Remote host where the command will be executed
     """
 
     PATH = '/usr/sap/{sid}/HDB{inst}/'
-    INSTALL_EXEC = '{software_path}/DATA_UNITS/HDB_LCM_LINUX_X86_64/hdblcm'
+    INSTALL_EXEC = '{software_path}/DATA_UNITS/HDB_LCM_LINUX_{platform}/hdblcm'
+    SUPPORTED_PLATFORMS = [
+        'x86_64', 'ppc64le'
+    ]
     # SID is usualy written uppercased, but the OS user is always created 
lower case.
     HANAUSER = '{sid}adm'.lower()
     SYNCMODES = ['sync', 'syncmem', 'async']
     SUCCESSFULLY_REGISTERED = 0 # Node correctly registered as secondary node
     SSFS_DIFFERENT_ERROR = 149 # ssfs files are different in the two nodes 
error return code
 
-    def __init__(self, sid, inst, password):
+    def __init__(self, sid, inst, password, **kwargs):
         # Force instance nr always with 2 positions.
         inst = '{:0>2}'.format(inst)
         if not all(isinstance(i, basestring) for i in [sid, inst, password]):
@@ -84,6 +89,19 @@
         self.sid = sid
         self.inst = inst
         self._password = password
+        self.remote_host = kwargs.get('remote_host', None)
+
+    @classmethod
+    def get_platform(cls):
+        """
+        Get the SAP HANA installation folder by platform
+        """
+        current_platform = platform.machine()
+        logger = logging.getLogger('__name__')
+        logger.info('current platform is %s', current_platform)
+        if current_platform not in cls.SUPPORTED_PLATFORMS:
+            raise ValueError('not supported platform: 
{}'.format(current_platform))
+        return current_platform.upper()
 
     def _run_hana_command(self, cmd, exception=True):
         """
@@ -99,7 +117,7 @@
         """
         #TODO: Add absolute paths to hana commands using sid and inst number
         user = self.HANAUSER.format(sid=self.sid)
-        result = shell.execute_cmd(cmd, user, self._password)
+        result = shell.execute_cmd(cmd, user, self._password, self.remote_host)
 
         if exception and result.returncode != 0:
             raise HanaError('Error running hana command: 
{}'.format(result.cmd))
@@ -115,7 +133,7 @@
         """
         user = self.HANAUSER.format(sid=self.sid)
         try:
-            result = shell.execute_cmd('HDB info', user, self._password)
+            result = shell.execute_cmd('HDB info', user, self._password, 
self.remote_host)
             return not result.returncode
         except EnvironmentError as err: #FileNotFoundError is not compatible 
with python2
             self._logger.error(err)
@@ -145,7 +163,7 @@
 
     @classmethod
     def create_conf_file(
-            cls, software_path, conf_file, root_user, root_password):
+            cls, software_path, conf_file, root_user, root_password, 
remote_host=None):
         """
         Create SAP HANA configuration file
 
@@ -154,18 +172,21 @@
             conf_file (str): Path where configuration file will be created
             root_user (str): Root user name
             root_password (str): Root user password
+            remote_host (str, opt): Remote host where the command will be 
executed
+
         """
-        executable = cls.INSTALL_EXEC.format(software_path=software_path)
+        platform_folder = cls.get_platform()
+        executable = cls.INSTALL_EXEC.format(software_path=software_path, 
platform=platform_folder)
         cmd = '{executable} --action=install '\
             '--dump_configfile_template={conf_file}'.format(
                 executable=executable, conf_file=conf_file)
-        result = shell.execute_cmd(cmd, root_user, root_password)
+        result = shell.execute_cmd(cmd, root_user, root_password, remote_host)
         if result.returncode:
             raise HanaError('SAP HANA configuration file creation failed')
         return conf_file
 
     @classmethod
-    def install(cls, software_path, conf_file, root_user, password):
+    def install(cls, software_path, conf_file, root_user, password, 
remote_host=None):
         """
         Install SAP HANA platform providing a configuration file
 
@@ -174,13 +195,15 @@
             conf_file (str): Path to the configuration file
             root_user (str): Root user name
             password (str): Root user password
+            remote_host (str, opt): Remote host where the command will be 
executed
         """
         # TODO: mount partition if needed
         # TODO: do some integrity check stuff
-        executable = cls.INSTALL_EXEC.format(software_path=software_path)
+        platform_folder = cls.get_platform()
+        executable = cls.INSTALL_EXEC.format(software_path=software_path, 
platform=platform_folder)
         cmd = '{executable} -b --configfile={conf_file}'.format(
             executable=executable, conf_file=conf_file)
-        result = shell.execute_cmd(cmd, root_user, password)
+        result = shell.execute_cmd(cmd, root_user, password, remote_host)
         if result.returncode:
             raise HanaError('SAP HANA installation failed')
 
@@ -191,7 +214,7 @@
         cmd = '{installation_folder}/{sid}/hdblcm/hdblcm '\
             '--uninstall -b'.format(
                 installation_folder=installation_folder, sid=self.sid.upper())
-        result = shell.execute_cmd(cmd, root_user, password)
+        result = shell.execute_cmd(cmd, root_user, password, self.remote_host)
         if result.returncode:
             raise HanaError('SAP HANA uninstallation failed')
 
@@ -204,7 +227,7 @@
         """
         cmd = 'pidof hdb.sap{sid}_HDB{inst}'.format(
             sid=self.sid.upper(), inst=self.inst)
-        result = shell.execute_cmd(cmd)
+        result = self._run_hana_command(cmd, exception=False)
         return not result.returncode
 
     # pylint:disable=W1401
@@ -591,10 +614,11 @@
         user_name = kwargs.get('user_name', None)
         user_password = kwargs.get('user_password', None)
 
-        self._manage_ini_file(parameter_str=parameter_str, database=database,
-                              file_name=file_name, layer=layer, 
layer_name=layer_name,
-                              set_value=True, reconfig=reconfig, 
key_name=key_name,
-                              user_name=user_name, user_password=user_password)
+        self._manage_ini_file(
+            parameter_str=parameter_str, database=database,
+            file_name=file_name, layer=layer, layer_name=layer_name,
+            set_value=True, reconfig=reconfig, key_name=key_name,
+            user_name=user_name, user_password=user_password)
 
     def unset_ini_parameter(
             self, ini_parameter_names, database, file_name, layer,
@@ -631,7 +655,8 @@
         user_name = kwargs.get('user_name', None)
         user_password = kwargs.get('user_password', None)
 
-        self._manage_ini_file(parameter_str=parameter_str, database=database,
-                              file_name=file_name, layer=layer, 
layer_name=layer_name,
-                              set_value=False, reconfig=reconfig, 
key_name=key_name,
-                              user_name=user_name, user_password=user_password)
+        self._manage_ini_file(
+            parameter_str=parameter_str, database=database,
+            file_name=file_name, layer=layer, layer_name=layer_name,
+            set_value=False, reconfig=reconfig, key_name=key_name,
+            user_name=user_name, user_password=user_password)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/shaptools/shapcli.py 
new/shaptools-0.3.1/shaptools/shapcli.py
--- old/shaptools-0.3.0/shaptools/shapcli.py    2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/shaptools/shapcli.py    2019-07-23 13:12:24.071282674 
+0200
@@ -30,6 +30,23 @@
         return message
 
 
+class ConfigData(object):
+    """
+    Class to store the required configuration data
+    """
+
+    def __init__(self, data_dict, logger):
+        try:
+            self.sid = data_dict['sid']
+            self.instance = data_dict['instance']
+            self.password = data_dict['password']
+            self.remote = data_dict.get('remote', None)
+        except KeyError as err:
+            logger.error(err)
+            logger.error('Configuration file must have the sid, instance and 
password entries')
+            raise
+
+
 def setup_logger(level):
     """
     Setup logging
@@ -53,7 +70,7 @@
         '-v', '--verbosity',
         help='Python logging level. Options: DEBUG, INFO, WARN, ERROR (INFO by 
default)')
     parser.add_argument(
-        '-r', '--remotely',
+        '-r', '--remote',
         help='Run the command in other machine using ssh')
     parser.add_argument(
         '-c', '--config',
@@ -284,7 +301,7 @@
         # hana_instance.get_sr_status()
         cmd = 'HDBSettings.sh systemReplicationStatus.py{}'.format(
             ' --sapcontrol=1' if sr_args.sapcontrol else '')
-        hana_instance._run_hana_command(cmd)
+        hana_instance._run_hana_command(cmd, exception=False)
     elif str_args == 'disable':
         hana_instance.sr_disable_primary()
     elif str_args == 'cleanup':
@@ -309,12 +326,7 @@
     """
     with open(config_file, 'r') as f_ptr:
         json_data = json.load(f_ptr)
-    try:
-        return (json_data['sid'], json_data['instance'], json_data['password'])
-    except KeyError as err:
-        logger.error(err)
-        logger.error('Configuration file must have the sid, instance and 
password entries')
-        raise
+    return json_data
 
 
 # pylint:disable=W0212
@@ -325,19 +337,25 @@
     parser, args = parse_arguments()
     logger = setup_logger(args.verbosity or logging.DEBUG)
 
+    # If -c or --config flag is received data is loaded from the configuration 
file
     if args.config:
-        sid, instance, password = load_config_file(args.config, logger)
+        data = load_config_file(args.config, logger)
+        config_data = ConfigData(data, logger)
     elif args.sid and args.instance and args.password:
-        sid = args.sid
-        instance = args.instance
-        password = args.password
+        config_data = ConfigData(vars(args), logger)
     else:
         logger.info(
-            'Configuration file or sid,instance and passwords parameters must 
be provided\n')
+            'Configuration file or sid, instance and passwords parameters must 
be provided\n')
         parser.print_help()
         exit(1)
+
+    if args.remote:
+        config_data.remote = args.remote
+
     try:
-        hana_instance = hana.HanaInstance(sid, instance, password)
+        hana_instance = hana.HanaInstance(
+            config_data.sid, config_data.instance,
+            config_data.password, remote_host=config_data.remote)
         if vars(args).get('hana'):
             run_hana_subcommands(hana_instance, args, logger)
         elif vars(args).get('sr'):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/shaptools/shell.py 
new/shaptools-0.3.1/shaptools/shell.py
--- old/shaptools-0.3.0/shaptools/shell.py      2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/shaptools/shell.py      2019-07-23 13:12:24.071282674 
+0200
@@ -82,6 +82,26 @@
     return 'su -lc "{cmd}" {user}'.format(cmd=cmd, user=user)
 
 
+def format_remote_cmd(cmd, remote_host, user):
+    """
+    Format cmd to run remotely using ssh
+
+    Args:
+        cmd (str): Command to be executed
+        remote_host (str): User password
+        user (str): User to execute the command
+
+    Returns:
+        str: cmd adapted to be executed remotely
+    """
+    if not user:
+        raise ValueError('user must be provided')
+
+    cmd = 'ssh {user}@{remote_host} "bash --login -c \'{cmd}\'"'.format(
+        user=user, remote_host=remote_host, cmd=cmd)
+    return cmd
+
+
 def create_ssh_askpass(password, cmd):
     """
     Create ask pass command
@@ -98,7 +118,7 @@
     return ssh_askpass_str
 
 
-def execute_cmd(cmd, user=None, password=None):
+def execute_cmd(cmd, user=None, password=None, remote_host=None):
     """
     Execute a shell command. If user and password are provided it will be
     executed with this user.
@@ -107,6 +127,7 @@
         cmd (str): Command to be executed
         user (str, opt): User to execute the command
         password (str, opt): User password
+        remote_host (str, opt): Remote host where the command will be executed
 
     Returns:
         ProcessResult: ProcessResult instance storing subprocess returncode,
@@ -115,7 +136,11 @@
 
     LOGGER.debug('Executing command "%s" with user %s', cmd, user)
 
-    if user is not None:
+    if remote_host:
+        cmd = format_remote_cmd(cmd, remote_host, user)
+        LOGGER.debug('Command updated to "%s"', cmd)
+
+    elif user:
         cmd = format_su_cmd(cmd, user)
         LOGGER.debug('Command updated to "%s"', cmd)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/tests/hana_test.py 
new/shaptools-0.3.1/tests/hana_test.py
--- old/shaptools-0.3.0/tests/hana_test.py      2019-07-18 11:03:12.820176367 
+0200
+++ new/shaptools-0.3.1/tests/hana_test.py      2019-07-23 13:12:24.071282674 
+0200
@@ -78,6 +78,26 @@
             'provided sid, inst and password parameters must be str type' in
             str(err.exception))
 
+    @mock.patch('platform.machine')
+    def test_get_platform(self, mock_machine):
+        mock_machine.return_value = 'x86_64'
+        machine = hana.HanaInstance.get_platform()
+        self.assertEqual(machine, 'X86_64')
+        mock_machine.assert_called_once_with()
+
+        mock_machine.reset_mock()
+        mock_machine.return_value = 'ppc64le'
+        machine = hana.HanaInstance.get_platform()
+        self.assertEqual(machine, 'PPC64LE')
+        mock_machine.assert_called_once_with()
+
+    @mock.patch('platform.machine')
+    def test_get_platform_error(self, mock_machine):
+        mock_machine.return_value = 'ppc64'
+        with self.assertRaises(ValueError) as err:
+            hana.HanaInstance.get_platform()
+        self.assertTrue('not supported platform: {}'.format('ppc64') in 
str(err.exception))
+        mock_machine.assert_called_once_with()
 
     @mock.patch('shaptools.shell.execute_cmd')
     def test_run_hana_command(self, mock_execute):
@@ -88,7 +108,7 @@
 
         result = self._hana._run_hana_command('test command')
 
-        mock_execute.assert_called_once_with('test command', 'prdadm', 'pass')
+        mock_execute.assert_called_once_with('test command', 'prdadm', 'pass', 
None)
         self.assertEqual(proc_mock, result)
 
     @mock.patch('shaptools.shell.execute_cmd')
@@ -101,7 +121,7 @@
         with self.assertRaises(hana.HanaError) as err:
             self._hana._run_hana_command('test command')
 
-        mock_execute.assert_called_once_with('test command', 'prdadm', 'pass')
+        mock_execute.assert_called_once_with('test command', 'prdadm', 'pass', 
None)
         self.assertTrue(
             'Error running hana command: {}'.format(
                 'updated command') in str(err.exception))
@@ -114,7 +134,7 @@
 
         result = self._hana.is_installed()
 
-        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass')
+        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass', 
None)
 
         self.assertTrue(result)
 
@@ -126,7 +146,7 @@
 
         result = self._hana.is_installed()
 
-        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass')
+        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass', 
None)
 
         self.assertFalse(result)
 
@@ -140,7 +160,7 @@
 
         result = self._hana.is_installed()
 
-        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass')
+        mock_execute.assert_called_once_with('HDB info', 'prdadm', 'pass', 
None)
 
         self.assertFalse(result)
         logger.assert_called_once_with(error)
@@ -159,67 +179,80 @@
             **{'sid': 'PRD', 'password': 'Qwerty1234', 'system_user_password': 
'Qwerty1234'})
         self.assertTrue(filecmp.cmp(pwd+'/support/modified.conf', conf_file))
 
+    @mock.patch('shaptools.hana.HanaInstance.get_platform')
     @mock.patch('shaptools.shell.execute_cmd')
-    def test_create_conf_file(self, mock_execute):
+    def test_create_conf_file(self, mock_execute, mock_get_platform):
         proc_mock = mock.Mock()
         proc_mock.returncode = 0
         mock_execute.return_value = proc_mock
+        mock_get_platform.return_value = 'my_arch'
 
         conf_file = hana.HanaInstance.create_conf_file(
             'software_path', 'conf_file.conf', 'root', 'pass')
 
         mock_execute.assert_called_once_with(
-            'software_path/DATA_UNITS/HDB_LCM_LINUX_X86_64/hdblcm '
+            'software_path/DATA_UNITS/HDB_LCM_LINUX_my_arch/hdblcm '
             '--action=install --dump_configfile_template={conf_file}'.format(
-                conf_file='conf_file.conf'), 'root', 'pass')
+                conf_file='conf_file.conf'), 'root', 'pass', None)
+        mock_get_platform.assert_called_once_with()
         self.assertEqual('conf_file.conf', conf_file)
 
+    @mock.patch('shaptools.hana.HanaInstance.get_platform')
     @mock.patch('shaptools.shell.execute_cmd')
-    def test_create_conf_file_error(self, mock_execute):
+    def test_create_conf_file_error(self, mock_execute, mock_get_platform):
         proc_mock = mock.Mock()
         proc_mock.returncode = 1
         mock_execute.return_value = proc_mock
+        mock_get_platform.return_value = 'my_arch'
 
         with self.assertRaises(hana.HanaError) as err:
             hana.HanaInstance.create_conf_file(
                 'software_path', 'conf_file.conf', 'root', 'pass')
 
         mock_execute.assert_called_once_with(
-            'software_path/DATA_UNITS/HDB_LCM_LINUX_X86_64/hdblcm '
+            'software_path/DATA_UNITS/HDB_LCM_LINUX_my_arch/hdblcm '
             '--action=install --dump_configfile_template={conf_file}'.format(
-                conf_file='conf_file.conf'), 'root', 'pass')
+                conf_file='conf_file.conf'), 'root', 'pass', None)
+
+        mock_get_platform.assert_called_once_with()
 
         self.assertTrue(
             'SAP HANA configuration file creation failed' in 
str(err.exception))
 
+    @mock.patch('shaptools.hana.HanaInstance.get_platform')
     @mock.patch('shaptools.shell.execute_cmd')
-    def test_install(self, mock_execute):
+    def test_install(self, mock_execute, mock_get_platform):
         proc_mock = mock.Mock()
         proc_mock.returncode = 0
         mock_execute.return_value = proc_mock
+        mock_get_platform.return_value = 'my_arch'
 
         hana.HanaInstance.install(
             'software_path', 'conf_file.conf', 'root', 'pass')
 
         mock_execute.assert_called_once_with(
-            'software_path/DATA_UNITS/HDB_LCM_LINUX_X86_64/hdblcm '
+            'software_path/DATA_UNITS/HDB_LCM_LINUX_my_arch/hdblcm '
             '-b --configfile={conf_file}'.format(
-                conf_file='conf_file.conf'), 'root', 'pass')
+                conf_file='conf_file.conf'), 'root', 'pass', None)
+        mock_get_platform.assert_called_once_with()
 
+    @mock.patch('shaptools.hana.HanaInstance.get_platform')
     @mock.patch('shaptools.shell.execute_cmd')
-    def test_install_error(self, mock_execute):
+    def test_install_error(self, mock_execute, mock_get_platform):
         proc_mock = mock.Mock()
         proc_mock.returncode = 1
         mock_execute.return_value = proc_mock
+        mock_get_platform.return_value = 'my_arch'
 
         with self.assertRaises(hana.HanaError) as err:
             hana.HanaInstance.install(
                 'software_path', 'conf_file.conf', 'root', 'pass')
 
         mock_execute.assert_called_once_with(
-            'software_path/DATA_UNITS/HDB_LCM_LINUX_X86_64/hdblcm '
+            'software_path/DATA_UNITS/HDB_LCM_LINUX_my_arch/hdblcm '
             '-b --configfile={conf_file}'.format(
-                conf_file='conf_file.conf'), 'root', 'pass')
+                conf_file='conf_file.conf'), 'root', 'pass', None)
+        mock_get_platform.assert_called_once_with()
 
         self.assertTrue(
             'SAP HANA installation failed' in str(err.exception))
@@ -233,7 +266,7 @@
         self._hana.uninstall('root', 'pass')
 
         mock_execute.assert_called_once_with(
-            '/hana/shared/PRD/hdblcm/hdblcm --uninstall -b', 'root', 'pass')
+            '/hana/shared/PRD/hdblcm/hdblcm --uninstall -b', 'root', 'pass', 
None)
 
     @mock.patch('shaptools.shell.execute_cmd')
     def test_uninstall_error(self, mock_execute):
@@ -245,20 +278,19 @@
             self._hana.uninstall('root', 'pass', 'path')
 
         mock_execute.assert_called_once_with(
-            'path/PRD/hdblcm/hdblcm --uninstall -b', 'root', 'pass')
+            'path/PRD/hdblcm/hdblcm --uninstall -b', 'root', 'pass', None)
 
         self.assertTrue(
             'SAP HANA uninstallation failed' in str(err.exception))
 
     @mock.patch('shaptools.shell.execute_cmd')
     def test_is_running(self, mock_execute):
-        proc_mock = mock.Mock()
-        proc_mock.returncode = 0
-        mock_execute.return_value = proc_mock
-
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_result = mock.Mock(returncode=0)
+        self._hana._run_hana_command.return_value = mock_result
         result = self._hana.is_running()
-
-        mock_execute.assert_called_once_with('pidof hdb.sapPRD_HDB00')
+        mock_command.assert_called_once_with('pidof hdb.sapPRD_HDB00', 
exception=False)
         self.assertTrue(result)
 
     @mock.patch('subprocess.Popen')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/tests/shapcli_test.py 
new/shaptools-0.3.1/tests/shapcli_test.py
--- old/shaptools-0.3.0/tests/shapcli_test.py   2019-07-18 11:03:12.824176437 
+0200
+++ new/shaptools-0.3.1/tests/shapcli_test.py   2019-07-23 13:12:24.071282674 
+0200
@@ -79,6 +79,34 @@
 
         assert logger == mock_logger_instance
 
+    @mock.patch('shaptools.shapcli.json.load')
+    @mock.patch('shaptools.shapcli.open')
+    def test_config_data(self, mock_open, mock_json_load):
+
+        mock_logger = mock.Mock()
+
+        data = shapcli.ConfigData({'sid': 'prd', 'instance': '00', 'password': 
'pass'}, mock_logger)
+
+        assert data.sid == 'prd'
+        assert data.instance == '00'
+        assert data.password == 'pass'
+        assert data.remote == None
+
+        data = shapcli.ConfigData(
+            {'sid': 'prd', 'instance': '00', 'password': 'pass', 'remote': 
'host'}, mock_logger)
+
+        assert data.sid == 'prd'
+        assert data.instance == '00'
+        assert data.password == 'pass'
+        assert data.remote == 'host'
+
+        with pytest.raises(KeyError) as err:
+            shapcli.ConfigData({'sid': 'prd', 'instance': '00'}, mock_logger)
+
+        mock_logger.error.assert_has_calls([
+            mock.call('Configuration file must have the sid, instance and 
password entries')
+        ])
+
     @mock.patch('argparse.ArgumentParser')
     @mock.patch('shaptools.shapcli.parse_hana_arguments')
     @mock.patch('shaptools.shapcli.parse_sr_arguments')
@@ -103,7 +131,7 @@
         mock_argument_parser_instance.add_argument.assert_has_calls([
             mock.call('-v', '--verbosity',
                 help='Python logging level. Options: DEBUG, INFO, WARN, ERROR 
(INFO by default)'),
-            mock.call('-r', '--remotely',
+            mock.call('-r', '--remote',
                 help='Run the command in other machine using ssh'),
             mock.call('-c', '--config',
                 help='JSON configuration file with SAP HANA instance data 
(sid, instance and password)'),
@@ -410,13 +438,13 @@
         mock_hana_args = mock.Mock(sr='status', sapcontrol=True)
         shapcli.run_sr_subcommands(mock_hana_instance, mock_hana_args, 
mock_logger)
         cmd = 'HDBSettings.sh systemReplicationStatus.py --sapcontrol=1'
-        mock_hana_instance._run_hana_command.assert_called_once_with(cmd)
+        mock_hana_instance._run_hana_command.assert_called_once_with(cmd, 
exception=False)
         mock_hana_instance.reset_mock()
 
         mock_hana_args = mock.Mock(sr='status', sapcontrol=False)
         shapcli.run_sr_subcommands(mock_hana_instance, mock_hana_args, 
mock_logger)
         cmd = 'HDBSettings.sh systemReplicationStatus.py'
-        mock_hana_instance._run_hana_command.assert_called_once_with(cmd)
+        mock_hana_instance._run_hana_command.assert_called_once_with(cmd, 
exception=False)
         mock_hana_instance.reset_mock()
 
         mock_hana_args = mock.Mock(sr='disable')
@@ -467,23 +495,7 @@
         mock_json_load.return_value = {'sid': 'prd', 'instance': '00', 
'password': 'pass'}
 
         data = shapcli.load_config_file('config.json', mock_logger)
-        assert data[0] == 'prd'
-        assert data[1] == '00'
-        assert data[2] == 'pass'
-
-    @mock.patch('shaptools.shapcli.json.load')
-    @mock.patch('shaptools.shapcli.open')
-    def test_load_config_file_error(self, mock_open, mock_json_load):
-
-        mock_logger = mock.Mock()
-        mock_json_load.return_value = {'sid': 'prd', 'instance': '00'}
-
-        with pytest.raises(KeyError) as err:
-            shapcli.load_config_file('config.json', mock_logger)
-
-        mock_logger.error.assert_has_calls([
-            mock.call('Configuration file must have the sid, instance and 
password entries')
-        ])
+        assert data == {'sid': 'prd', 'instance': '00', 'password': 'pass'}
 
     @mock.patch('shaptools.shapcli.run_hana_subcommands')
     @mock.patch('shaptools.shapcli.hana.HanaInstance')
@@ -496,12 +508,13 @@
             mock_run_hana_subcommands):
 
         mock_parser = mock.Mock()
-        mock_args = mock.Mock(verbosity='INFO', config='config.json', 
hana=True)
+        mock_args = mock.Mock(verbosity='INFO', config='config.json', 
hana=True, remote=None)
         mock_logger = mock.Mock()
         mock_hana_instance = mock.Mock()
         mock_parse_arguments.return_value = [mock_parser, mock_args]
         mock_setup_logger.return_value = mock_logger
-        mock_load_config_file.return_value = ['prd', '00', 'pass']
+        mock_load_config_file.return_value = {
+            'sid': 'prd', 'instance': '00', 'password': 'pass', 'remote': 
'host'}
         mock_hana.return_value = mock_hana_instance
 
         shapcli.run()
@@ -509,7 +522,7 @@
         mock_parse_arguments.assert_called_once_with()
         mock_setup_logger.assert_called_once_with('INFO')
         mock_load_config_file.assert_called_once_with('config.json', 
mock_logger)
-        mock_hana.assert_called_once_with('prd', '00', 'pass')
+        mock_hana.assert_called_once_with('prd', '00', 'pass', 
remote_host='host')
         mock_run_hana_subcommands.assert_called_once_with(mock_hana_instance, 
mock_args, mock_logger)
 
     @mock.patch('shaptools.shapcli.run_sr_subcommands')
@@ -522,7 +535,8 @@
 
         mock_parser = mock.Mock()
         mock_args = mock.Mock(
-            verbosity='INFO', config=False, sid='qas', instance='01', 
password='mypass', sr=True)
+            verbosity='INFO', config=False, sid='qas', instance='01', 
password='mypass',
+            remote='remote', sr=True)
         mock_logger = mock.Mock()
         mock_hana_instance = mock.Mock()
         mock_parse_arguments.return_value = [mock_parser, mock_args]
@@ -533,7 +547,7 @@
 
         mock_parse_arguments.assert_called_once_with()
         mock_setup_logger.assert_called_once_with('INFO')
-        mock_hana.assert_called_once_with('qas', '01', 'mypass')
+        mock_hana.assert_called_once_with('qas', '01', 'mypass', 
remote_host='remote')
         mock_run_sr_subcommands.assert_called_once_with(mock_hana_instance, 
mock_args, mock_logger)
 
     @mock.patch('shaptools.shapcli.hana.HanaInstance')
@@ -544,7 +558,7 @@
 
         mock_parser = mock.Mock()
         mock_args = mock.Mock(
-            verbosity='INFO', config=False, sid='qas', instance='01', 
password='mypass')
+            verbosity='INFO', config=False, sid='qas', instance='01', 
password='mypass', remote=None)
         mock_logger = mock.Mock()
         mock_hana_instance = mock.Mock()
         mock_parse_arguments.return_value = [mock_parser, mock_args]
@@ -555,7 +569,7 @@
 
         mock_parse_arguments.assert_called_once_with()
         mock_setup_logger.assert_called_once_with('INFO')
-        mock_hana.assert_called_once_with('qas', '01', 'mypass')
+        mock_hana.assert_called_once_with('qas', '01', 'mypass', 
remote_host=None)
         mock_parser.print_help.assert_called_once_with()
 
 
@@ -580,7 +594,7 @@
         mock_parse_arguments.assert_called_once_with()
         mock_setup_logger.assert_called_once_with(logging.DEBUG)
         mock_logger.info.assert_called_once_with(
-            'Configuration file or sid,instance and passwords parameters must 
be provided\n')
+            'Configuration file or sid, instance and passwords parameters must 
be provided\n')
         mock_parser.print_help.assert_called_once_with()
 
     @mock.patch('shaptools.shapcli.run_sr_subcommands')
@@ -593,7 +607,8 @@
 
         mock_parser = mock.Mock()
         mock_args = mock.Mock(
-            verbosity='INFO', config=False, sid='qas', instance='01', 
password='mypass', sr=True)
+            verbosity='INFO', config=False, sid='qas', instance='01',
+            password='mypass', sr=True, remote=None)
         mock_logger = mock.Mock()
         mock_hana_instance = mock.Mock()
         mock_parse_arguments.return_value = [mock_parser, mock_args]
@@ -609,5 +624,5 @@
 
         mock_parse_arguments.assert_called_once_with()
         mock_setup_logger.assert_called_once_with('INFO')
-        mock_hana.assert_called_once_with('qas', '01', 'mypass')
+        mock_hana.assert_called_once_with('qas', '01', 'mypass', 
remote_host=None)
         mock_run_sr_subcommands.assert_called_once_with(mock_hana_instance, 
mock_args, mock_logger)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.3.0/tests/shell_test.py 
new/shaptools-0.3.1/tests/shell_test.py
--- old/shaptools-0.3.0/tests/shell_test.py     2019-07-18 11:03:12.824176437 
+0200
+++ new/shaptools-0.3.1/tests/shell_test.py     2019-07-23 13:12:24.071282674 
+0200
@@ -106,6 +106,19 @@
         cmd = shell.format_su_cmd('hdbnsutil -sr_enable --name=PRAGUE', 
'prdadm')
         self.assertEqual('su -lc "hdbnsutil -sr_enable --name=PRAGUE" prdadm', 
cmd)
 
+    def test_format_remote_cmd(self):
+        cmd = shell.format_remote_cmd('ls -la', 'remote', 'test')
+        self.assertEqual('ssh test@remote "bash --login -c \'ls -la\'"', cmd)
+
+        cmd = shell.format_remote_cmd('hdbnsutil -sr_enable --name=PRAGUE', 
'remote', 'prdadm')
+        self.assertEqual(
+            'ssh prdadm@remote "bash --login -c \'hdbnsutil -sr_enable 
--name=PRAGUE\'"', cmd)
+
+    def test_format_remote_cmd_error(self):
+        with self.assertRaises(ValueError) as err:
+            shell.format_remote_cmd('ls -la', 'remote', None)
+        self.assertTrue('user must be provided' in str(err.exception))
+
     def test_execute_cmd_popen(self):
         # This test is used to check popen correct usage
         result = shell.execute_cmd('ls -la')
@@ -139,6 +152,42 @@
 
         self.assertEqual(mock_process_inst, result)
 
+    @mock.patch('shaptools.shell.format_remote_cmd')
+    @mock.patch('shaptools.shell.ProcessResult')
+    @mock.patch('subprocess.Popen')
+    @mock.patch('logging.Logger.debug')
+    def test_execute_cmd_remote(
+            self, logger, mock_popen, mock_process, mock_format):
+
+        mock_format.return_value = 'updated command'
+
+        mock_popen_inst = mock.Mock()
+        mock_popen_inst.returncode = 5
+        mock_popen_inst.communicate.return_value = (b'out', b'err')
+        mock_popen.return_value = mock_popen_inst
+
+        mock_process_inst = mock.Mock()
+        mock_process.return_value = mock_process_inst
+
+        result = shell.execute_cmd('ls -la', 'test', 'pass', 'remote')
+
+        logger.assert_has_calls([
+            mock.call('Executing command "%s" with user %s', 'ls -la', 'test'),
+            mock.call('Command updated to "%s"', 'updated command')
+        ])
+
+        mock_format.assert_called_once_with('ls -la', 'remote', 'test')
+
+        mock_popen.assert_called_once_with(
+            ['updated', 'command'], stdout=subprocess.PIPE, 
stdin=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+
+        mock_popen_inst.communicate.assert_called_once_with(input=b'pass')
+
+        mock_process.assert_called_once_with('updated command', 5, b'out', 
b'err')
+
+        self.assertEqual(mock_process_inst, result)
+
     @mock.patch('shaptools.shell.format_su_cmd')
     @mock.patch('shaptools.shell.ProcessResult')
     @mock.patch('subprocess.Popen')


Reply via email to