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)