Hello community, here is the log from the commit of package salt-shaptools for openSUSE:Factory checked in at 2020-05-15 23:52:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt-shaptools (Old) and /work/SRC/openSUSE:Factory/.salt-shaptools.new.2738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt-shaptools" Fri May 15 23:52:41 2020 rev:13 rq:805831 version:0.3.4 Changes: -------- --- /work/SRC/openSUSE:Factory/salt-shaptools/salt-shaptools.changes 2020-03-08 22:21:29.143979291 +0100 +++ /work/SRC/openSUSE:Factory/.salt-shaptools.new.2738/salt-shaptools.changes 2020-05-15 23:52:44.657593591 +0200 @@ -1,0 +2,20 @@ +Fri Mar 27 18:06:32 UTC 2020 - Simranpal Singh <simranpal.si...@suse.com> + +- Version 0.3.4 + * Add new salt module and state to extract the sar files using SAPCAR + +------------------------------------------------------------------- +Fri Mar 20 14:49:04 UTC 2020 - Xabier Arbulu <xarb...@suse.com> + +- Version 0.3.3 + * Add new salt state to extract the HANA python dbapi client + +------------------------------------------------------------------- +Thu Mar 5 10:03:39 UTC 2020 - Xabier Arbulu <xarb...@suse.com> + +- Version 0.3.2 + * Add a new salt state method to update corosync configuration + file + * Fix travis file to install the py packages in develop mode + +------------------------------------------------------------------- Old: ---- salt-shaptools-0.3.1.tar.gz New: ---- salt-shaptools-0.3.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt-shaptools.spec ++++++ --- /var/tmp/diff_new_pack.aFKARX/_old 2020-05-15 23:52:45.757595711 +0200 +++ /var/tmp/diff_new_pack.aFKARX/_new 2020-05-15 23:52:45.761595719 +0200 @@ -19,7 +19,7 @@ # See also https://en.opensuse.org/openSUSE:Specfile_guidelines Name: salt-shaptools -Version: 0.3.1 +Version: 0.3.4 Release: 0 Summary: Salt modules and states for SAP Applications and SLE-HA components management @@ -51,7 +51,8 @@ cp -R salt/states/netweavermod.py %{buildroot}/srv/salt/_states cp -R salt/modules/saptunemod.py %{buildroot}/srv/salt/_modules cp -R salt/states/saptunemod.py %{buildroot}/srv/salt/_states - +cp -R salt/modules/sapcarmod.py %{buildroot}/srv/salt/_modules +cp -R salt/states/sapcarmod.py %{buildroot}/srv/salt/_states %files %defattr(-,root,root,-) ++++++ salt-shaptools-0.3.1.tar.gz -> salt-shaptools-0.3.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt/modules/hanamod.py new/salt-shaptools-0.3.4/salt/modules/hanamod.py --- old/salt-shaptools-0.3.1/salt/modules/hanamod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/salt/modules/hanamod.py 2020-03-31 20:02:39.980481668 +0200 @@ -22,9 +22,19 @@ # Import Python libs from __future__ import absolute_import, unicode_literals, print_function + +import logging import time +import re + +try: # pragma: no cover + import importlib as imp +except ImportError: # pragma: no cover + import imp from salt import exceptions +from salt.utils import files as salt_files + # Import third party libs try: @@ -35,8 +45,19 @@ except ImportError: # pragma: no cover HAS_HANA = False +LOGGER = logging.getLogger(__name__) + __virtualname__ = 'hana' +LABEL_FILE = 'LABEL.ASC' +LABELIDX_FILE = 'LABELIDX.ASC' + + +class SapFolderNotFoundError(Exception): + ''' + SAP folder not found exception + ''' + def __virtual__(): # pragma: no cover ''' @@ -865,17 +886,17 @@ ''' Wait until HANA is ready trying to connect to the database - host: + host Host where HANA is running - port: + port HANA database port - user: + user User to connect to the databse - password: + password Password to connect to the database - timeout: + timeout Timeout to try to connect to the database - interval: + interval Interval to try the connection CLI Example: @@ -900,4 +921,75 @@ raise exceptions.CommandExecutionError( 'HANA database not available after {} seconds in {}:{}'.format( timeout, host, port - )) \ No newline at end of file + )) + + +def reload_hdb_connector(): + ''' + As hdb_connector uses pyhdb or dbapi, if these packages are installed on the fly, + we need to reload the connector to import the correct api + ''' + imp.reload(hdb_connector) + + +def _find_sap_folder(software_folders, folder_pattern): + ''' + Find a SAP folder following a recursive approach using the LABEL and LABELIDX files + ''' + for folder in software_folders: + label = '{}/{}'.format(folder, LABEL_FILE) + try: + with salt_files.fopen(label, 'r') as label_file_ptr: + label_content = label_file_ptr.read().strip() + if folder_pattern.match(label_content): + return folder + else: + LOGGER.debug( + '%s folder does not contain %s pattern', folder, folder_pattern.pattern) + except IOError: + LOGGER.debug('%s file not found in %s. Skipping folder', LABEL_FILE, folder) + + labelidx = '{}/{}'.format(folder, LABELIDX_FILE) + try: + with salt_files.fopen(labelidx, 'r') as labelidx_file_ptr: + labelidx_content = labelidx_file_ptr.read().splitlines() + new_folders = [ + '{}/{}'.format(folder, new_folder) for new_folder in labelidx_content] + try: + return _find_sap_folder(new_folders, folder_pattern) + except SapFolderNotFoundError: + continue + except IOError: + LOGGER.debug('%s file not found in %s. Skipping folder', LABELIDX_FILE, folder) + + raise SapFolderNotFoundError( + 'SAP folder with {} pattern not found'.format(folder_pattern.pattern)) + + +def extract_pydbapi( + name, + software_folders, + output_dir, + hana_version='20'): + ''' + Extract HANA pydbapi python client from the provided software folders + + name + Name of the package that needs to be installed + software_folders + Folders list where the HANA client is located. It's used as a list as the pydbapi client + will be found automatically among different folders and providing several folders is a + standard way in SAP landscape + output_dir + Folder where the package is extracted + ''' + current_platform = hana.HanaInstance.get_platform() + hana_client_pattern = re.compile('^HDB_CLIENT:{}.*:{}:.*'.format( + hana_version, current_platform)) + try: + hana_client_folder = _find_sap_folder(software_folders, hana_client_pattern) + except SapFolderNotFoundError: + raise exceptions.CommandExecutionError('HANA client not found') + pydbapi_file = '{}/client/{}'.format(hana_client_folder, name) + __salt__['archive.tar'](options='xvf', tarfile=pydbapi_file, dest=output_dir) + return pydbapi_file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt/modules/sapcarmod.py new/salt-shaptools-0.3.4/salt/modules/sapcarmod.py --- old/salt-shaptools-0.3.1/salt/modules/sapcarmod.py 1970-01-01 01:00:00.000000000 +0100 +++ new/salt-shaptools-0.3.4/salt/modules/sapcarmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +''' +Module to provide SAP tools functionality to Salt + +.. versionadded:: pending + +:maintainer: Simranpal Singh <sisi...@suse.com> +:maturity: alpha +:depends: ``shaptools`` Python module +:platform: all + +:configuration: This module requires the shaptools package +''' + + +# Import Python libs +from __future__ import absolute_import, unicode_literals + +from salt import exceptions + +# Import third party libs +try: + from shaptools import saputils + HAS_SAPUTILS = True +except ImportError: # pragma: no cover + HAS_SAPUTILS = False + +__virtualname__ = 'sapcar' + + +def __virtual__(): # pragma: no cover + ''' + Only load this module if shaptools python module is installed + ''' + if HAS_SAPUTILS: + return __virtualname__ + return ( + False, + 'The sapcar execution module failed to load: the shaptools python' + ' library is not available.') + + +def extract( + sapcar_exe, + sar_file, + output_dir=None, + options=None): + ''' + Extract a SAP sar archive + + sapcar_exe_file + Path to the SAPCAR executable file. SAPCAR is a SAP tool to extract SAP SAR format archives + sar_file + Path to the sar file to be extracted + output_dir + Location where to extract the SAR file + options: + Additional parameters to the SAPCAR tool + ''' + try: + return saputils.extract_sapcar_file( + sapcar_exe=sapcar_exe, sar_file=sar_file, output_dir=output_dir, options=options) + except saputils.SapUtilsError as err: + raise exceptions.CommandExecutionError(err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt/states/crmshmod.py new/salt-shaptools-0.3.4/salt/states/crmshmod.py --- old/salt-shaptools-0.3.1/salt/states/crmshmod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/salt/states/crmshmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -35,6 +35,7 @@ # Import salt libs from salt import exceptions +from salt import utils as salt_utils from salt.ext import six @@ -287,3 +288,110 @@ except exceptions.CommandExecutionError as err: ret['comment'] = six.text_type(err) return ret + + +def _convert2dict(file_content_lines): + """ + Convert the corosync configuration file to a dictionary + """ + corodict = {} + index = 0 + + for i, line in enumerate(file_content_lines): + stripped_line = line.strip() + if not stripped_line or stripped_line[0] == '#': + continue + + if index > i: + continue + + line_items = stripped_line.split() + if '{' in stripped_line: + corodict[line_items[0]], new_index = _convert2dict(file_content_lines[i+1:]) + index = i + new_index + elif line_items[0][-1] == ':': + corodict[line_items[0][:-1]] = line_items[-1] + elif '}' in stripped_line: + return corodict, i+2 + + return corodict, index + + +def _mergedicts(main_dict, changes_dict, applied_changes, initial_path=''): + """ + Merge the 2 dictionaries. We cannot use update as it changes all the children of an entry + """ + for key, value in changes_dict.items(): + current_path = '{}.{}'.format(initial_path, key) + if key in main_dict.keys() and not isinstance(value, dict): + if str(main_dict[key]) != str(value): + applied_changes[current_path] = value + main_dict[key] = value + elif key in main_dict.keys(): + modified_dict, new_changes = _mergedicts(main_dict[key], value, applied_changes, current_path) + main_dict[key] = modified_dict + applied_changes.update(new_changes) + + else: # Entry not found in current main dictionary, so we can update all + main_dict[key] = changes_dict[key] + applied_changes[current_path] = value + + return main_dict, applied_changes + + +def _convert2corosync(corodict, indentation=''): + """ + Convert a corosync data dictionary to the corosync configuration file format + """ + output = '' + for key, value in corodict.items(): + if isinstance(value, dict): + output += '{}{} {{\n'.format(indentation, key) + indentation += '\t' + output += _convert2corosync(value, indentation) + indentation = indentation[:-1] + output += '{}}}\n'.format(indentation) + else: + output += '{}{}: {}\n'.format(indentation, key, value) + return output + + +def corosync_updated( + name, + data, + backup=True): + """ + Configure corosync configuration file + + name + Corosync configuration file path + data + Dictionary with the values that have to be changed. The method won't do any sanity check + so, it will put in the configuration file value added in this parameter + """ + + changes = {} + ret = {'name': name, + 'changes': changes, + 'result': False, + 'comment': ''} + + with salt_utils.files.fopen(name, 'r') as file_content: + corodict, _ = _convert2dict(file_content.read().splitlines()) + new_conf_dict, changes = _mergedicts(corodict, data, {}) + + if not changes: + ret['changes'] = changes + ret['comment'] = 'Corosync already has the required configuration' + ret['result'] = True + return ret + + new_conf_file_content = _convert2corosync(new_conf_dict) + if backup: + __salt__['file.copy'](name, '{}.backup'.format(name)) + __salt__['file.write'](name, new_conf_file_content) + + ret['changes'] = changes + ret['comment'] = 'Corosync configuration file updated' + ret['result'] = True + return ret diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt/states/hanamod.py new/salt-shaptools-0.3.4/salt/states/hanamod.py --- old/salt-shaptools-0.3.1/salt/states/hanamod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/salt/states/hanamod.py 2020-03-31 20:02:39.980481668 +0200 @@ -223,7 +223,7 @@ software_path=software_path, conf_file=TMP_CONFIG_FILE, root_user=root_user, - root_password=root_password) + root_password=root_password) if hdb_pwd_file: __salt__['cp.get_file']( path=hdb_pwd_file, @@ -755,3 +755,57 @@ except exceptions.CommandExecutionError as err: ret['comment'] = six.text_type(err) return ret + + +def pydbapi_extracted( + name, + software_folders, + output_dir, + hana_version='20', + force=False): + ''' + Extract HANA pydbapi python client from the provided software folders + + name + Name of the package that needs to be installed + software_folders + Folders list where the HANA client is located. It's used as a list as the pydbapi client + will be found automatically among different folders and providing several folders is a + standard way in SAP landscape + output_dir + Folder where the package is extracted + force + Force new extraction if the file already is extracted + ''' + + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + + if not force and __salt__['file.directory_exists'](output_dir): + ret['result'] = True + ret['comment'] = \ + '{} already exists. Skipping extraction (set force to True to force the '\ + 'extraction)'.format(output_dir) + return ret + + if __opts__['test']: + ret['result'] = None + ret['comment'] = '{} would be extracted'.format(name) + ret['changes']['output_dir'] = output_dir + return ret + + __salt__['file.mkdir'](output_dir) + + try: + client = __salt__['hana.extract_pydbapi'](name, software_folders, output_dir, hana_version) + except exceptions.CommandExecutionError as err: + ret['comment'] = six.text_type(err) + return ret + + ret['result'] = True + ret['comment'] = '{} correctly extracted'.format(client) + ret['changes'] = {'pydbapi': client, 'output_dir': output_dir} + + return ret diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt/states/sapcarmod.py new/salt-shaptools-0.3.4/salt/states/sapcarmod.py --- old/salt-shaptools-0.3.1/salt/states/sapcarmod.py 1970-01-01 01:00:00.000000000 +0100 +++ new/salt-shaptools-0.3.4/salt/states/sapcarmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -0,0 +1,86 @@ +''' +State module to provide SAP utilities functionality to Salt + +.. versionadded:: pending + +:maintainer: Simranpal Singh <sisi...@suse.com> +:maturity: alpha +:depends: python-shaptools +:platform: all + +:configuration: This module requires the python-shaptools module + +:usage: + +.. code-block:: yaml + extract_sap_car_file: + sapcar.extracted: + - name: home/sapuser/saprouter_600-80003478.sar + - sapcar_exe: ./SAPCAR.exe + - output_dir: home/sapuser/saprouter_inst + - options: "-manifest SIGNATURE.SMF" +''' + + +# Import python libs +from __future__ import absolute_import, unicode_literals + +# Import salt libs +from salt import exceptions +from salt.ext import six + + +__virtualname__ = 'sapcar' + + +def __virtual__(): # pragma: no cover + ''' + Only load this module if sapcar python module is installed + ''' + return 'sapcar.extract' in __salt__ + + +def extracted( + name, + sapcar_exe, + output_dir=None, + options=None): + """ + Extract a SAPCAR sar archive + + name + SAR file name to be extracted + sapcar_exe + Path to the SAPCAR executable file. SAPCAR is a SAP tool to extract SAP SAR format archives + output_dir + Location where to extract the SAR file. If not provided, use current directory as name + options: + Additional parameters to the SAPCAR tool + """ + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + + if __opts__['test']: + ret['result'] = None + ret['comment'] = '{} would be extracted'.format(name) + ret['changes']['output_dir'] = output_dir + return ret + + try: + # Here starts the actual process + __salt__['sapcar.extract']( + sapcar_exe=sapcar_exe, + sar_file=name, + output_dir=output_dir, + options=options) + + ret['changes']['output_dir'] = output_dir + ret['comment'] = '{} file extracted'.format(name) + ret['result'] = True + return ret + + except exceptions.CommandExecutionError as err: + ret['comment'] = six.text_type(err) + return ret \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt-shaptools.changes new/salt-shaptools-0.3.4/salt-shaptools.changes --- old/salt-shaptools-0.3.1/salt-shaptools.changes 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/salt-shaptools.changes 2020-03-31 20:02:39.980481668 +0200 @@ -1,4 +1,24 @@ ------------------------------------------------------------------- +Fri Mar 27 18:06:32 UTC 2020 - Simranpal Singh <simranpal.si...@suse.com> + +- Version 0.3.4 + * Add new salt module and state to extract the sar files using SAPCAR + +------------------------------------------------------------------- +Fri Mar 20 14:49:04 UTC 2020 - Xabier Arbulu <xarb...@suse.com> + +- Version 0.3.3 + * Add new salt state to extract the HANA python dbapi client + +------------------------------------------------------------------- +Thu Mar 5 10:03:39 UTC 2020 - Xabier Arbulu <xarb...@suse.com> + +- Version 0.3.2 + * Add a new salt state method to update corosync configuration + file + * Fix travis file to install the py packages in develop mode + +------------------------------------------------------------------- Fri Jan 24 10:40:26 UTC 2020 - Xabier Arbulu <xarb...@suse.com> - Version 0.3.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/salt-shaptools.spec new/salt-shaptools-0.3.4/salt-shaptools.spec --- old/salt-shaptools-0.3.1/salt-shaptools.spec 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/salt-shaptools.spec 2020-03-31 20:02:39.980481668 +0200 @@ -19,7 +19,7 @@ # See also https://en.opensuse.org/openSUSE:Specfile_guidelines Name: salt-shaptools -Version: 0.3.1 +Version: 0.3.4 Release: 0 Summary: Salt modules and states for SAP Applications and SLE-HA components management @@ -51,7 +51,8 @@ cp -R salt/states/netweavermod.py %{buildroot}/srv/salt/_states cp -R salt/modules/saptunemod.py %{buildroot}/srv/salt/_modules cp -R salt/states/saptunemod.py %{buildroot}/srv/salt/_states - +cp -R salt/modules/sapcarmod.py %{buildroot}/srv/salt/_modules +cp -R salt/states/sapcarmod.py %{buildroot}/srv/salt/_states %files %defattr(-,root,root,-) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/run.sh new/salt-shaptools-0.3.4/tests/run.sh --- old/salt-shaptools-0.3.1/tests/run.sh 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/tests/run.sh 2020-03-31 20:02:39.980481668 +0200 @@ -2,4 +2,4 @@ cp salt/states/*.py ../salt/salt/states/ cp tests/unit/modules/*.py ../salt/tests/unit/modules/ cp tests/unit/states/*.py ../salt/tests/unit/states/ -py.test -vv ../salt/tests/unit/modules/test_hanamod.py ../salt/tests/unit/states/test_hanamod.py ../salt/tests/unit/modules/test_crmshmod.py ../salt/tests/unit/modules/test_saptunemod.py ../salt/tests/unit/states/test_crmshmod.py ../salt/tests/unit/modules/test_drbdmod.py ../salt/tests/unit/states/test_drbdmod.py ../salt/tests/unit/states/test_saptunemod.py ../salt/tests/unit/modules/test_netweavermod.py ../salt/tests/unit/states/test_netweavermod.py --cov=salt.modules.hanamod --cov=salt.states.hanamod --cov=salt.modules.crmshmod --cov=salt.states.crmshmod --cov=salt.modules.drbdmod --cov=salt.modules.saptunemod --cov=salt.states.saptunemod --cov=salt.states.drbdmod --cov=salt.modules.netweavermod --cov=salt.states.netweavermod --cov-config .coveragerc --cov-report term --cov-report xml --cov-report html +py.test -vv ../salt/tests/unit/modules/test_hanamod.py ../salt/tests/unit/states/test_hanamod.py ../salt/tests/unit/modules/test_crmshmod.py ../salt/tests/unit/modules/test_saptunemod.py ../salt/tests/unit/modules/test_sapcarmod.py ../salt/tests/unit/states/test_crmshmod.py ../salt/tests/unit/modules/test_drbdmod.py ../salt/tests/unit/states/test_drbdmod.py ../salt/tests/unit/states/test_saptunemod.py ../salt/tests/unit/modules/test_netweavermod.py ../salt/tests/unit/states/test_netweavermod.py ../salt/tests/unit/states/test_sapcarmod.py --cov=salt.modules.hanamod --cov=salt.states.hanamod --cov=salt.modules.crmshmod --cov=salt.states.crmshmod --cov=salt.modules.drbdmod --cov=salt.modules.saptunemod --cov=salt.modules.sapcarmod --cov=salt.states.saptunemod --cov=salt.states.drbdmod --cov=salt.modules.netweavermod --cov=salt.states.netweavermod --cov=salt.states.sapcarmod --cov-config .coveragerc --cov-report term --cov-report xml --cov-report html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/unit/modules/test_hanamod.py new/salt-shaptools-0.3.4/tests/unit/modules/test_hanamod.py --- old/salt-shaptools-0.3.1/tests/unit/modules/test_hanamod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/tests/unit/modules/test_hanamod.py 2020-03-31 20:02:39.980481668 +0200 @@ -17,6 +17,7 @@ from tests.support.mock import ( MagicMock, patch, + mock_open, NO_MOCK, NO_MOCK_REASON ) @@ -813,3 +814,106 @@ mock.call('192.168.10.15', 30015, user='SYSTEM', password='pass') ]) assert 'HANA database not available after 2 seconds in 192.168.10.15:30015' in str(err.value) + + @mock.patch('salt.modules.hanamod.hdb_connector') + @mock.patch('importlib.reload') + def test_reload_hdb_connector(self, mock_reload, mock_hdb_connector): + hanamod.reload_hdb_connector() + mock_reload.assert_called_once_with(mock_hdb_connector) + + @mock.patch('logging.Logger.debug') + @mock.patch('salt.utils.files.fopen') + def test_find_sap_folder_error(self, mock_fopen, mock_debug): + mock_pattern = mock.Mock(pattern='my_pattern') + mock_fopen.side_effect = [ + IOError, IOError, IOError, IOError] + with pytest.raises(hanamod.SapFolderNotFoundError) as err: + hanamod._find_sap_folder(['1234', '5678'], mock_pattern) + + assert 'SAP folder with my_pattern pattern not found' in str(err.value) + mock_debug.assert_has_calls([ + mock.call('%s file not found in %s. Skipping folder', 'LABEL.ASC', '1234'), + mock.call('%s file not found in %s. Skipping folder', 'LABELIDX.ASC', '1234'), + mock.call('%s file not found in %s. Skipping folder', 'LABEL.ASC', '5678'), + mock.call('%s file not found in %s. Skipping folder', 'LABELIDX.ASC', '5678') + ]) + + def test_find_sap_folder_contain_hana(self): + mock_pattern = mock.Mock(return_value=True) + with patch('salt.utils.files.fopen', mock_open(read_data='data\n')) as mock_file: + folder = hanamod._find_sap_folder(['1234', '5678'], mock_pattern) + + mock_pattern.match.assert_called_once_with('data') + assert folder in '1234' + + @mock.patch('logging.Logger.debug') + def test_find_sap_folder_contain_units(self, mock_debug): + mock_pattern = mock.Mock(pattern='my_pattern') + mock_pattern.match.side_effect = [False, True] + with patch('salt.utils.files.fopen', mock_open(read_data= + ['data\n', 'DATA_UNITS\n', 'data_2\n'])) as mock_file: + folder = hanamod._find_sap_folder(['1234', '5678'], mock_pattern) + + mock_pattern.match.assert_has_calls([ + mock.call('data'), + mock.call('data_2') + ]) + mock_debug.assert_has_calls([ + mock.call('%s folder does not contain %s pattern', '1234', 'my_pattern') + ]) + assert folder in '1234/DATA_UNITS' + + @mock.patch('logging.Logger.debug') + def test_find_sap_folder_contain_units_error(self, mock_debug): + mock_pattern = mock.Mock(pattern='my_pattern') + mock_pattern.match.side_effect = [False, False] + with patch('salt.utils.files.fopen', mock_open(read_data=[ + 'data\n', 'DATA_UNITS\n', 'data_2\n', IOError])) as mock_file: + with pytest.raises(hanamod.SapFolderNotFoundError) as err: + folder = hanamod._find_sap_folder(['1234'], mock_pattern) + + mock_pattern.match.assert_has_calls([ + mock.call('data'), + mock.call('data_2') + ]) + mock_debug.assert_has_calls([ + mock.call('%s folder does not contain %s pattern', '1234', 'my_pattern') + ]) + assert 'SAP folder with my_pattern pattern not found' in str(err.value) + + @mock.patch('re.compile') + @mock.patch('salt.modules.hanamod._find_sap_folder') + @mock.patch('salt.modules.hanamod.hana.HanaInstance.get_platform') + def test_extract_pydbapi(self, mock_get_platform, mock_find_sap_folders, mock_compile): + mock_get_platform.return_value = 'LINUX_X86_64' + mock_find_sap_folders.return_value = 'my_folder' + compile_mocked = mock.Mock() + mock_compile.return_value = compile_mocked + mock_tar = MagicMock() + with patch.dict(hanamod.__salt__, {'archive.tar': mock_tar}): + pydbapi_file = hanamod.extract_pydbapi( + 'PYDBAPI.tar.gz', ['1234', '5678'], '/tmp/output') + + mock_compile.assert_called_once_with('^HDB_CLIENT:20.*:LINUX_X86_64:.*') + mock_find_sap_folders.assert_called_once_with( + ['1234', '5678'], compile_mocked) + mock_tar.assert_called_once_with( + options='xvf', tarfile='my_folder/client/PYDBAPI.tar.gz', dest='/tmp/output') + assert pydbapi_file == 'my_folder/client/PYDBAPI.tar.gz' + + @mock.patch('re.compile') + @mock.patch('salt.modules.hanamod._find_sap_folder') + @mock.patch('salt.modules.hanamod.hana.HanaInstance.get_platform') + def test_extract_pydbapi_error(self, mock_get_platform, mock_find_sap_folders, mock_compile): + mock_get_platform.return_value = 'LINUX_X86_64' + compile_mocked = mock.Mock() + mock_compile.return_value = compile_mocked + mock_find_sap_folders.side_effect = hanamod.SapFolderNotFoundError + with pytest.raises(exceptions.CommandExecutionError) as err: + pydbapi_file = hanamod.extract_pydbapi( + 'PYDBAPI.tar.gz', ['1234', '5678'], '/tmp/output') + + mock_compile.assert_called_once_with('^HDB_CLIENT:20.*:LINUX_X86_64:.*') + mock_find_sap_folders.assert_called_once_with( + ['1234', '5678'], compile_mocked) + assert 'HANA client not found' in str(err.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/unit/modules/test_sapcarmod.py new/salt-shaptools-0.3.4/tests/unit/modules/test_sapcarmod.py --- old/salt-shaptools-0.3.1/tests/unit/modules/test_sapcarmod.py 1970-01-01 01:00:00.000000000 +0100 +++ new/salt-shaptools-0.3.4/tests/unit/modules/test_sapcarmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -0,0 +1,60 @@ + +# -*- coding: utf-8 -*- +''' + :codeauthor: Simranpal Singh <sisi...@suse.com> +''' + +# Import Python Libs +from __future__ import absolute_import, print_function, unicode_literals +import pytest + +from salt import exceptions + +# Import Salt Testing Libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.unit import TestCase, skipIf +from tests.support import mock +from tests.support.mock import ( + MagicMock, + patch, + mock_open, + NO_MOCK, + NO_MOCK_REASON +) + +# Import Salt Libs +import salt.modules.sapcarmod as sapcarmod + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class SapcarModuleTest(TestCase, LoaderModuleMockMixin): + ''' + This class contains a set of functions that test salt.modules.sapcarmod. + ''' + + def setup_loader_modules(self): + return {sapcarmod: {}} + + @patch('salt.modules.sapcarmod.saputils.extract_sapcar_file') + def test_extract_return(self, mock_extract): + ''' + Test extract method - return + ''' + mock_extract.return_value = 0 + assert sapcarmod.extract('/sapmedia/SAPCAR','/sapmedia/IMDB_SERVER_LINUX.SAR', '/sapmedia/HANA', '-v') == 0 + mock_extract.assert_called_once_with( + sapcar_exe='/sapmedia/SAPCAR', sar_file='/sapmedia/IMDB_SERVER_LINUX.SAR', + output_dir='/sapmedia/HANA', options='-v') + + @patch('salt.modules.sapcarmod.saputils.extract_sapcar_file') + def test_extract_raise(self, mock_extract): + ''' + Test extract method - raise + ''' + mock_extract.side_effect = sapcarmod.saputils.SapUtilsError('error') + with pytest.raises(exceptions.CommandExecutionError) as err: + sapcarmod.extract('/sapmedia/SAPCAR','/sapmedia/IMDB_SERVER_LINUX.SAR', '/sapmedia/HANA', '-v') + mock_extract.assert_called_once_with( + sapcar_exe='/sapmedia/SAPCAR', sar_file='/sapmedia/IMDB_SERVER_LINUX.SAR', + output_dir='/sapmedia/HANA', options='-v') + \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/unit/states/test_crmshmod.py new/salt-shaptools-0.3.4/tests/unit/states/test_crmshmod.py --- old/salt-shaptools-0.3.1/tests/unit/states/test_crmshmod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/tests/unit/states/test_crmshmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -6,6 +6,7 @@ # Import Python libs from __future__ import absolute_import, unicode_literals, print_function +import sys from salt import exceptions # Import Salt Testing Libs @@ -15,6 +16,7 @@ from tests.support.mock import ( NO_MOCK, NO_MOCK_REASON, + mock_open, MagicMock, patch ) @@ -480,3 +482,244 @@ method='update', url='file.config', is_xml=False) + + def test_convert2dict(self): + corofile = """ +# Please read the corosync.conf.5 manual page +totem { + version: 2 + max_messages: 20 + interface { + ringnumber: 0 + + } + transport: udpu +} + +logging { + timestamp: on + logger_subsys { + debug: off + } + +} + +quorum { + expected_votes: 1 + two_node: 0 +}""" + + corodict, _ = crmshmod._convert2dict(corofile.splitlines()) + + assert corodict == { + 'totem': { + 'version': '2', + 'max_messages': '20', + 'interface': { + 'ringnumber': '0' + }, + 'transport': 'udpu' + }, + 'logging': { + 'timestamp': 'on', + 'logger_subsys': { + 'debug': 'off' + } + }, + 'quorum': { + 'expected_votes': '1', + 'two_node': '0' + } + } + + def test_merge_dicts1(self): + main_dict = { + 'a': { + 'b': 1, + 'c': 2 + }, + 'd': 3 + } + changed_dict = { + 'a': { + 'c': 4 + }, + 'd': 5 + } + merged_dict, applied_changes = crmshmod._mergedicts( + main_dict, changed_dict, {}, '') + + assert merged_dict == { + 'a': { + 'b': 1, + 'c': 4 + }, + 'd': 5 + } + + assert applied_changes == { + '.a.c': 4, + '.d': 5 + } + + def test_merge_dicts2(self): + main_dict = { + 'a': { + 'b': { + 'f': 7 + }, + 'c': 2 + }, + 'd': 3 + } + changed_dict = { + 'a': { + 'b': { + 'f': 8 + }, + }, + 'd': 5 + } + merged_dict, applied_changes = crmshmod._mergedicts( + main_dict, changed_dict, {}, '') + + assert merged_dict == { + 'a': { + 'b': { + 'f': 8 + }, + 'c': 2 + }, + 'd': 5 + } + + assert applied_changes == { + '.d': 5, + '.a.b.f': 8 + } + + def test_merge_dicts3(self): + main_dict = { + 'a': { + 'b': 1, + 'c': 2 + }, + 'd': 3 + } + changed_dict = { + 'e': { + 'c': 4 + }, + 'a': { + 'b': 3 + }, + 'd': 5 + } + merged_dict, applied_changes = crmshmod._mergedicts( + main_dict, changed_dict, {}, '') + + assert merged_dict == { + 'a': { + 'b': 3, + 'c': 2 + }, + 'e': { + 'c': 4 + }, + 'd': 5 + } + + assert applied_changes == { + '.d': 5, + '.a.b': 3, + '.e': {'c': 4} + } + + def test_convert2corosync(self): + main_dict = { + 'a': { + 'b': { + 'f': 7 + }, + 'c': 2 + }, + 'd': 3 + } + + output = crmshmod._convert2corosync(main_dict, '') + + # Py2 and py3 have different way of ordering the `items` method + # For the functionality this does not really affect + if sys.version_info[0] == 2: + assert output == "a {\n\tc: 2\n\tb {\n\t\tf: 7\n\t}\n}\nd: 3\n" + else: + assert output == "a {\n\tb {\n\t\tf: 7\n\t}\n\tc: 2\n}\nd: 3\n" + + @mock.patch('salt.states.crmshmod._convert2dict') + @mock.patch('salt.states.crmshmod._mergedicts') + def test_corosync_updated_already(self, mock_mergedicts, mock_convert2dict): + ''' + Test to check corosync_updated when configuration is already applied + ''' + + ret = {'name': '/etc/corosync/corosync.conf', + 'changes': {}, + 'result': True, + 'comment': 'Corosync already has the required configuration'} + + mock_convert2dict.return_value = ({'data': 1}, {}) + mock_mergedicts.return_value = ({}, {}) + + file_content = "my corosync file content\nmy corosync file 2nd line content" + with patch("salt.utils.files.fopen", mock_open(read_data=file_content)): + assert crmshmod.corosync_updated( + name='/etc/corosync/corosync.conf', + data={'my_data': 1}) == ret + + mock_convert2dict.assert_called_once_with( + ['my corosync file content', 'my corosync file 2nd line content'] + ) + mock_mergedicts.assert_called_once_with( + {'data': 1}, {'my_data': 1}, {}) + + @mock.patch('salt.states.crmshmod._convert2corosync') + @mock.patch('salt.states.crmshmod._convert2dict') + @mock.patch('salt.states.crmshmod._mergedicts') + def test_corosync_updated(self, mock_mergedicts, mock_convert2dict, mock_convert2corosync): + ''' + Test to check corosync_updated when configuration is applied + ''' + + ret = {'name': '/etc/corosync/corosync.conf', + 'changes': {'change1': 1, 'change2': 2}, + 'result': True, + 'comment': 'Corosync configuration file updated'} + + mock_copy = MagicMock() + mock_write = MagicMock() + mock_convert2dict.return_value = ({'data': 1}, {}) + mock_mergedicts.return_value = ({'updated': 2}, {'change1': 1, 'change2': 2}) + mock_convert2corosync.return_value = 'new content' + + file_content = "my corosync file content\nmy corosync file 2nd line content" + + with patch.dict(crmshmod.__salt__, {'file.copy': mock_copy, + 'file.write': mock_write}): + with patch("salt.utils.files.fopen", mock_open(read_data=file_content)): + assert crmshmod.corosync_updated( + name='/etc/corosync/corosync.conf', + data={'my_data': 1}) == ret + + mock_convert2dict.assert_called_once_with( + ['my corosync file content', 'my corosync file 2nd line content'] + ) + mock_mergedicts.assert_called_once_with( + {'data': 1}, {'my_data': 1}, {}) + + mock_convert2corosync.assert_called_once_with({'updated': 2}) + + mock_copy.assert_called_once_with( + '/etc/corosync/corosync.conf', '/etc/corosync/corosync.conf.backup') + + mock_write.assert_called_once_with( + '/etc/corosync/corosync.conf', 'new content') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/unit/states/test_hanamod.py new/salt-shaptools-0.3.4/tests/unit/states/test_hanamod.py --- old/salt-shaptools-0.3.1/tests/unit/states/test_hanamod.py 2020-03-06 15:03:43.200091220 +0100 +++ new/salt-shaptools-0.3.4/tests/unit/states/test_hanamod.py 2020-03-31 20:02:39.980481668 +0200 @@ -262,7 +262,7 @@ assert hanamod.installed( 'prd', '00', 'pass', '/software', 'root', 'pass') == ret - + mock_create.assert_called_once_with( software_path='/software', conf_file=hanamod.TMP_CONFIG_FILE, @@ -1041,3 +1041,64 @@ sid='prd', inst='00', password='pass') + + def test_pydbapi_extracted_already_exists(self): + ret = {'name': 'PYDBAPI.tar', + 'changes': {}, + 'result': True, + 'comment': '/tmp/output already exists. Skipping extraction (set force to True to force the extraction)'} + + mock_dir_exists = MagicMock(return_value=True) + + with patch.dict(hanamod.__salt__, {'file.directory_exists': mock_dir_exists}): + assert hanamod.pydbapi_extracted( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output') == ret + + mock_dir_exists.assert_called_once_with('/tmp/output') + + def test_pydbapi_extracted_test(self): + ret = {'name': 'PYDBAPI.tar', + 'changes': {'output_dir': '/tmp/output'}, + 'result': None, + 'comment': 'PYDBAPI.tar would be extracted'} + + with patch.dict(hanamod.__opts__, {'test': True}): + assert hanamod.pydbapi_extracted( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output', force=True) == ret + + def test_pydbapi_extracted_error(self): + ret = {'name': 'PYDBAPI.tar', + 'changes': {}, + 'result': False, + 'comment': 'error extracting'} + + mock_mkdir = MagicMock() + mock_extract_pydbapi = MagicMock( + side_effect=exceptions.CommandExecutionError('error extracting')) + + with patch.dict(hanamod.__salt__, {'file.mkdir': mock_mkdir, + 'hana.extract_pydbapi': mock_extract_pydbapi}): + assert hanamod.pydbapi_extracted( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output', force=True) == ret + + mock_mkdir.assert_called_once_with('/tmp/output') + mock_extract_pydbapi.assert_called_once_with( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output', '20') + + def test_pydbapi_extracted_correct(self): + ret = {'name': 'PYDBAPI.tar', + 'changes': {'pydbapi': 'py_client', 'output_dir': '/tmp/output'}, + 'result': True, + 'comment': 'py_client correctly extracted'} + + mock_mkdir = MagicMock() + mock_extract_pydbapi = MagicMock(return_value='py_client') + + with patch.dict(hanamod.__salt__, {'file.mkdir': mock_mkdir, + 'hana.extract_pydbapi': mock_extract_pydbapi}): + assert hanamod.pydbapi_extracted( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output', force=True) == ret + + mock_mkdir.assert_called_once_with('/tmp/output') + mock_extract_pydbapi.assert_called_once_with( + 'PYDBAPI.tar', ['1234', '5678'], '/tmp/output', '20') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.3.1/tests/unit/states/test_sapcarmod.py new/salt-shaptools-0.3.4/tests/unit/states/test_sapcarmod.py --- old/salt-shaptools-0.3.1/tests/unit/states/test_sapcarmod.py 1970-01-01 01:00:00.000000000 +0100 +++ new/salt-shaptools-0.3.4/tests/unit/states/test_sapcarmod.py 2020-03-31 20:02:39.980481668 +0200 @@ -0,0 +1,90 @@ + +# -*- coding: utf-8 -*- +''' + :codeauthor: Simranpal Singh <sisi...@suse.com> +''' +# Import Python libs +from __future__ import absolute_import, unicode_literals, print_function + +from salt import exceptions + +# Import Salt Testing Libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.unit import skipIf, TestCase +from tests.support import mock +from tests.support.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +# Import Salt Libs +import salt.states.sapcarmod as sapcar + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class SapcarmodTestCase(TestCase, LoaderModuleMockMixin): + ''' + Test cases for salt.states.sapcarmod + ''' + def setup_loader_modules(self): + return {sapcar: {'__opts__': {'test': False}}} + + def test_available_test(self): + ''' + Test to check extracted in test mode + ''' + + ret = {'name': '/sapmedia/IMDB_SERVER_LINUX.SAR', + 'changes': {'output_dir': '/sapmedia'}, + 'result': None, + 'comment': '/sapmedia/IMDB_SERVER_LINUX.SAR would be extracted'} + + with patch.dict(sapcar.__opts__, {'test': True}): + assert sapcar.extracted( + name='/sapmedia/IMDB_SERVER_LINUX.SAR', output_dir='/sapmedia', + sapcar_exe='/sapmedia/SAPCAR') + + def test_extracted_basic(self): + ''' + Test to check extracted when sapcar successfully extracts a sar file + ''' + + expected_ret = { + 'name': '/sapmedia/IMDB_SERVER_LINUX.SAR', + 'changes': {'output_dir': '/sapmedia'}, + 'result': True, + 'comment': '/sapmedia/IMDB_SERVER_LINUX.SAR file extracted' + } + + mock_extract = MagicMock() + with patch.dict(sapcar.__salt__, { + 'sapcar.extract': mock_extract}): + assert sapcar.extracted( + name='/sapmedia/IMDB_SERVER_LINUX.SAR', output_dir='/sapmedia', + sapcar_exe='/sapmedia/SAPCAR') == expected_ret + mock_extract.assert_called_once_with( + sapcar_exe='/sapmedia/SAPCAR', sar_file='/sapmedia/IMDB_SERVER_LINUX.SAR', + output_dir='/sapmedia', options=None) + + def test_extracted_error_exception(self): + ''' + Test to check extracted when sapcar fails to extracts a sar file + ''' + + expected_ret = { + 'changes': {}, + 'result': False, + 'name': '/sapmedia/IMDB_SERVER_LINUX.SAR', + 'comment': 'sapcar error' + } + + mock_extract = MagicMock(side_effect=exceptions.CommandExecutionError('sapcar error')) + with patch.dict(sapcar.__salt__, { + 'sapcar.extract': mock_extract}): + assert sapcar.extracted( + '/sapmedia/IMDB_SERVER_LINUX.SAR','/sapmedia/SAPCAR', options='-v') == expected_ret + mock_extract.assert_called_once_with( + sapcar_exe='/sapmedia/SAPCAR', sar_file='/sapmedia/IMDB_SERVER_LINUX.SAR', + output_dir=None, options='-v')