Hello community,

here is the log from the commit of package python-shaptools for 
openSUSE:Factory checked in at 2019-06-12 13:17:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-shaptools (Old)
 and      /work/SRC/openSUSE:Factory/.python-shaptools.new.4811 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-shaptools"

Wed Jun 12 13:17:43 2019 rev:2 rq:709132 version:0.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-shaptools/python-shaptools.changes        
2019-05-08 15:16:34.413002909 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-shaptools.new.4811/python-shaptools.changes  
    2019-06-12 13:17:43.844600778 +0200
@@ -1,0 +2,24 @@
+Tue Jun 11 11:29:44 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.2.1 with fixed spec files. Now the package
+  is available from SLE12-SP2 to SLE15 versions
+
+-------------------------------------------------------------------
+Tue Jun  4 07:23:40 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.2.0 with the latest changes
+
+-------------------------------------------------------------------
+Wed May 29 12:26:08 UTC 2019 - Ayoub Belarbi ([email protected])
+
+- Update hdb connector to return metadata besides the query
+  records.
+
+-------------------------------------------------------------------
+Thu May 16 09:35:41 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Update SR registration process. Now the methods retries the
+  registration command until a successful return and copies the
+  SSFS files from the primary node as well
+
+-------------------------------------------------------------------

Old:
----
  shaptools-0.1.0.tar.gz

New:
----
  shaptools-0.2.1.tar.gz

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

Other differences:
------------------
++++++ python-shaptools.spec ++++++
--- /var/tmp/diff_new_pack.xPgRun/_old  2019-06-12 13:17:44.736599843 +0200
+++ /var/tmp/diff_new_pack.xPgRun/_new  2019-06-12 13:17:44.740599839 +0200
@@ -14,23 +14,29 @@
 
 # Please submit bugfixes or comments via http://bugs.opensuse.org/
 
+%if 0%{?suse_version} < 1500
+%bcond_with test
+%else
+%bcond_without test
+%endif
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-shaptools
-Version:        0.1.0
+Version:        0.2.1
 Release:        0
-License:        Apache-2.0
 Summary:        Python tools to interact with SAP HANA utilities
-Url:            https://github.com/SUSE/shaptools
+License:        Apache-2.0
 Group:          Development/Languages/Python
+Url:            https://github.com/SUSE/shaptools
 Source:         shaptools-%{version}.tar.gz
-BuildRequires:  python-rpm-macros
-BuildRequires:  %{python_module devel}
+%if %{with test}
+BuildRequires:  %{python_module mock}
+BuildRequires:  %{python_module pytest}
+%endif
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  unzip
 BuildRequires:  fdupes
+BuildRequires:  python-rpm-macros
 BuildArch:      noarch
-
 %python_subpackages
 
 %description
@@ -45,13 +51,19 @@
 %install
 %python_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
+# do not install tests
+%python_expand rm -r %{buildroot}%{$python_sitelib}/tests
+
+%if %{with test}
+%check
+%pytest tests
+%endif
 
 %files %{python_files}
-%doc CHANGELOG.md README.md
-# %license macro is not availabe on older releases
-%if 0%{?sle_version} <= 120300
-%doc LICENSE
+%if 0%{?sle_version:1} && 0%{?sle_version} < 120300
+%doc README.md LICENSE
 %else
