Hello community, here is the log from the commit of package salt-shaptools for openSUSE:Factory checked in at 2019-12-09 21:35:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt-shaptools (Old) and /work/SRC/openSUSE:Factory/.salt-shaptools.new.4691 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt-shaptools" Mon Dec 9 21:35:49 2019 rev:5 rq:755180 version:0.2.5 Changes: -------- --- /work/SRC/openSUSE:Factory/salt-shaptools/salt-shaptools.changes 2019-11-22 10:27:44.909232429 +0100 +++ /work/SRC/openSUSE:Factory/.salt-shaptools.new.4691/salt-shaptools.changes 2019-12-09 21:36:05.962077454 +0100 @@ -1,0 +2,11 @@ +Tue Dec 3 06:41:36 UTC 2019 - nick wang <[email protected]> + +- Version 0.2.5 + DRBD: support to get status via json format by default. + +------------------------------------------------------------------- +Thu Nov 21 09:05:07 UTC 2019 - nick wang <[email protected]> + +- Version 0.2.4, fix error parsing drbd status when congested. + +------------------------------------------------------------------- Old: ---- salt-shaptools-0.2.3.tar.gz New: ---- salt-shaptools-0.2.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt-shaptools.spec ++++++ --- /var/tmp/diff_new_pack.jAkuvr/_old 2019-12-09 21:36:07.366076901 +0100 +++ /var/tmp/diff_new_pack.jAkuvr/_new 2019-12-09 21:36:07.366076901 +0100 @@ -19,7 +19,7 @@ # See also https://en.opensuse.org/openSUSE:Specfile_guidelines Name: salt-shaptools -Version: 0.2.3 +Version: 0.2.5 Release: 0 Summary: Salt modules and states for SAP Applications and SLE-HA components management ++++++ salt-shaptools-0.2.3.tar.gz -> salt-shaptools-0.2.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/salt/modules/crmshmod.py new/salt-shaptools-0.2.5/salt/modules/crmshmod.py --- old/salt-shaptools-0.2.3/salt/modules/crmshmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/salt/modules/crmshmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -50,9 +50,6 @@ version = __salt__['pkg.version'](CRMSH) use_crm = __salt__['pkg.version_cmp']( version, CRM_NEW_VERSION) >= 0 - LOGGER.info('crmsh version: %s', version) - LOGGER.info( - '%s will be used', 'crm' if use_crm else 'ha-cluster') else: return ( @@ -66,7 +63,7 @@ 'The crmsh execution module failed to load: the ha-cluster-init' ' package is not available.') - __salt__['crmsh.version'] = use_crm + __salt__['crm.version'] = use_crm return __virtualname__ @@ -389,7 +386,7 @@ ''' # INFO: 2 different methods are created to make easy to read/understand # and create the corresponing UT - if __salt__['crmsh.version']: + if __salt__['crm.version']: return _crm_init( name, watchdog, interface, unicast, admin_ip, sbd, sbd_dev, quiet) @@ -475,7 +472,7 @@ ''' # INFO: 2 different methods are created to make easy to read/understand # and create the corresponing UT - if __salt__['crmsh.version']: + if __salt__['crm.version']: return _crm_join(host, watchdog, interface, quiet) return _ha_cluster_join(host, watchdog, interface, quiet) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/salt/modules/drbdmod.py new/salt-shaptools-0.2.5/salt/modules/drbdmod.py --- old/salt-shaptools-0.2.3/salt/modules/drbdmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/salt/modules/drbdmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -30,6 +30,12 @@ __virtualname__ = 'drbd' DRBD_COMMAND = 'drbdadm' +ERR_STR = 'UNKNOWN' +DUMMY_STR = 'IGNORED' +WITH_JSON = True +DRBDADM = 'drbd-utils' +# drbd-utils >= 9.0.0 for json status +DRBDADM_JSON_VERSION = '9.0.0' def __virtual__(): # pragma: no cover @@ -37,6 +43,14 @@ Only load this module if drbdadm(drbd-utils) is installed ''' if bool(salt.utils.path.which(DRBD_COMMAND)): + __salt__['drbd.json'] = WITH_JSON + + version = __salt__['pkg.version'](DRBDADM) + json_support = __salt__['pkg.version_cmp'](version, + DRBDADM_JSON_VERSION) >= 0 + if not json_support: + __salt__['drbd.json'] = False + return __virtualname__ return ( False, @@ -85,10 +99,12 @@ switch = { 0: 'RESOURCE', 2: {' disk:': 'LOCALDISK', ' role:': 'PEERNODE', ' connection:': 'PEERNODE'}, - 4: {' peer-disk:': 'PEERDISK'} + 4: {' peer-disk:': 'PEERDISK'}, + 6: DUMMY_STR, + 8: DUMMY_STR, } - ret = switch.get(spaces, 'UNKNOWN') + ret = switch.get(spaces, ERR_STR) # isinstance(ret, str) only works when run directly, calling need unicode(six) if isinstance(ret, six.text_type): @@ -98,6 +114,9 @@ if x in line: return ret[x] + # Doesn't find expected KEY in support indent + return ERR_STR + def _add_res(line): ''' @@ -171,7 +190,7 @@ def _empty(dummy): ''' - Action of empty line of ``drbdadm status`` + Action of empty line or extra verbose info of ``drbdadm status`` ''' @@ -179,7 +198,7 @@ ''' Action of unsupported line of ``drbdadm status`` ''' - __context__['drbd.statusret'] = {"Unknown parser": line} + raise CommandExecutionError('The unknown line:\n' + line) def _line_parser(line): @@ -195,6 +214,8 @@ 'PEERNODE': _add_peernode, 'LOCALDISK': _add_volume, 'PEERDISK': _add_volume, + DUMMY_STR: _empty, + ERR_STR: _unknown_parser, } func = switch.get(section, _unknown_parser) @@ -206,14 +227,19 @@ ''' Check whether all local volumes are UpToDate. ''' + if __salt__['drbd.json']: + output = OUTPUT_OPTIONS['json'] + else: + output = OUTPUT_OPTIONS['text'] + + res = output["get_res_func"](name) - res = status(name) if not res: return False # Since name is not all, res only have one element - for vol in res[0]['local volumes']: - if vol['disk'] != 'UpToDate': + for vol in res[0][output["volume"]]: + if vol[output["state"]] != 'UpToDate': return False return True @@ -227,19 +253,23 @@ If peernode is not match, will return None, same as False. ''' - ret = None + if __salt__['drbd.json']: + output = OUTPUT_OPTIONS['json'] + else: + output = OUTPUT_OPTIONS['text'] + + res = output["get_res_func"](name) - res = status(name) if not res: - return ret + return False # Since name is not all, res only have one element - for node in res[0]['peer nodes']: - if peernode != 'all' and node['peernode name'] != peernode: + for node in res[0][output["connection"]]: + if peernode != 'all' and node[output["peer_node"]] != peernode: continue - for vol in node['peer volumes']: - if vol['peer-disk'] != 'UpToDate': + for vol in node[output["peer_node_vol"]]: + if vol[output["peer_node_state"]] != 'UpToDate': return False else: # At lease one volume is 'UpToDate' @@ -365,8 +395,13 @@ LOGGER.info('No status due to %s (%s).', result['stderr'], result['retcode']) return None - for line in result['stdout'].splitlines(): - _line_parser(line) + try: + for line in result['stdout'].splitlines(): + _line_parser(line) + except CommandExecutionError as err: + raise CommandExecutionError('UNKNOWN status output format found', + info=(result['stdout'] + "\n\n" + + six.text_type(err))) if __context__['drbd.resource']: __context__['drbd.statusret'].append(__context__['drbd.resource']) @@ -596,18 +631,13 @@ salt '*' drbd.setup_status name=<resource name> ''' - ret = {'name': name, - 'result': False, - 'comment': ''} - cmd = 'drbdsetup status --json {}'.format(name) results = __salt__['cmd.run_all'](cmd) if 'retcode' not in results or results['retcode'] != 0: - ret['comment'] = 'Error({}) happend when show resource via drbdsetup.'.format( - results['retcode']) - return ret + LOGGER.info('No drbdsetup status due to %s (%s).', results['stderr'], results['retcode']) + return None try: ret = salt.utils.json.loads(results['stdout'], strict=False) @@ -618,6 +648,29 @@ return ret +# Define OUTPUT_OPTIONS after setup_status() and status() defined +OUTPUT_OPTIONS = { + "json": { + "volume": "devices", + "state": "disk-state", + "connection": "connections", + "peer_node": "name", + "peer_node_vol": "peer_devices", + "peer_node_state": "peer-disk-state", + "get_res_func": setup_status + }, + "text": { + "volume": "local volumes", + "state": "disk", + "connection": "peer nodes", + "peer_node": "peernode name", + "peer_node_vol": "peer volumes", + "peer_node_state": "peer-disk", + "get_res_func": status + } +} + + def check_sync_status(name, peernode='all'): ''' Query a drbd resource until fully synced for all volumes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/salt/states/drbdmod.py new/salt-shaptools-0.2.5/salt/states/drbdmod.py --- old/salt-shaptools-0.2.3/salt/states/drbdmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/salt/states/drbdmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -63,6 +63,15 @@ def _get_res_status(name): + if __salt__['drbd.json']: + res = __get_res_drbdsetup_status(name) + else: + res = __get_res_drbdadm_status(name) + + return res + + +def __get_res_drbdadm_status(name): try: result = __salt__['drbd.status'](name=name) except CommandExecutionError as err: @@ -80,6 +89,24 @@ return None +def __get_res_drbdsetup_status(name): + try: + result = __salt__['drbd.setup_status'](name=name) + except CommandExecutionError as err: + LOGGER.error(six.text_type(err)) + return None + + if not result: + return None + + for res in result: + if res['name'] == name: + LOGGER.debug(res) + return res + + return None + + def _get_resource_list(): ret = [] cmd = 'drbdadm dump all' @@ -261,6 +288,17 @@ return ret +# Define OUTPUT_OPTIONS before it used in promoted() and demoted() +OUTPUT_OPTIONS = { + 'json': { + 'role': 'role', + }, + 'text': { + 'role': 'local role', + } +} + + def promoted(name, force=False): ''' Make sure the DRBD resource is being primary. @@ -285,10 +323,16 @@ ret['comment'] = 'Resource {} not defined in your config.'.format(name) return ret + json_format = __salt__['drbd.json'] + if json_format: + output = OUTPUT_OPTIONS['json'] + else: + output = OUTPUT_OPTIONS['text'] + # Check resource is running res = _get_res_status(name) if res: - if res['local role'] == 'Primary': + if res[output['role']] == 'Primary': ret['result'] = True ret['comment'] = 'Resource {} has already been promoted.'.format(name) return ret @@ -345,10 +389,16 @@ ret['comment'] = 'Resource {} not defined in your config.'.format(name) return ret + json_format = __salt__['drbd.json'] + if json_format: + output = OUTPUT_OPTIONS['json'] + else: + output = OUTPUT_OPTIONS['text'] + # Check resource is running res = _get_res_status(name) if res: - if res['local role'] == 'Secondary': + if res[output['role']] == 'Secondary': ret['result'] = True ret['comment'] = 'Resource {} has already been demoted.'.format(name) return ret @@ -382,6 +432,9 @@ return ret +# May replace by "drbdsetup wait-sync-resource" in drbd9 with modification. +# Cause it only suspend when resoure is in syncing. +# Behavior the same when a resource not and finished sync. def wait_for_successful_synced(name, interval=30, timeout=600, **kwargs): ''' Query a drbd resource until fully synced for all volumes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/salt-shaptools.changes new/salt-shaptools-0.2.5/salt-shaptools.changes --- old/salt-shaptools-0.2.3/salt-shaptools.changes 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/salt-shaptools.changes 2019-12-09 03:44:43.029007691 +0100 @@ -1,4 +1,15 @@ ------------------------------------------------------------------- +Tue Dec 3 06:41:36 UTC 2019 - nick wang <[email protected]> + +- Version 0.2.5 + DRBD: support to get status via json format by default. + +------------------------------------------------------------------- +Thu Nov 21 09:05:07 UTC 2019 - nick wang <[email protected]> + +- Version 0.2.4, fix error parsing drbd status when congested. + +------------------------------------------------------------------- Mon Nov 5 08:48:50 UTC 2019 - nick wang <[email protected]> - Create package version 0.2.3 with drbd files renamed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/salt-shaptools.spec new/salt-shaptools-0.2.5/salt-shaptools.spec --- old/salt-shaptools-0.2.3/salt-shaptools.spec 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/salt-shaptools.spec 2019-12-09 03:44:43.029007691 +0100 @@ -19,7 +19,7 @@ # See also https://en.opensuse.org/openSUSE:Specfile_guidelines Name: salt-shaptools -Version: 0.2.3 +Version: 0.2.5 Release: 0 Summary: Salt modules and states for SAP Applications and SLE-HA components management diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/tests/unit/modules/test_crmshmod.py new/salt-shaptools-0.2.5/tests/unit/modules/test_crmshmod.py --- old/salt-shaptools-0.2.3/tests/unit/modules/test_crmshmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/tests/unit/modules/test_crmshmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -34,9 +34,8 @@ def setup_loader_modules(self): return {crmshmod: {}} - @mock.patch('logging.Logger.info') @mock.patch('salt.utils.path.which') - def test_virtual_crm(self, mock_which, logger): + def test_virtual_crm(self, mock_which): mock_pkg_version = MagicMock(return_value='1.0.0') mock_pkg_version_cmp = MagicMock(return_value=1) @@ -46,10 +45,6 @@ 'pkg.version_cmp': mock_pkg_version_cmp}): assert crmshmod.__virtual__() == 'crm' mock_which.assert_called_once_with(crmshmod.CRM_COMMAND) - logger.assert_has_calls([ - mock.call('crmsh version: %s', '1.0.0'), - mock.call('%s will be used', 'crm') - ]) @mock.patch('salt.utils.path.which') def test_virtual_crm_error(self, mock_which): @@ -61,9 +56,8 @@ ' is not available.') mock_which.assert_called_once_with(crmshmod.CRM_COMMAND) - @mock.patch('logging.Logger.info') @mock.patch('salt.utils.path.which') - def test_virtual_ha(self, mock_which, logger): + def test_virtual_ha(self, mock_which): mock_pkg_version = MagicMock(return_value='1.0.0') mock_pkg_version_cmp = MagicMock(return_value=-1) @@ -72,18 +66,13 @@ 'pkg.version': mock_pkg_version, 'pkg.version_cmp': mock_pkg_version_cmp}): assert crmshmod.__virtual__() == 'crm' - logger.assert_has_calls([ - mock.call('crmsh version: %s', '1.0.0'), - mock.call('%s will be used', 'ha-cluster') - ]) mock_which.assert_has_calls([ mock.call(crmshmod.CRM_COMMAND), mock.call(crmshmod.HA_INIT_COMMAND) ]) - @mock.patch('logging.Logger.info') @mock.patch('salt.utils.path.which') - def test_virtual_ha_error(self, mock_which, logger): + def test_virtual_ha_error(self, mock_which): mock_pkg_version = MagicMock(return_value='1.0.0') mock_pkg_version_cmp = MagicMock(return_value=-1) @@ -92,10 +81,6 @@ 'pkg.version': mock_pkg_version, 'pkg.version_cmp': mock_pkg_version_cmp}): response = crmshmod.__virtual__() - logger.assert_has_calls([ - mock.call('crmsh version: %s', '1.0.0'), - mock.call('%s will be used', 'ha-cluster') - ]) mock_which.assert_has_calls([ mock.call(crmshmod.CRM_COMMAND), mock.call(crmshmod.HA_INIT_COMMAND) @@ -414,7 +399,7 @@ ''' Test cluster_init with crm option ''' - with patch.dict(crmshmod.__salt__, {'crmsh.version': True}): + with patch.dict(crmshmod.__salt__, {'crm.version': True}): crm_init.return_value = 0 value = crmshmod.cluster_init('hacluster', 'dog', 'eth1') assert value == 0 @@ -427,7 +412,7 @@ ''' Test cluster_init with ha_cluster_init option ''' - with patch.dict(crmshmod.__salt__, {'crmsh.version': False}): + with patch.dict(crmshmod.__salt__, {'crm.version': False}): ha_cluster_init.return_value = 0 value = crmshmod.cluster_init('hacluster', 'dog', 'eth1') assert value == 0 @@ -521,7 +506,7 @@ ''' Test cluster_join with crm option ''' - with patch.dict(crmshmod.__salt__, {'crmsh.version': True}): + with patch.dict(crmshmod.__salt__, {'crm.version': True}): crm_join.return_value = 0 value = crmshmod.cluster_join('host', 'dog', 'eth1') assert value == 0 @@ -533,7 +518,7 @@ ''' Test cluster_join with ha_cluster_join option ''' - with patch.dict(crmshmod.__salt__, {'crmsh.version': False}): + with patch.dict(crmshmod.__salt__, {'crm.version': False}): ha_cluster_join.return_value = 0 value = crmshmod.cluster_join('host', 'dog', 'eth1') assert value == 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/tests/unit/modules/test_drbdmod.py new/salt-shaptools-0.2.5/tests/unit/modules/test_drbdmod.py --- old/salt-shaptools-0.2.3/tests/unit/modules/test_drbdmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/tests/unit/modules/test_drbdmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -29,7 +29,7 @@ Test cases for salt.modules.drbd ''' def setup_loader_modules(self): - return {drbd: {}} + return {drbd: {'__salt__': {'drbd.json': True}}} # 'overview' function tests: 1 def test_overview(self): @@ -186,7 +186,7 @@ with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): assert drbd.status() == ret - ret = {'Unknown parser': ' single role:Primary'} + # SubTest: Test the _unknown_parser fake = {} fake['stdout'] = ''' single role:Primary @@ -197,7 +197,23 @@ mock_cmd = MagicMock(return_value=fake) with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): - assert drbd.status() == ret + self.assertRaises(exceptions.CommandExecutionError, drbd.status) + + # SubTest: Test the right indent but no expected KEY + fake = {} + fake['stdout'] = ''' +single role:Primary + error-key:UpToDate + opensuse-node2 role:Secondary + replication:SyncSource peer-disk:Inconsistent done:96.47 +''' + fake['stderr'] = "" + fake['retcode'] = 0 + + mock_cmd = MagicMock(return_value=fake) + + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + self.assertRaises(exceptions.CommandExecutionError, drbd.status) def test_createmd(self): ''' @@ -471,16 +487,14 @@ mock_cmd.assert_called_once_with('drbdsetup status --json all') # Test 2: Return code is not 0 - ret = {'name': 'all', - 'result': False, - 'comment': 'Error(10) happend when show resource via drbdsetup.'} - - fake = {'retcode': 10} + fake = {} + fake['stderr'] = "" + fake['retcode'] = 10 mock_cmd = MagicMock(return_value=fake) with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): - assert drbd.setup_status() == ret + assert drbd.setup_status() is None mock_cmd.assert_called_once_with('drbdsetup status --json all') # Test 3: Raise json ValueError @@ -519,8 +533,10 @@ fake['retcode'] = 0 mock_cmd = MagicMock(return_value=fake) + mock_drbdsetup = MagicMock(return_value="") - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert drbd.check_sync_status('beijing') mock_cmd.assert_called_with('drbdadm status beijing') @@ -543,7 +559,8 @@ mock_cmd = MagicMock(return_value=fake) - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert not drbd.check_sync_status('beijing') mock_cmd.assert_called_with('drbdadm status beijing') @@ -566,7 +583,8 @@ mock_cmd = MagicMock(return_value=fake) - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert not drbd.check_sync_status('beijing') mock_cmd.assert_called_with('drbdadm status beijing') @@ -589,7 +607,8 @@ mock_cmd = MagicMock(return_value=fake) - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert drbd.check_sync_status('beijing', peernode='node3') # Test 4.1: Test status return Error @@ -600,7 +619,8 @@ mock_cmd = MagicMock(return_value=fake) - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert not drbd.check_sync_status('beijing') # Test 4.2: Test status return Error @@ -626,5 +646,76 @@ fake1['retcode'] = 1 mock_cmd = MagicMock(side_effect=[fake, fake1]) - with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd, + 'drbd.json': False}): assert not drbd.check_sync_status('beijing') + + def test_check_sync_status_drbdsetup_json(self): + ''' + Test if check_sync_status function work well with drbdsetup status --json + in drbd9 and drbd-utils >= 9.0.0 + ''' + + # Test 1: Test all UpToDate + fake = {} + fake['stdout'] = ''' +[ +{ + "name": "shanghai", + "node-id": 1, + "role": "Secondary", + "suspended": false, + "write-ordering": "flush", + "devices": [ + { + "volume": 0, + "minor": 2, + "disk-state": "UpToDate", + "client": false, + "quorum": true, + "size": 699332, + "read": 0, + "written": 0, + "al-writes": 0, + "bm-writes": 0, + "upper-pending": 0, + "lower-pending": 0 + } ], + "connections": [ + { + "peer-node-id": 2, + "name": "dummytest-drbd02", + "connection-state": "Connected", + "congested": false, + "peer-role": "Secondary", + "ap-in-flight": 0, + "rs-in-flight": 0, + "peer_devices": [ + { + "volume": 0, + "replication-state": "Established", + "peer-disk-state": "UpToDate", + "peer-client": false, + "resync-suspended": "no", + "received": 0, + "sent": 0, + "out-of-sync": 0, + "pending": 0, + "unacked": 0, + "has-sync-details": false, + "has-online-verify-details": false, + "percent-in-sync": 100.00 + } ] + } ] +} +] + +''' + fake['stderr'] = "" + fake['retcode'] = 0 + + mock_cmd = MagicMock(return_value=fake) + + with patch.dict(drbd.__salt__, {'cmd.run_all': mock_cmd}): + assert drbd.check_sync_status('shanghai') + mock_cmd.assert_called_with('drbdsetup status --json shanghai') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-shaptools-0.2.3/tests/unit/states/test_drbdmod.py new/salt-shaptools-0.2.5/tests/unit/states/test_drbdmod.py --- old/salt-shaptools-0.2.3/tests/unit/states/test_drbdmod.py 2019-11-06 09:36:32.167389490 +0100 +++ new/salt-shaptools-0.2.5/tests/unit/states/test_drbdmod.py 2019-12-09 03:44:43.029007691 +0100 @@ -31,7 +31,8 @@ Test cases for salt.states.drbd ''' def setup_loader_modules(self): - return {drbd: {'__opts__': {'test': False}}} + return {drbd: {'__opts__': {'test': False}, + '__salt__': {'drbd.json': True}}} def test_initialized(self): ''' @@ -143,7 +144,8 @@ mock_status = MagicMock(side_effect=exceptions.CommandExecutionError) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.stopped(RES_NAME) == ret # Test 2: drbd status return empty [] @@ -158,7 +160,8 @@ mock_status = MagicMock(return_value=[{'resource name': 'not_the_same'}]) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.stopped(RES_NAME) == ret def test_get_resource_list(self): @@ -228,7 +231,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.started(RES_NAME) == ret # SubTest 3: The test option @@ -246,7 +250,8 @@ with patch.dict(drbd.__opts__, {'test': True}): with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.started(RES_NAME) == ret # SubTest 4: Error in start @@ -265,6 +270,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.up': mock_up}): assert drbd.started(RES_NAME) == ret mock_up.assert_called_once_with(name=RES_NAME) @@ -285,6 +291,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.up': mock_up}): assert drbd.started(RES_NAME) == ret mock_up.assert_called_once_with(name=RES_NAME) @@ -306,6 +313,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.up': mock_up}): assert drbd.started(RES_NAME) == ret mock_up.assert_called_once_with(name=RES_NAME) @@ -353,7 +361,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.stopped(RES_NAME) == ret # SubTest 3: The test option @@ -371,7 +380,8 @@ with patch.dict(drbd.__opts__, {'test': True}): with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.stopped(RES_NAME) == ret # SubTest 4: Error in stop @@ -390,6 +400,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.down': mock_down}): assert drbd.stopped(RES_NAME) == ret mock_down.assert_called_once_with(name=RES_NAME) @@ -410,6 +421,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.down': mock_down}): assert drbd.stopped(RES_NAME) == ret mock_down.assert_called_once_with(name=RES_NAME) @@ -431,6 +443,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.down': mock_down}): assert drbd.stopped(RES_NAME) == ret mock_down.assert_called_once_with(name=RES_NAME) @@ -478,7 +491,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.promoted(RES_NAME) == ret # SubTest 2.2: Resource is stopped @@ -506,7 +520,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.promoted(RES_NAME) == ret # SubTest 3: The test option @@ -524,7 +539,8 @@ with patch.dict(drbd.__opts__, {'test': True}): with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.promoted(RES_NAME) == ret # SubTest 4: Error in promotion @@ -543,11 +559,12 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.primary': mock_primary}): assert drbd.promoted(RES_NAME) == ret mock_primary.assert_called_once_with(force=False, name=RES_NAME) - # SubTest 5: Succeed in promotion + # SubTest 5.1: Succeed in promotion ret = { 'name': RES_NAME, 'result': True, @@ -563,6 +580,28 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, + 'drbd.primary': mock_primary}): + assert drbd.promoted(RES_NAME) == ret + mock_primary.assert_called_once_with(force=False, name=RES_NAME) + + # SubTest 5.2: Succeed in promotion with json + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {'name': RES_NAME}, + 'comment': 'Resource {} is promoted.'.format(RES_NAME), + } + + res_status = [{'name': RES_NAME, 'role': 'Secondary'}] + + mock_cmd = MagicMock(side_effect=[0, 1]) + mock_status = MagicMock(return_value=res_status) + mock_primary = MagicMock(return_value=0) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status, + 'drbd.json': True, 'drbd.primary': mock_primary}): assert drbd.promoted(RES_NAME) == ret mock_primary.assert_called_once_with(force=False, name=RES_NAME) @@ -584,6 +623,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.primary': mock_primary}): assert drbd.promoted(RES_NAME) == ret mock_primary.assert_called_once_with(force=False, name=RES_NAME) @@ -631,7 +671,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.demoted(RES_NAME) == ret # SubTest 2.2: Resource is stopped @@ -659,7 +700,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.demoted(RES_NAME) == ret # SubTest 3: The test option @@ -677,7 +719,8 @@ with patch.dict(drbd.__opts__, {'test': True}): with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.demoted(RES_NAME) == ret # SubTest 4: Error in demotion @@ -696,11 +739,12 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.secondary': mock_secondary}): assert drbd.demoted(RES_NAME) == ret mock_secondary.assert_called_once_with(name=RES_NAME) - # SubTest 5: Succeed in demotion + # SubTest 5.1: Succeed in demotion ret = { 'name': RES_NAME, 'result': True, @@ -716,6 +760,28 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, + 'drbd.secondary': mock_secondary}): + assert drbd.demoted(RES_NAME) == ret + mock_secondary.assert_called_once_with(name=RES_NAME) + + # SubTest 5.2: Succeed in demotion with json + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {'name': RES_NAME}, + 'comment': 'Resource {} is demoted.'.format(RES_NAME), + } + + res_status = [{'name': RES_NAME, 'role': 'Primary'}] + + mock_cmd = MagicMock(side_effect=[0, 1]) + mock_status = MagicMock(return_value=res_status) + mock_secondary = MagicMock(return_value=0) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status, + 'drbd.json': True, 'drbd.secondary': mock_secondary}): assert drbd.demoted(RES_NAME) == ret mock_secondary.assert_called_once_with(name=RES_NAME) @@ -737,6 +803,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.secondary': mock_secondary}): assert drbd.demoted(RES_NAME) == ret mock_secondary.assert_called_once_with(name=RES_NAME) @@ -786,6 +853,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.check_sync_status': mock_sync_status}): assert drbd.wait_for_successful_synced(RES_NAME) == ret @@ -814,7 +882,8 @@ mock_status = MagicMock(return_value=res_status) with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, - 'drbd.status': mock_status}): + 'drbd.status': mock_status, + 'drbd.json': False}): assert drbd.wait_for_successful_synced(RES_NAME) == ret # SubTest 3: The test option @@ -835,6 +904,7 @@ with patch.dict(drbd.__opts__, {'test': True}): with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.check_sync_status': mock_sync_status}): assert drbd.wait_for_successful_synced(RES_NAME) == ret mock_sync_status.assert_called_once_with(name=RES_NAME) @@ -872,6 +942,7 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.check_sync_status': mock_sync_status}): with patch.object(time, 'time', mock_time_time): with patch.object(time, 'sleep', mock_time_sleep): @@ -902,11 +973,12 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.check_sync_status': mock_sync_status}): - with patch.object(time, 'time', mock_time_time): - with patch.object(time, 'sleep', mock_time_sleep): - assert drbd.wait_for_successful_synced(RES_NAME, interval=0.3, timeout=1) == ret - mock_sync_status.assert_called_with(name=RES_NAME) + with patch.object(time, 'time', mock_time_time), \ + patch.object(time, 'sleep', mock_time_sleep): + assert drbd.wait_for_successful_synced(RES_NAME, interval=0.3, timeout=1) == ret + mock_sync_status.assert_called_with(name=RES_NAME) # SubTest 6: Command error ret = { @@ -933,8 +1005,179 @@ with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, 'drbd.status': mock_status, + 'drbd.json': False, 'drbd.check_sync_status': mock_sync_status}): - with patch.object(time, 'time', mock_time_time): - with patch.object(time, 'sleep', mock_time_sleep): - assert drbd.wait_for_successful_synced(RES_NAME, interval=0.3, timeout=1) == ret - mock_sync_status.assert_called_with(name=RES_NAME) + with patch.object(time, 'time', mock_time_time), \ + patch.object(time, 'sleep', mock_time_sleep): + assert drbd.wait_for_successful_synced(RES_NAME, interval=0.3, timeout=1) == ret + mock_sync_status.assert_called_with(name=RES_NAME) + + def test_get_resource_status_drbdsetup_json(self): + ''' + Test __get_res_drbdsetup_status with json support via started() + and wait_for_successful_synced(). + ''' + + # SubTest 1: Resource is already started with json support. started() + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {}, + 'comment': 'Resource {} is already started.'.format(RES_NAME), + } + + # A full 'drbdsetup status --json xxx' output: + # [ + # { + # "name": "shanghai", + # "node-id": 1, + # "role": "Primary", + # "suspended": false, + # "write-ordering": "flush", + # "devices": [ + # { + # "volume": 0, + # "minor": 2, + # "disk-state": "UpToDate", + # "client": false, + # "quorum": true, + # "size": 699332, + # "read": 21, + # "written": 2504, + # "al-writes": 2, + # "bm-writes": 0, + # "upper-pending": 0, + # "lower-pending": 0 + # } ], + # "connections": [ + # { + # "peer-node-id": 2, + # "name": "dummytest-drbd02", + # "connection-state": "Connected", + # "congested": false, + # "peer-role": "Secondary", + # "ap-in-flight": 0, + # "rs-in-flight": 0, + # "peer_devices": [ + # { + # "volume": 0, + # "replication-state": "Established", + # "peer-disk-state": "UpToDate", + # "peer-client": false, + # "resync-suspended": "no", + # "received": 0, + # "sent": 2504, + # "out-of-sync": 0, + # "pending": 0, + # "unacked": 0, + # "has-sync-details": false, + # "has-online-verify-details": false, + # "percent-in-sync": 100.00 + # } ] + # } ] + # } + # ] + # + # A full resource example + # {'name': 'shanghai', 'node-id': 1, 'role': 'Primary', 'suspended': False, 'write-ordering': 'flush', + # 'devices': [{'volume': 0, 'minor': 2, 'disk-state': 'UpToDate', 'client': False, 'quorum': True, + # 'size': 699332, 'read': 21, 'written': 2880, 'al-writes': 2, 'bm-writes': 0, + # 'upper-pending': 0, 'lower-pending': 0}], + # 'connections': [{'peer-node-id': 2, 'name': 'dummytest-drbd02', 'connection-state': 'Connected', + # 'congested': False, 'peer-role': 'Secondary', 'ap-in-flight': 0, 'rs-in-flight': 0, + # 'peer_devices': [{'volume': 0, 'replication-state': 'Established', + # 'peer-disk-state': 'UpToDate', 'peer-client': False, + # 'resync-suspended': 'no', 'received': 0, 'sent': 2880, + # 'out-of-sync': 0, 'pending': 0, 'unacked': 0, + # 'has-sync-details': False, 'has-online-verify-details': False, + # 'percent-in-sync': 100.0}] + # }] + # } + + res_status = [{'name': RES_NAME}] + + mock_cmd = MagicMock(return_value=0) + mock_status = MagicMock(return_value=res_status) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status}): + assert drbd.started(RES_NAME) == ret + + # Test 2: drbdsetup status --json raise Exception + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {}, + 'comment': 'Resource {} is already stopped.'.format(RES_NAME), + } + + mock_cmd = MagicMock(return_value=0) + mock_status = MagicMock(side_effect=exceptions.CommandExecutionError) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status}): + assert drbd.stopped(RES_NAME) == ret + + # Test 3: drbdsetup status --json get empty result + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {}, + 'comment': 'Resource {} is already stopped.'.format(RES_NAME), + } + + mock_cmd = MagicMock(return_value=0) + mock_status = MagicMock(return_value=None) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status}): + assert drbd.stopped(RES_NAME) == ret + + # Test 4: drbdsetup status --json get result but name is not the same + ret = { + 'name': RES_NAME, + 'result': True, + 'changes': {}, + 'comment': 'Resource {} is already stopped.'.format(RES_NAME), + } + + result = [{'name': 'not same name'}] + + mock_cmd = MagicMock(return_value=0) + mock_status = MagicMock(return_value=result) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status}): + assert drbd.stopped(RES_NAME) == ret + + # SubTest 5: Command error with json support. wait_for_successful_synced() + ret = { + 'name': RES_NAME, + 'result': False, + 'changes': {}, + 'comment': 'drbd.check_sync_status: (drdbsetup status --json {}) error.'.format( + RES_NAME), + } + + res_status = [{'name': RES_NAME}] + + mock_cmd = MagicMock(side_effect=[0, 1]) + mock_status = MagicMock(return_value=res_status) + mock_sync_status = MagicMock(side_effect=[0, 0, 0, exceptions.CommandExecutionError( + 'drbd.check_sync_status: (drdbsetup status --json {}) error.'.format(RES_NAME))]) + + mock_time_sleep = MagicMock() + mock_time_time = MagicMock(side_effect=[1557121667.98029, + 1557121667.99029, + 1557121668.29029, + 1557121668.59029, + 1557121668.89029, + 1557121669.19029]) + + with patch.dict(drbd.__salt__, {'cmd.retcode': mock_cmd, + 'drbd.setup_status': mock_status, + 'drbd.check_sync_status': mock_sync_status}): + with patch.object(time, 'time', mock_time_time), \ + patch.object(time, 'sleep', mock_time_sleep): + assert drbd.wait_for_successful_synced(RES_NAME, interval=0.3, timeout=1) == ret + mock_sync_status.assert_called_with(name=RES_NAME)