+%doc README.md
 %license LICENSE
 %endif
 %{python_sitelib}/*

++++++ shaptools-0.1.0.tar.gz -> shaptools-0.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/CHANGELOG.md 
new/shaptools-0.2.1/CHANGELOG.md
--- old/shaptools-0.1.0/CHANGELOG.md    2019-04-26 12:20:21.376891183 +0200
+++ new/shaptools-0.2.1/CHANGELOG.md    1970-01-01 01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-# Version 0.1.0
-- First version of the project.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/README.md 
new/shaptools-0.2.1/README.md
--- old/shaptools-0.1.0/README.md       2019-04-26 12:20:21.376891183 +0200
+++ new/shaptools-0.2.1/README.md       2019-06-11 14:03:10.316276735 +0200
@@ -16,11 +16,11 @@
 h = hana.HanaInstance('prd', '00', 'Qwerty1234')
 
 if not h.is_installed():
-  conf_file = hana.HanaInstance(.create_conf_file(
+  conf_file = hana.HanaInstance.create_conf_file(
     '/sap_inst/51052481', '/home/myuser/hana.conf', 'root', 'root')
-  hana.HanaInstance(.update_conf_file(
+  hana.HanaInstance.update_conf_file(
     conf_file, sid='PRD', password='Qwerty1234', 
system_user_password='Qwerty1234')
-  hana.HanaInstance(.install('/sap_inst/51052481', conf_file, 'root', 'root')
+  hana.HanaInstance.install('/sap_inst/51052481', conf_file, 'root', 'root')
 
 if not h.is_running():
     h.start()
@@ -29,7 +29,7 @@
 
 h.create_user_key(
   'backupkey', 'hana01:30013', 'SYSTEM', 'Qwerty1234', 'SYSTEMDB')
-h.create_backup('backupkey', 'Qwerty1234', 'SYSTEMDB', 'backup')
+h.create_backup('SYSTEMDB', 'backup', 'backupkey', 'SYSTEM', 'Qwerty1234')
 h.sr_enable_primary('NUREMBERG')
 ```
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/python-shaptools.changes 
new/shaptools-0.2.1/python-shaptools.changes
--- old/shaptools-0.1.0/python-shaptools.changes        2019-04-26 
12:20:21.376891183 +0200
+++ new/shaptools-0.2.1/python-shaptools.changes        2019-06-11 
14:03:10.316276735 +0200
@@ -1,7 +1,31 @@
 -------------------------------------------------------------------
+Tue Jun 11 11:29:44 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.2.1 with fixed spec files. Now the package
+  is available from SLE12-SP2 to SLE15 versions
+
+-------------------------------------------------------------------
+Tue Jun  4 07:23:40 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Create package version 0.2.0 with the latest changes
+
+-------------------------------------------------------------------
+Wed May 29 12:26:08 UTC 2019 - Ayoub Belarbi ([email protected])
+
+- Update hdb connector to return metadata besides the query
+  records.
+
+-------------------------------------------------------------------
+Thu May 16 09:35:41 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
+
+- Update SR registration process. Now the methods retries the
+  registration command until a successful return and copies the
+  SSFS files from the primary node as well
+
+-------------------------------------------------------------------
 Tue Apr 23 11:04:53 UTC 2019 - Xabier Arbulu Insausti <[email protected]>
 
-- Remove enum34 dependency from code 
+- Remove enum34 dependency from code
 
 -------------------------------------------------------------------
 Wed Mar 6 11:01:10 UTC 2019 - [email protected]
@@ -26,4 +50,4 @@
 -------------------------------------------------------------------
 Thu Dec 20 08:33:10 UTC 2018 - [email protected]
 
-- First package version 
+- First package version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/python-shaptools.spec 
new/shaptools-0.2.1/python-shaptools.spec
--- old/shaptools-0.1.0/python-shaptools.spec   2019-04-26 12:20:21.376891183 
+0200
+++ new/shaptools-0.2.1/python-shaptools.spec   2019-06-11 14:03:10.316276735 
+0200
@@ -14,23 +14,29 @@
 
 # Please submit bugfixes or comments via http://bugs.opensuse.org/
 
+%if 0%{?suse_version} < 1500
+%bcond_with test
+%else
+%bcond_without test
+%endif
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-shaptools
-Version:        0.1.0
+Version:        0.2.1
 Release:        0
-License:        Apache-2.0
 Summary:        Python tools to interact with SAP HANA utilities
-Url:            https://github.com/SUSE/shaptools
+License:        Apache-2.0
 Group:          Development/Languages/Python
+Url:            https://github.com/SUSE/shaptools
 Source:         shaptools-%{version}.tar.gz
-BuildRequires:  python-rpm-macros
-BuildRequires:  %{python_module devel}
+%if %{with test}
+BuildRequires:  %{python_module mock}
+BuildRequires:  %{python_module pytest}
+%endif
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  unzip
 BuildRequires:  fdupes
+BuildRequires:  python-rpm-macros
 BuildArch:      noarch
-
 %python_subpackages
 
 %description
@@ -45,13 +51,19 @@
 %install
 %python_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
+# do not install tests
+%python_expand rm -r %{buildroot}%{$python_sitelib}/tests
+
+%if %{with test}
+%check
+%pytest tests
+%endif
 
 %files %{python_files}
-%doc CHANGELOG.md README.md
-# %license macro is not availabe on older releases
-%if 0%{?sle_version} <= 120300
-%doc LICENSE
+%if 0%{?sle_version:1} && 0%{?sle_version} < 120300
+%doc README.md LICENSE
 %else
+%doc README.md
 %license LICENSE
 %endif
 %{python_sitelib}/*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/setup.py new/shaptools-0.2.1/setup.py
--- old/shaptools-0.1.0/setup.py        2019-04-26 12:20:21.376891183 +0200
+++ new/shaptools-0.2.1/setup.py        2019-06-11 14:03:10.316276735 +0200
@@ -44,7 +44,9 @@
 
 DEPENDENCIES = read('requirements.txt').split()
 
-PACKAGE_DATA = {}
+PACKAGE_DATA = {
+    'shaptools': ['support/ssh_askpass']
+}
 DATA_FILES = []
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/shaptools/__init__.py 
new/shaptools-0.2.1/shaptools/__init__.py
--- old/shaptools-0.1.0/shaptools/__init__.py   2019-04-26 12:20:21.376891183 
+0200
+++ new/shaptools-0.2.1/shaptools/__init__.py   2019-06-11 14:03:10.316276735 
+0200
@@ -6,4 +6,4 @@
 :since: 2018-11-15
 """
 
-__version__ = "0.1.0"
+__version__ = "0.2.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/shaptools/hana.py 
new/shaptools-0.2.1/shaptools/hana.py
--- old/shaptools-0.1.0/shaptools/hana.py       2019-04-26 12:20:21.376891183 
+0200
+++ new/shaptools-0.2.1/shaptools/hana.py       2019-06-11 14:03:10.316276735 
+0200
@@ -17,6 +17,7 @@
 import logging
 import fileinput
 import re
+import time
 
 from shaptools import shell
 
@@ -69,6 +70,8 @@
     # 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):
         # Force instance nr always with 2 positions.
@@ -297,9 +300,34 @@
         cmd = 'hdbnsutil -sr_disable'
         self._run_hana_command(cmd)
 
+    def copy_ssfs_files(self, remote_host, primary_pass):
+        """
+        Copy the ssfs data and key files to the secondary node
+
+        Args:
+            primary_pass: Password of the primary node
+        """
+        user = self.HANAUSER.format(sid=self.sid)
+        sid_upper = self.sid.upper()
+        cmd = \
+            "scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "\
+            
"{user}@{remote_host}:/usr/sap/{sid}/SYS/global/security/rsecssfs/data/SSFS_{sid}.DAT
 "\
+            
"/usr/sap/{sid}/SYS/global/security/rsecssfs/data/SSFS_{sid}.DAT".format(
+                user=user, remote_host=remote_host, sid=sid_upper)
+        cmd = shell.create_ssh_askpass(primary_pass, cmd)
+        self._run_hana_command(cmd)
+
+        cmd = \
+            "scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "\
+            
"{user}@{remote_host}:/usr/sap/{sid}/SYS/global/security/rsecssfs/key/SSFS_{sid}.KEY
 "\
+            
"/usr/sap/{sid}/SYS/global/security/rsecssfs/key/SSFS_{sid}.KEY".format(
+                user=user, remote_host=remote_host, sid=sid_upper)
+        cmd = shell.create_ssh_askpass(primary_pass, cmd)
+        self._run_hana_command(cmd)
+
     def sr_register_secondary(
             self, name, remote_host, remote_instance,
-            replication_mode, operation_mode):
+            replication_mode, operation_mode, **kwargs):
         """
         Register SAP HANA system replication as secondary node
 
@@ -309,12 +337,39 @@
             remote_instance (str): Primary node instance
             replication_mode (str): Replication mode
             operation_mode (str): Operation mode
+            primary_password (str, optional): Password from node where system
+                replicationis is enabled. Current node password will be used by
+                default (xxxadm sap user password)
+            timeout (int, optional): Timeout to try to register the node in 
seconds
+            interval (int, optional): Retry interval in seconds
+
         """
+        timeout = kwargs.get('timeout', 0)
+        interval = kwargs.get('interval', 5)
+        primary_pass = kwargs.get('primary_password', self._password)
+
         remote_instance = '{:0>2}'.format(remote_instance)
         cmd = 'hdbnsutil -sr_register --name={} --remoteHost={} '\
               '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
                   name, remote_host, remote_instance, replication_mode, 
operation_mode)
-        self._run_hana_command(cmd)
+
+        current_time = time.clock()
+        current_timeout = current_time + timeout
+        while current_time <= current_timeout:
+            return_code = self._run_hana_command(cmd, False).returncode
+            if return_code == self.SUCCESSFULLY_REGISTERED:
+                break
+            elif return_code == self.SSFS_DIFFERENT_ERROR:
+                self.copy_ssfs_files(remote_host, primary_pass)
+                self._run_hana_command(cmd)
+                break
+            time.sleep(interval)
+            current_time = time.clock()
+            continue
+        else:
+            raise HanaError(
+                'System replication registration process failed after {} 
seconds'.format(
+                    timeout))
 
     def sr_unregister_secondary(self, primary_name):
         """
@@ -377,13 +432,13 @@
         Args:
             key_name (str, optional): Keystore to connect to sap hana db
             user_name (str, optional): User to connect to sap hana db
-            user_password (str, optiona): Password to connecto to sap hana db
+            user_password (str, optional): Password to connect to sap hana db
         """
         if kwargs.get('key_name', None):
-            cmd = 'hdbsql -U {}'.format(kwargs['key_name'])
+            cmd = 'hdbsql -i {} -U {}'.format(self.inst, kwargs['key_name'])
         elif kwargs.get('user_name', None) and kwargs.get('user_password', 
None):
-            cmd = 'hdbsql -u {} -p {}'.format(
-                kwargs['user_name'], kwargs['user_password'])
+            cmd = 'hdbsql -i {} -u {} -p {}'.format(
+                self.inst, kwargs['user_name'], kwargs['user_password'])
         else:
             raise ValueError(
                 'key_name or user_name/user_password parameters must be used')
@@ -448,3 +503,135 @@
         # (see SAPHana RA)
         status["status"] = SR_STATUS.get(result.returncode, SR_STATUS[12])
         return status
+
+    def _manage_ini_file(
+            self, parameter_str, database, file_name, layer,
+            **kwargs):
+        """
+        Construct command with HANA SQL to update configuration parameters in 
ini file
+
+        key_name or user_name/user_password parameters must be used
+        Args:
+            parameter_str (list): List containing HANA parameter details in a 
dict format
+            database (str): Database name
+            file_name (str): INI configuration file name
+            layer (str): Target layer for the configuration change 'SYSTEM', 
'HOST' or 'DATABASE'
+            layer_name (str, optional): Target either a tenant name or a 
target host name
+            reconfig (bool, optional): If apply changes to running HANA 
instance
+            set_value (bool, optional): Choose SET or UNSET operation to 
update parameters
+            key_name (str, optional): Keystore to connect to sap hana db
+            user_name (str, optional): User to connect to sap hana db
+            user_password (str, optional): Password to connect to sap hana db
+        """
+        layer_name = kwargs.get('layer_name', None)
+        reconfig = kwargs.get('reconfig', False)
+        set_value = kwargs.get('set_value', True)
+        key_name = kwargs.get('key_name', None)
+        user_name = kwargs.get('user_name', None)
+        user_password = kwargs.get('user_password', None)
+
+        hdbsql_cmd = self._hdbsql_connect(
+            key_name=key_name, user_name=user_name, 
user_password=user_password)
+
+        if layer in ('HOST', 'DATABASE') and layer_name is not None:
+            layer_name_str = ', \'{}\''.format(layer_name)
+        else:
+            layer_name_str = ''
+
+        set_str = 'SET' if set_value else 'UNSET'
+        reconfig_option = ' WITH RECONFIGURE' if reconfig else ''
+
+        cmd = ('{hdbsql_cmd} -d {db} '
+               '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', 
\'{layer}\'{layer_name}) '
+               '{set_str}{parameter_str}{reconfig};\\"'.format(
+                   hdbsql_cmd=hdbsql_cmd, db=database, file_name=file_name, 
layer=layer,
+                   layer_name=layer_name_str, set_str=set_str, 
parameter_str=parameter_str,
+                   reconfig=reconfig_option))
+
+        # TODO: return the HANA SQL Statement error if sql fails
+        self._run_hana_command(cmd)
+
+    def set_ini_parameter(
+            self, ini_parameter_values, database, file_name, layer,
+            **kwargs):
+        """
+        Set HANA configuration parameters in ini file
+
+        SQL syntax:
+        ALTER SYSTEM ALTER CONFIGURATION (<filename>, <layer>[, <layer_name>])
+        SET (<section_name_1>,<parameter_name_1>) = <parameter_value_1>,
+            (<section_name_2>,<parameter_name_2>) = <parameter_value_2>
+        WITH RECONFIGURE
+
+        key_name or user_name/user_password parameters must be used
+        Args:
+            ini_parameter_values (list): List containing HANA parameter details
+            where each entry is a dictionary like below:
+            {'section_name':'name', 'parameter_name':'param_name', 
'parameter_value':'value'}
+                section_name (str): Section name of parameter in ini file
+                parameter_name (str): Name of the parameter to be modified
+                parameter_value (str): The value of the parameter to be set
+            database (str): Database name
+            file_name (str): INI configuration file name
+            layer (str): Target layer for the configuration change 'SYSTEM', 
'HOST' or 'DATABASE'
+            layer_name (str, optional): Target either a tenant name or a 
target host name
+            reconfig (bool, optional): If apply changes to running HANA 
instance
+            key_name (str, optional): Keystore to connect to sap hana db
+            user_name (str, optional): User to connect to sap hana db
+            user_password (str, optional): Password to connect to sap hana db
+        """
+
+        parameter_str = ', '.join("(\'{}\',\'{}\')=\'{}\'".format(
+            params['section_name'], params['parameter_name'],
+            params['parameter_value']) for params in ini_parameter_values)
+
+        layer_name = kwargs.get('layer_name', None)
+        reconfig = kwargs.get('reconfig', False)
+        key_name = kwargs.get('key_name', None)
+        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)
+
+    def unset_ini_parameter(
+            self, ini_parameter_names, database, file_name, layer,
+            **kwargs):
+        """
+        Unset HANA configuration parameters in ini file
+
+        SQL syntax:
+        ALTER SYSTEM ALTER CONFIGURATION (<filename>, <layer>[, <layer_name>])
+        UNSET (<section_name>,<parameter_name>);
+
+        key_name or user_name/user_password parameters must be used
+        Args:
+            ini_parameter_names (list): List containing HANA parameter details
+            where each entry is a dictionary like below:
+            {'section_name':'name', 'parameter_name':'param_name'}
+                section_name (str): Section name of parameter in ini file
+                parameter_name (str): Name of the parameter to be modified
+            database (str): Database name
+            file_name (str): INI configuration file name
+            layer (str): Target layer for the configuration change 'SYSTEM', 
'HOST' or 'DATABASE'
+            layer_name (str, optional): Target either a tenant name or a 
target host name
+            reconfig (bool, optional): If apply changes to running HANA 
instance
+            key_name (str, optional): Keystore to connect to sap hana db
+            user_name (str, optional): User to connect to sap hana db
+            user_password (str, optional): Password to connect to sap hana db
+        """
+        parameter_str = ', '.join("(\'{}\',\'{}\')".format(
+            params['section_name'], params['parameter_name']) for params in 
ini_parameter_names)
+
+        layer_name = kwargs.get('layer_name', None)
+        reconfig = kwargs.get('reconfig', False)
+        key_name = kwargs.get('key_name', None)
+        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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/shaptools/hdb_connector/__init__.py 
new/shaptools-0.2.1/shaptools/hdb_connector/__init__.py
--- old/shaptools-0.1.0/shaptools/hdb_connector/__init__.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/shaptools/hdb_connector/__init__.py     2019-06-11 
14:03:10.316276735 +0200
@@ -0,0 +1,34 @@
+"""
+SAP HANA database connector factory
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-08
+"""
+
+try:
+    from shaptools.hdb_connector.connectors import dbapi_connector
+    API = 'dbapi' # pragma: no cover
+except ImportError:
+    try:
+        from shaptools.hdb_connector.connectors import pyhdb_connector
+        API = 'pyhdb' # pragma: no cover
+    except ImportError:
+        from shaptools.hdb_connector.connectors import base_connector
+        API = None
+
+
+class HdbConnector(object):
+    """
+    HDB factory connector
+    """
+
+    # pragma: no cover
+    def __new__(cls):
+        if API == 'dbapi':
+            return dbapi_connector.DbapiConnector() # pragma: no cover
+        elif API == 'pyhdb':
+            return pyhdb_connector.PyhdbConnector() # pragma: no cover
+        raise base_connector.DriverNotAvailableError('dbapi nor pyhdb are 
installed')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/shaptools/hdb_connector/connectors/base_connector.py 
new/shaptools-0.2.1/shaptools/hdb_connector/connectors/base_connector.py
--- old/shaptools-0.1.0/shaptools/hdb_connector/connectors/base_connector.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/shaptools/hdb_connector/connectors/base_connector.py    
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,102 @@
+"""
+Base connector
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-08
+"""
+
+import logging
+
+
+class BaseError(Exception):
+    """
+    Base exception
+    """
+
+
+class DriverNotAvailableError(Exception):
+    """
+    dbapi nor pyhdb are installed
+    """
+
+
+class ConnectionError(Exception):
+    """
+    Error during connection
+    """
+
+
+class QueryError(BaseError):
+    """
+    Error during query
+    """
+
+class QueryResult(object):
+    """
+    Class to manage query results
+
+    Args:
+        records (list of tuples): rows of a query result
+        metadata (tuple): Sequence of 7-item sequences that describe one 
result column
+    """
+
+    def __init__(self, records, metadata):
+        self._logger = logging.getLogger(__name__)
+        self.records = records
+        self.metadata = metadata
+
+    @classmethod
+    def load_cursor(cls, cursor):
+        """
+        load cursor and extract records and metadata
+
+        Args:
+            cursor (obj): Cursor object created by the connector (dbapi or 
pydhb)
+        """
+        records = cursor.fetchall() # TODO: catch any exceptions raised by 
fetchall()
+        metadata = cursor.description
+        instance = cls(records, metadata)
+        instance._logger.info('query records: %s', instance.records)
+        return instance
+
+class BaseConnector(object):
+    """
+    Base SAP HANA database connector
+    """
+
+    def __init__(self):
+        self._logger = logging.getLogger(__name__)
+        self._connection = None
+
+    def connect(self, host, port=30015, **kwargs):
+        """
+        Connect to the SAP HANA database
+
+        # TODO: Add option to connect using the key
+        # TODO: Add encryption options
+
+        Args:
+            host (str): Host where the database is running
+            port (int): Database port (3{inst_number}15 by default)
+            user (str): Existing username in the database
+            password (str): User password
+        """
+        raise NotImplementedError(
+            'method must be implemented in inherited connectors')
+
+    def query(self, sql_statement):
+        """
+        Query a sql statement and return response
+        """
+        raise NotImplementedError(
+            'method must be implemented in inherited connectors')
+
+    def disconnect(self):
+        """
+        Disconnect from SAP HANA database
+        """
+        raise NotImplementedError(
+            'method must be implemented in inherited connectors')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/shaptools/hdb_connector/connectors/dbapi_connector.py 
new/shaptools-0.2.1/shaptools/hdb_connector/connectors/dbapi_connector.py
--- old/shaptools-0.1.0/shaptools/hdb_connector/connectors/dbapi_connector.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/shaptools/hdb_connector/connectors/dbapi_connector.py   
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,72 @@
+"""
+SAP HANA database connector using official dbapi package
+
+How to install:
+https://help.sap.com/viewer/1efad1691c1f496b8b580064a6536c2d/Cloud/en-US/39eca89d94ca464ca52385ad50fc7dea.html
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-08
+"""
+
+from hdbcli import dbapi
+
+from shaptools.hdb_connector.connectors import base_connector
+
+
+class DbapiConnector(base_connector.BaseConnector):
+    """
+    Class to manage dbapi connection and queries
+    """
+
+    def __init__(self):
+        super(DbapiConnector, self).__init__()
+        self._logger.info('dbapi package loaded')
+
+    def connect(self, host, port=30015, **kwargs):
+        """
+        Connect to the SAP HANA database
+
+        # TODO: Add option to connect using the key
+        # TODO: Add encryption options
+
+        Args:
+            host (str): Host where the database is running
+            port (int): Database port (3{inst_number}15 by default)
+            user (str): Existing username in the database
+            password (str): User password
+        """
+        self._logger.info('connecting to SAP HANA database at %s:%s', host, 
port)
+        try:
+            self._connection = dbapi.connect(
+                address=host,
+                port=port,
+                user=kwargs.get('user'),
+                password=kwargs.get('password'),
+            )
+        except dbapi.Error as err:
+            raise base_connector.ConnectionError('connection failed: 
{}'.format(err))
+        self._logger.info('connected successfully')
+
+    def query(self, sql_statement):
+        """
+        Query a sql query result and return a result object
+        """
+        self._logger.info('executing sql query: %s', sql_statement)
+        try:
+            with self._connection.cursor() as cursor:
+                cursor.execute(sql_statement)
+                result = base_connector.QueryResult.load_cursor(cursor)
+        except dbapi.Error as err:
+            raise base_connector.QueryError('query failed: {}'.format(err))
+        return result
+
+    def disconnect(self):
+        """
+        Disconnect from SAP HANA database
+        """
+        self._logger.info('disconnecting from SAP HANA database')
+        self._connection.close()
+        self._logger.info('disconnected successfully')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/shaptools/hdb_connector/connectors/pyhdb_connector.py 
new/shaptools-0.2.1/shaptools/hdb_connector/connectors/pyhdb_connector.py
--- old/shaptools-0.1.0/shaptools/hdb_connector/connectors/pyhdb_connector.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/shaptools/hdb_connector/connectors/pyhdb_connector.py   
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,77 @@
+"""
+SAP HANA database connector using pyhdb open sourced package
+
+How to install:
+https://github.com/SAP/PyHDB
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-08
+"""
+
+import socket
+import pyhdb
+
+from shaptools.hdb_connector.connectors import base_connector
+
+
+class PyhdbConnector(base_connector.BaseConnector):
+    """
+    Class to manage pyhdb connection and queries
+    """
+
+    def __init__(self):
+        super(PyhdbConnector, self).__init__()
+        self._logger.info('pyhdb package loaded')
+
+    def connect(self, host, port=30015, **kwargs):
+        """
+        Connect to the SAP HANA database
+
+        # TODO: Add option to connect using the key
+        # TODO: Add encryption options
+
+        Args:
+            host (str): Host where the database is running
+            port (int): Database port (3{inst_number}15 by default)
+            user (str): Existing username in the database
+            password (str): User password
+        """
+        self._logger.info('connecting to SAP HANA database at %s:%s', host, 
port)
+        try:
+            self._connection = pyhdb.connect(
+                host=host,
+                port=port,
+                user=kwargs.get('user'),
+                password=kwargs.get('password'),
+            )
+        except socket.error as err:
+            raise base_connector.ConnectionError('connection failed: 
{}'.format(err))
+        self._logger.info('connected successfully')
+
+    def query(self, sql_statement):
+        """
+        Query a sql query result and return a result object
+        """
+        self._logger.info('executing sql query: %s', sql_statement)
+        try:
+            cursor = None
+            cursor = self._connection.cursor()
+            cursor.execute(sql_statement)
+            result = base_connector.QueryResult.load_cursor(cursor)
+        except pyhdb.exceptions.DatabaseError as err:
+            raise base_connector.QueryError('query failed: {}'.format(err))
+        finally:
+            if cursor:
+                cursor.close()
+        return result
+
+    def disconnect(self):
+        """
+        Disconnect from SAP HANA database
+        """
+        self._logger.info('disconnecting from SAP HANA database')
+        self._connection.close()
+        self._logger.info('disconnected successfully')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/shaptools/shell.py 
new/shaptools-0.2.1/shaptools/shell.py
--- old/shaptools-0.1.0/shaptools/shell.py      2019-04-26 12:20:21.376891183 
+0200
+++ new/shaptools-0.2.1/shaptools/shell.py      2019-06-11 14:03:10.316276735 
+0200
@@ -9,11 +9,13 @@
 """
 
 import logging
+import os
 import subprocess
 import shlex
 import re
 
 LOGGER = logging.getLogger('shell')
+ASKPASS_SCRIPT = 'support/ssh_askpass'
 
 
 class ProcessResult:
@@ -80,6 +82,22 @@
     return 'su -lc "{cmd}" {user}'.format(cmd=cmd, user=user)
 
 
+def create_ssh_askpass(password, cmd):
+    """
+    Create ask pass command
+    Note: subprocess os.setsid doesn't work as the user might have a password
+
+    Args:
+        password (str): ssh command password
+        cmd (str): Command to run
+    """
+    dirname = os.path.dirname(__file__)
+    ask_pass_script = os.path.join(dirname, ASKPASS_SCRIPT)
+    ssh_askpass_str = 'export SSH_ASKPASS={};export PASS={};export 
DISPLAY=:0;setsid {}'.format(
+        ask_pass_script, password, cmd)
+    return ssh_askpass_str
+
+
 def execute_cmd(cmd, user=None, password=None):
     """
     Execute a shell command. If user and password are provided it will be
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/shaptools/support/ssh_askpass 
new/shaptools-0.2.1/shaptools/support/ssh_askpass
--- old/shaptools-0.1.0/shaptools/support/ssh_askpass   1970-01-01 
01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/shaptools/support/ssh_askpass   2019-06-11 
14:03:10.316276735 +0200
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "$PASS"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/tests/hana_test.py 
new/shaptools-0.2.1/tests/hana_test.py
--- old/shaptools-0.1.0/tests/hana_test.py      2019-04-26 12:20:21.376891183 
+0200
+++ new/shaptools-0.2.1/tests/hana_test.py      2019-06-11 14:03:10.316276735 
+0200
@@ -342,14 +342,124 @@
         self._hana.sr_disable_primary()
         mock_command.assert_called_once_with('hdbnsutil -sr_disable')
 
-    def test_register(self):
-        mock_command = mock.Mock()
-        self._hana._run_hana_command = mock_command
+    @mock.patch('shaptools.shell.create_ssh_askpass')
+    def test_copy_ssfs_files(self, mock_sshpass):
+        mock_sshpass.side_effect = ['cmd1', 'cmd2']
+        self._hana._run_hana_command = mock.Mock()
+        self._hana.copy_ssfs_files('host', 'prim_pass')
+        mock_sshpass.assert_has_calls([
+            mock.call(
+                'prim_pass', "scp -o StrictHostKeyChecking=no -o 
UserKnownHostsFile=/dev/null "\
+                
'{user}@{remote_host}:/usr/sap/{sid}/SYS/global/security/rsecssfs/data/SSFS_{sid}.DAT
 '\
+                
'/usr/sap/{sid}/SYS/global/security/rsecssfs/data/SSFS_{sid}.DAT'.format(
+                    user='prdadm', remote_host='host', sid='PRD')),
+            mock.call(
+                'prim_pass', 'scp -o StrictHostKeyChecking=no -o 
UserKnownHostsFile=/dev/null '\
+                
'{user}@{remote_host}:/usr/sap/{sid}/SYS/global/security/rsecssfs/key/SSFS_{sid}.KEY
 '\
+                
'/usr/sap/{sid}/SYS/global/security/rsecssfs/key/SSFS_{sid}.KEY'.format(
+                    user='prdadm', remote_host='host', sid='PRD')),
+        ])
+        self._hana._run_hana_command.assert_has_calls([
+            mock.call('cmd1'),
+            mock.call('cmd2'),
+        ])
+
+
+    @mock.patch('time.clock')
+    def test_register_basic(self, mock_clock):
+        mock_clock.return_value = 0
+        self._hana._run_hana_command = mock.Mock()
+        result_mock = mock.Mock(returncode=0)
+        self._hana._run_hana_command.return_value = result_mock
         self._hana.sr_register_secondary('test', 'host', 1, 'sync', 'ops')
-        mock_command.assert_called_once_with(
+        self._hana._run_hana_command.assert_called_once_with(
             'hdbnsutil -sr_register --name={} --remoteHost={} '\
             '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
-            'test', 'host', '01', 'sync', 'ops'))
+            'test', 'host', '01', 'sync', 'ops'), False)
+
+    @mock.patch('time.clock')
+    def test_register_copy_ssfs(self, mock_clock):
+        mock_clock.return_value = 0
+        self._hana._run_hana_command = mock.Mock()
+        result_mock = mock.Mock(returncode=149)
+        self._hana.copy_ssfs_files = mock.Mock()
+        self._hana._run_hana_command.return_value = result_mock
+        self._hana.sr_register_secondary('test', 'host', 1, 'sync', 'ops', 
primary_pass='pass')
+        self._hana._run_hana_command.assert_has_calls([
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False),
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'))
+        ])
+        self._hana.copy_ssfs_files.assert_called_once_with('host', 'pass')
+
+    @mock.patch('time.clock')
+    @mock.patch('time.sleep')
+    def test_register_loop(self, mock_sleep, mock_clock):
+        mock_clock.side_effect = [0, 1, 2, 3]
+        self._hana._run_hana_command = mock.Mock()
+        result_mock1 = mock.Mock(returncode=1)
+        result_mock2 = mock.Mock(returncode=1)
+        result_mock3 = mock.Mock(returncode=0)
+
+        self._hana._run_hana_command.side_effect = [result_mock1, 
result_mock2, result_mock3]
+
+        self._hana.sr_register_secondary('test', 'host', 1, 'sync', 'ops', 
timeout=5, interval=2)
+
+        self._hana._run_hana_command.assert_has_calls([
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False),
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False),
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False)
+        ])
+        mock_sleep.assert_has_calls([mock.call(2), mock.call(2)])
+
+    @mock.patch('time.clock')
+    @mock.patch('time.sleep')
+    def test_register_error(self, mock_sleep, mock_clock):
+        mock_clock.side_effect = [0, 1, 2, 3]
+        self._hana._run_hana_command = mock.Mock()
+        result_mock1 = mock.Mock(returncode=1)
+        result_mock2 = mock.Mock(returncode=1)
+        result_mock3 = mock.Mock(returncode=1)
+
+        self._hana._run_hana_command.side_effect = [result_mock1, 
result_mock2, result_mock3]
+
+        with self.assertRaises(hana.HanaError) as err:
+            self._hana.sr_register_secondary(
+                'test', 'host', 1, 'sync', 'ops', timeout=2, interval=2)
+
+        self.assertTrue(
+            'System replication registration process failed after {} 
seconds'.format(2) in
+            str(err.exception))
+
+        self._hana._run_hana_command.assert_has_calls([
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False),
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False),
+            mock.call(
+                'hdbnsutil -sr_register --name={} --remoteHost={} '\
+                '--remoteInstance={} --replicationMode={} 
--operationMode={}'.format(
+                'test', 'host', '01', 'sync', 'ops'), False)
+        ])
+        mock_sleep.assert_has_calls([mock.call(2), mock.call(2), mock.call(2)])
 
     def test_unregister(self):
         mock_command = mock.Mock()
@@ -406,7 +516,8 @@
 
     def test_hdbsql_connect_key(self):
         cmd = self._hana._hdbsql_connect(key_name='mykey')
-        self.assertEqual('hdbsql -U mykey', cmd)
+        expected_cmd = 'hdbsql -i {} -U mykey'.format(self._hana.inst)
+        self.assertEqual(expected_cmd, cmd)
 
     def test_hdbsql_connect_key_error(self):
         with self.assertRaises(ValueError) as err:
@@ -417,7 +528,8 @@
 
     def test_hdbsql_connect_userpass(self):
         cmd = self._hana._hdbsql_connect(user_name='user', 
user_password='pass')
-        self.assertEqual('hdbsql -u user -p pass', cmd)
+        expected_cmd = 'hdbsql -i {} -u user -p pass'.format(self._hana.inst)
+        self.assertEqual(expected_cmd, cmd)
 
     def test_hdbsql_connect_userpass_error(self):
         with self.assertRaises(ValueError) as err:
@@ -506,6 +618,129 @@
                 'HDBSettings.sh systemReplicationStatus.py', exception=False)
             self.assertEqual(status, {"status": expect})
 
+    def test_set_ini_parameter(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.set_ini_parameter(
+            ini_parameter_values=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit', 
'parameter_value':'25000'}],
+            database='db', file_name='global.ini', layer='SYSTEM',
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\') 
SET'
+            '{ini_parameter_values};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini', layer='SYSTEM',
+            
ini_parameter_values='(\'memorymanager\',\'global_allocation_limit\')=\'25000\''))
+
+    def test_set_ini_parameter_layer(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.set_ini_parameter(
+            ini_parameter_values=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit', 
'parameter_value':'25000'}],
+            database='db', file_name='global.ini',
+            layer='HOST', layer_name='host01',
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\', 
\'{layer_name}\') '
+            'SET{ini_parameter_values};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini',
+            layer='HOST', layer_name='host01',
+            
ini_parameter_values='(\'memorymanager\',\'global_allocation_limit\')=\'25000\''))
+
+    def test_set_ini_parameter_reconfig(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.set_ini_parameter(
+            ini_parameter_values=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit', 
'parameter_value':'25000'}],
+            database='db', file_name='global.ini', layer='SYSTEM',
+            reconfig=True, key_name='key', user_name='key_user', 
user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\') 
SET'
+            '{ini_parameter_values}{reconfig};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini', layer='SYSTEM',
+            
ini_parameter_values='(\'memorymanager\',\'global_allocation_limit\')=\'25000\'',
+            reconfig=' WITH RECONFIGURE'))
+
+    def test_unset_ini_parameter(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.unset_ini_parameter(
+            ini_parameter_names=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit'}],
+            database='db', file_name='global.ini', layer='SYSTEM',
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\') 
UNSET'
+            '{ini_parameter_names};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini', layer='SYSTEM',
+            
ini_parameter_names='(\'memorymanager\',\'global_allocation_limit\')'))
+
+    def test_unset_ini_parameter_layer(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.unset_ini_parameter(
+            ini_parameter_names=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit'}],
+            database='db', file_name='global.ini', layer='HOST', 
layer_name='host01',
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\', 
\'{layer_name}\') UNSET'
+            '{ini_parameter_names};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini', layer='HOST', 
layer_name='host01',
+            
ini_parameter_names='(\'memorymanager\',\'global_allocation_limit\')'))
+
+    def test_unset_ini_parameter_reconfig(self):
+        mock_command = mock.Mock()
+        self._hana._run_hana_command = mock_command
+        mock_hdbsql = mock.Mock(return_value='hdbsql')
+        self._hana._hdbsql_connect = mock_hdbsql
+
+        self._hana.unset_ini_parameter(
+            ini_parameter_names=[{'section_name':'memorymanager',
+            'parameter_name':'global_allocation_limit'}],
+            database='db', file_name='global.ini', layer='SYSTEM', 
reconfig=True,
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_hdbsql.assert_called_once_with(
+            key_name='key', user_name='key_user', user_password='key_password')
+        mock_command.assert_called_once_with(
+            '{hdbsql} -d {db} '\
+            '\\"ALTER SYSTEM ALTER CONFIGURATION(\'{file_name}\', \'{layer}\') 
UNSET'
+            '{ini_parameter_names}{reconfig};\\"'.format(
+            hdbsql='hdbsql', db='db', file_name='global.ini', layer='SYSTEM',
+            
ini_parameter_names='(\'memorymanager\',\'global_allocation_limit\')',
+            reconfig=' WITH RECONFIGURE'))
 
 _hdbnsutil_sr_state_outputs = {
     "Not set (HDB daemon stopped)": """nameserver hana01:30001 not responding.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/tests/hdb_connector/__init__.py 
new/shaptools-0.2.1/tests/hdb_connector/__init__.py
--- old/shaptools-0.1.0/tests/hdb_connector/__init__.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/tests/hdb_connector/__init__.py 2019-06-11 
14:03:10.316276735 +0200
@@ -0,0 +1 @@
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/tests/hdb_connector/base_connect_test.py 
new/shaptools-0.2.1/tests/hdb_connector/base_connect_test.py
--- old/shaptools-0.1.0/tests/hdb_connector/base_connect_test.py        
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/tests/hdb_connector/base_connect_test.py        
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,124 @@
+"""
+Unitary tests for hdb_connector/connector/base_connector.py.
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-14
+"""
+
+# pylint:disable=C0103,C0111,W0212,W0611
+
+import os
+import sys
+import logging
+import unittest
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
'..')))
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class TestQueryResul(unittest.TestCase):
+    """
+    Unitary tests for base_connector.py QueryResult class
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Global setUp.
+        """
+
+        logging.basicConfig(level=logging.INFO)
+        from shaptools.hdb_connector.connectors import base_connector
+        cls._base_connector = base_connector
+
+    def setUp(self):
+        """
+        Test setUp.
+        """
+
+    def tearDown(self):
+        """
+        Test tearDown.
+        """
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        Global tearDown.
+        """
+
+    @mock.patch('logging.Logger.info')
+    def test_load_cursor(self, logger):
+        mock_cursor = mock.Mock()
+        mock_cursor.description = 'metadata'
+        mock_cursor.fetchall.return_value = ['data1', 'data2']
+        result = self._base_connector.QueryResult.load_cursor(mock_cursor)
+        logger.assert_called_once_with('query records: %s', ['data1', 'data2'])
+        self.assertEqual(result.records, ['data1', 'data2'])
+        self.assertEqual(result.metadata, 'metadata')
+
+
+class TestHana(unittest.TestCase):
+    """
+    Unitary tests for base_connector.py BaseConnector class
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Global setUp.
+        """
+
+        logging.basicConfig(level=logging.INFO)
+        sys.modules['hdbcli'] = mock.Mock()
+        sys.modules['pyhdb'] = mock.Mock()
+
+        from shaptools.hdb_connector.connectors import base_connector
+        cls._base_connector = base_connector
+
+    def setUp(self):
+        """
+        Test setUp.
+        """
+        self._conn = self._base_connector.BaseConnector()
+
+    def tearDown(self):
+        """
+        Test tearDown.
+        """
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        Global tearDown.
+        """
+        sys.modules.pop('hdbcli')
+        sys.modules.pop('pyhdb')
+
+    def test_connect(self):
+        with self.assertRaises(NotImplementedError) as err:
+            self._conn.connect('host')
+            self.assertTrue(
+                'method must be implemented in inherited connectors'
+                in str(err.exception))
+
+    def test_query(self):
+        with self.assertRaises(NotImplementedError) as err:
+            self._conn.query('query')
+            self.assertTrue(
+                'method must be implemented in inherited connectors'
+                in str(err.exception))
+
+    def test_disconnect(self):
+        with self.assertRaises(NotImplementedError) as err:
+            self._conn.disconnect()
+            self.assertTrue(
+                'method must be implemented in inherited connectors'
+                in str(err.exception))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/tests/hdb_connector/dbapi_connector_test.py 
new/shaptools-0.2.1/tests/hdb_connector/dbapi_connector_test.py
--- old/shaptools-0.1.0/tests/hdb_connector/dbapi_connector_test.py     
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/tests/hdb_connector/dbapi_connector_test.py     
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,138 @@
+"""
+Unitary tests for hdb_connector/connector/dbapi_connector.py.
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-14
+"""
+
+# pylint:disable=C0103,C0111,W0212,W0611
+
+import os
+import sys
+import logging
+import unittest
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
'..')))
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class DbapiException(Exception):
+    """
+    dbapi.Error mock exception
+    """
+
+
+class TestDbapiConnector(unittest.TestCase):
+    """
+    Unitary tests for dbapi_connector.py.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Global setUp.
+        """
+
+        logging.basicConfig(level=logging.INFO)
+        sys.modules['hdbcli'] = mock.Mock()
+        from shaptools.hdb_connector.connectors import dbapi_connector
+        cls._dbapi_connector = dbapi_connector
+
+    def setUp(self):
+        """
+        Test setUp.
+        """
+        self._conn = self._dbapi_connector.DbapiConnector()
+
+    def tearDown(self):
+        """
+        Test tearDown.
+        """
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        Global tearDown.
+        """
+        sys.modules.pop('hdbcli')
+
+    @mock.patch('shaptools.hdb_connector.connectors.dbapi_connector.dbapi')
+    @mock.patch('logging.Logger.info')
+    def test_connect(self, mock_logger, mock_dbapi):
+        self._conn.connect('host', 1234, user='user', password='pass')
+        mock_dbapi.connect.assert_called_once_with(
+            address='host', port=1234, user='user', password='pass')
+        mock_logger.assert_has_calls([
+            mock.call('connecting to SAP HANA database at %s:%s', 'host', 
1234),
+            mock.call('connected successfully')
+        ])
+
+    @mock.patch('shaptools.hdb_connector.connectors.dbapi_connector.dbapi')
+    @mock.patch('logging.Logger.info')
+    def test_connect_error(self, mock_logger, mock_dbapi):
+        mock_dbapi.Error = DbapiException
+        mock_dbapi.connect.side_effect = DbapiException('error')
+        with 
self.assertRaises(self._dbapi_connector.base_connector.ConnectionError) as err:
+            self._conn.connect('host', 1234, user='user', password='pass')
+
+        self.assertTrue('connection failed: {}'.format('error') in 
str(err.exception))
+        mock_dbapi.connect.assert_called_once_with(
+            address='host', port=1234, user='user', password='pass')
+
+        mock_logger.assert_called_once_with(
+            'connecting to SAP HANA database at %s:%s', 'host', 1234)
+
+    
@mock.patch('shaptools.hdb_connector.connectors.base_connector.QueryResult')
+    @mock.patch('logging.Logger.info')
+    def test_query(self, mock_logger, mock_result):
+        cursor_mock_instance = mock.Mock()
+        cursor_mock = mock.Mock(return_value=cursor_mock_instance)
+        mock_result_inst = mock.Mock()
+        mock_result_inst.records = ['data1', 'data2']
+        mock_result_inst.metadata = 'metadata'
+        mock_result.load_cursor.return_value = mock_result_inst
+        context_manager_mock = mock.Mock(
+            __enter__ = cursor_mock,
+            __exit__ = mock.Mock()
+        )
+        self._conn._connection = mock.Mock()
+        self._conn._connection.cursor.return_value = context_manager_mock
+
+        result = self._conn.query('query')
+
+        cursor_mock_instance.execute.assert_called_once_with('query')
+        mock_result.load_cursor.assert_called_once_with(cursor_mock_instance)
+
+        self.assertEqual(result.records, ['data1', 'data2'])
+        self.assertEqual(result.metadata, 'metadata')
+        mock_logger.assert_called_once_with('executing sql query: %s', 'query')
+
+    @mock.patch('shaptools.hdb_connector.connectors.dbapi_connector.dbapi')
+    @mock.patch('logging.Logger.info')
+    def test_query_error(self, mock_logger, mock_dbapi):
+        mock_dbapi.Error = DbapiException
+        self._conn._connection = mock.Mock()
+        self._conn._connection.cursor.side_effect = DbapiException('error')
+        with 
self.assertRaises(self._dbapi_connector.base_connector.QueryError) as err:
+            self._conn.query('query')
+
+        self.assertTrue('query failed: {}'.format('error') in 
str(err.exception))
+        self._conn._connection.cursor.assert_called_once_with()
+        mock_logger.assert_called_once_with('executing sql query: %s', 'query')
+
+    @mock.patch('logging.Logger.info')
+    def test_disconnect(self, mock_logger):
+        self._conn._connection = mock.Mock()
+        self._conn.disconnect()
+        self._conn._connection.close.assert_called_once_with()
+        mock_logger.assert_has_calls([
+            mock.call('disconnecting from SAP HANA database'),
+            mock.call('disconnected successfully')
+        ])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/tests/hdb_connector/init_test.py 
new/shaptools-0.2.1/tests/hdb_connector/init_test.py
--- old/shaptools-0.1.0/tests/hdb_connector/init_test.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/tests/hdb_connector/init_test.py        2019-06-11 
14:03:10.316276735 +0200
@@ -0,0 +1,60 @@
+"""
+Unitary tests for hdb_connector/__init__.py.
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-14
+"""
+
+# pylint:disable=C0103,C0111,W0212,W0611
+
+import os
+import sys
+import logging
+import unittest
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
'..')))
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+from shaptools.hdb_connector.connectors import base_connector
+
+class TestInit(unittest.TestCase):
+    """
+    Unitary tests for __init__.py.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Global setUp.
+        """
+
+        logging.basicConfig(level=logging.INFO)
+
+    def setUp(self):
+        """
+        Test setUp.
+        """
+
+    def tearDown(self):
+        """
+        Test tearDown.
+        """
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        Global tearDown.
+        """
+
+    def test_error(self):
+        from shaptools import hdb_connector
+        with self.assertRaises(base_connector.DriverNotAvailableError) as err:
+            hdb_connector.HdbConnector()
+        self.assertTrue('dbapi nor pyhdb are installed' in str(err.exception))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/shaptools-0.1.0/tests/hdb_connector/pyhdb_connector_test.py 
new/shaptools-0.2.1/tests/hdb_connector/pyhdb_connector_test.py
--- old/shaptools-0.1.0/tests/hdb_connector/pyhdb_connector_test.py     
1970-01-01 01:00:00.000000000 +0100
+++ new/shaptools-0.2.1/tests/hdb_connector/pyhdb_connector_test.py     
2019-06-11 14:03:10.316276735 +0200
@@ -0,0 +1,154 @@
+"""
+Unitary tests for hdb_connector/connector/pyhdb_connector.py.
+
+:author: xarbulu
+:organization: SUSE LLC
+:contact: [email protected]
+
+:since: 2019-05-14
+"""
+
+# pylint:disable=C0103,C0111,W0212,W0611
+
+import os
+import sys
+import logging
+import unittest
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
'..')))
+
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
+
+class PyhdbException(Exception):
+    """
+    pyhdb.exceptions mock exception
+    """
+
+
+class TestHDBConnector(unittest.TestCase):
+    """
+    Unitary tests for pyhdb_connector.py.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Global setUp.
+        """
+
+        logging.basicConfig(level=logging.INFO)
+        sys.modules['pyhdb'] = mock.Mock()
+        from shaptools.hdb_connector.connectors import pyhdb_connector
+        cls._pyhdb_connector = pyhdb_connector
+
+    def setUp(self):
+        """
+        Test setUp.
+        """
+        self._conn = self._pyhdb_connector.PyhdbConnector()
+
+    def tearDown(self):
+        """
+        Test tearDown.
+        """
+
+    @classmethod
+    def tearDownClass(cls):
+        """
+        Global tearDown.
+        """
+        sys.modules.pop('pyhdb')
+
+    @mock.patch('shaptools.hdb_connector.connectors.pyhdb_connector.pyhdb')
+    @mock.patch('logging.Logger.info')
+    def test_connect(self, mock_logger, mock_pyhdb):
+        self._conn.connect('host', 1234, user='user', password='pass')
+        mock_pyhdb.connect.assert_called_once_with(
+            host='host', port=1234, user='user', password='pass')
+        mock_logger.assert_has_calls([
+            mock.call('connecting to SAP HANA database at %s:%s', 'host', 
1234),
+            mock.call('connected successfully')
+        ])
+
+    @mock.patch('shaptools.hdb_connector.connectors.pyhdb_connector.socket')
+    @mock.patch('shaptools.hdb_connector.connectors.pyhdb_connector.pyhdb')
+    @mock.patch('logging.Logger.info')
+    def test_connect_error(self, mock_logger, mock_pyhdb, mock_socket):
+        mock_socket.error = PyhdbException
+        mock_pyhdb.connect.side_effect = mock_socket.error('error')
+        with 
self.assertRaises(self._pyhdb_connector.base_connector.ConnectionError) as err:
+            self._conn.connect('host', 1234, user='user', password='pass')
+
+        self.assertTrue('connection failed: {}'.format('error') in 
str(err.exception))
+        mock_pyhdb.connect.assert_called_once_with(
+            host='host', port=1234, user='user', password='pass')
+
+        mock_logger.assert_called_once_with(
+            'connecting to SAP HANA database at %s:%s', 'host', 1234)
+
+    
@mock.patch('shaptools.hdb_connector.connectors.base_connector.QueryResult')
+    @mock.patch('logging.Logger.info')
+    def test_query(self, mock_logger, mock_result):
+
+        mock_cursor = mock.Mock()
+        self._conn._connection = mock.Mock()
+        self._conn._connection.cursor.return_value = mock_cursor
+
+        mock_result_inst = mock.Mock()
+        mock_result_inst.records = ['data1', 'data2']
+        mock_result_inst.metadata = 'metadata'
+        mock_result.load_cursor.return_value = mock_result_inst
+
+        result = self._conn.query('query')
+
+        mock_cursor.execute.assert_called_once_with('query')
+        mock_result.load_cursor.assert_called_once_with(mock_cursor)
+
+        self.assertEqual(result.records, ['data1', 'data2'])
+        self.assertEqual(result.metadata, 'metadata')
+        mock_logger.assert_called_once_with('executing sql query: %s', 'query')
+        mock_cursor.close.assert_called_once_with()
+
+    @mock.patch('shaptools.hdb_connector.connectors.pyhdb_connector.pyhdb')
+    @mock.patch('logging.Logger.info')
+    def test_query_error(self, mock_logger, mock_pyhdb):
+        mock_pyhdb.exceptions.DatabaseError = PyhdbException
+        self._conn._connection = mock.Mock()
+        self._conn._connection.cursor.side_effect = PyhdbException('error')
+        with 
self.assertRaises(self._pyhdb_connector.base_connector.QueryError) as err:
+            self._conn.query('query')
+
+        self.assertTrue('query failed: {}'.format('error') in 
str(err.exception))
+        self._conn._connection.cursor.assert_called_once_with()
+        mock_logger.assert_called_once_with('executing sql query: %s', 'query')
+
+    @mock.patch('shaptools.hdb_connector.connectors.pyhdb_connector.pyhdb')
+    @mock.patch('logging.Logger.info')
+    def test_query_error_execute(self, mock_logger, mock_pyhdb):
+        mock_pyhdb.exceptions.DatabaseError = PyhdbException
+        self._conn._connection = mock.Mock()
+        cursor_mock = mock.Mock()
+        self._conn._connection.cursor.return_value = cursor_mock
+        cursor_mock.execute = mock.Mock()
+        cursor_mock.execute.side_effect = PyhdbException('error')
+        with 
self.assertRaises(self._pyhdb_connector.base_connector.QueryError) as err:
+            self._conn.query('query')
+
+        self.assertTrue('query failed: {}'.format('error') in 
str(err.exception))
+        self._conn._connection.cursor.assert_called_once_with()
+        mock_logger.assert_called_once_with('executing sql query: %s', 'query')
+        cursor_mock.close.assert_called_once_with()
+
+    @mock.patch('logging.Logger.info')
+    def test_disconnect(self, mock_logger):
+        self._conn._connection = mock.Mock()
+        self._conn.disconnect()
+        self._conn._connection.close.assert_called_once_with()
+        mock_logger.assert_has_calls([
+            mock.call('disconnecting from SAP HANA database'),
+            mock.call('disconnected successfully')
+        ])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shaptools-0.1.0/tests/shell_test.py 
new/shaptools-0.2.1/tests/shell_test.py
--- old/shaptools-0.1.0/tests/shell_test.py     2019-04-26 12:20:21.380891212 
+0200
+++ new/shaptools-0.2.1/tests/shell_test.py     2019-06-11 14:03:10.316276735 
+0200
@@ -172,3 +172,16 @@
         mock_process.assert_called_once_with('updated command', 5, b'out', 
b'err')
 
         self.assertEqual(mock_process_inst, result)
+
+    @mock.patch('os.path')
+    def test_create_ssh_askpass(self, mock_path):
+
+
+        mock_path.dirname.return_value = 'file'
+        mock_path.join.return_value = 'file/support/ssh_askpass'
+
+        result = shell.create_ssh_askpass('pass', 'my_command')
+
+        self.assertEqual(
+            'export SSH_ASKPASS=file/support/ssh_askpass;export 
PASS=pass;export DISPLAY=:0;setsid my_command',
+            result)


Reply via email to