""" Unitary tests for crmsh/lock.py :author: xinliang :organization: SUSE Linux GmbH :contact: XLiang@suse.de :since: 2020-12-18 """ # pylint:disable=C0103,C0111,W0212,W0611 import unittest try: from unittest import mock except ImportError: import mock from crmsh import lock, config class TestLock(unittest.TestCase): """ Unitary tests for crmsh.lock.Lock """ @classmethod def setUpClass(cls): """ Global setUp. """ def setUp(self): """ Test setUp. """ self.local_inst = lock.Lock() def tearDown(self): """ Test tearDown. """ @classmethod def tearDownClass(cls): """ Global tearDown. """ @mock.patch('crmsh.sh.ShellUtils.get_stdout_stderr') def test_run(self, mock_run): mock_run.return_value = (0, "output data", None) rc, out, err = self.local_inst._run("test_cmd") mock_run.assert_called_once_with("test_cmd") @mock.patch('crmsh.lock.Lock._run') def test_create_lock_dir_false(self, mock_run): mock_run.return_value = (1, None, None) rc = self.local_inst._create_lock_dir() self.assertEqual(rc, False) mock_run.assert_called_once_with("mkdir {}".format(lock.Lock.LOCK_DIR_DEFAULT)) @mock.patch('crmsh.lock.Lock._run') def test_create_lock_dir(self, mock_run): mock_run.return_value = (0, None, None) rc = self.local_inst._create_lock_dir() self.assertEqual(rc, True) mock_run.assert_called_once_with("mkdir {}".format(lock.Lock.LOCK_DIR_DEFAULT)) @mock.patch('crmsh.lock.Lock._create_lock_dir') def test_lock_or_fail(self, mock_create): mock_create.return_value = False with self.assertRaises(lock.ClaimLockError) as err: self.local_inst._lock_or_fail() self.assertEqual("Failed to claim lock (the lock directory exists at {})".format(lock.Lock.LOCK_DIR_DEFAULT), str(err.exception)) mock_create.assert_called_once_with() @mock.patch('crmsh.lock.Lock._run') def test_unlock(self, mock_run): self.local_inst.lock_owner = True self.local_inst._unlock() mock_run.assert_called_once_with("rm -rf {}".format(lock.Lock.LOCK_DIR_DEFAULT)) @mock.patch('crmsh.lock.Lock._unlock') @mock.patch('crmsh.lock.Lock._lock_or_fail') def test_lock_exception(self, mock_lock, mock_unlock): mock_lock.side_effect = lock.ClaimLockError with self.assertRaises(lock.ClaimLockError): with self.local_inst.lock(): pass mock_lock.assert_called_once_with() mock_unlock.assert_called_once_with() @mock.patch('crmsh.lock.Lock._unlock') @mock.patch('crmsh.lock.Lock._lock_or_fail') def test_lock(self, mock_lock, mock_unlock): with self.local_inst.lock(): pass mock_lock.assert_called_once_with() mock_unlock.assert_called_once_with() class TestRemoteLock(unittest.TestCase): """ Unitary tests for crmsh.lock.RemoteLock """ @classmethod def setUpClass(cls): """ Global setUp. """ def setUp(self): """ Test setUp. """ self.lock_inst = lock.RemoteLock("node1") self.lock_inst_no_wait = lock.RemoteLock("node1", wait=False) def tearDown(self): """ Test tearDown. """ @classmethod def tearDownClass(cls): """ Global tearDown. """ @mock.patch('crmsh.sh.ClusterShell.get_rc_stdout_stderr_without_input') def test_run_ssh_error(self, mock_run): mock_run.return_value = (255, None, "ssh error") with self.assertRaises(lock.SSHError) as err: self.lock_inst._run("cmd") self.assertEqual("ssh error", str(err.exception)) mock_run.assert_called_once_with("node1", "cmd") @mock.patch('crmsh.sh.ClusterShell.get_rc_stdout_stderr_without_input') def test_run(self, mock_run): mock_run.return_value = (0, None, None) res = self.lock_inst._run("cmd") self.assertEqual(res, mock_run.return_value) mock_run.assert_called_once_with("node1", "cmd") def test_lock_timeout_error_format(self): config.core.lock_timeout = "pwd" with self.assertRaises(ValueError) as err: self.lock_inst.lock_timeout self.assertEqual("Invalid format of core.lock_timeout(should be a number)", str(err.exception)) def test_lock_timeout_min_error(self): config.core.lock_timeout = "12" with self.assertRaises(ValueError) as err: self.lock_inst.lock_timeout self.assertEqual("Minimum value of core.lock_timeout should be 120", str(err.exception)) def test_lock_timeout(self): config.core.lock_timeout = "130" self.assertEqual(self.lock_inst.lock_timeout, 130) @mock.patch('crmsh.lock.RemoteLock._run') def test_get_online_nodelist_error(self, mock_run): mock_run.return_value = (1, None, "error data") with self.assertRaises(ValueError) as err: self.lock_inst._get_online_nodelist() self.assertEqual("error data", str(err.exception)) mock_run.assert_called_once_with("crm_node -l") @mock.patch('crmsh.lock.RemoteLock._run') def test_get_online_nodelist(self, mock_run): output = """ 1084783297 15sp2-1 member 1084783193 15sp2-2 lost 1084783331 15sp2-3 member """ mock_run.return_value = (0, output, None) res = self.lock_inst._get_online_nodelist() self.assertEqual(res, ["15sp2-1", "15sp2-3"]) mock_run.assert_called_once_with("crm_node -l") @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') def test_lock_or_wait_break(self, mock_time, mock_time_out, mock_create): mock_time.return_value = 10000 mock_time_out.return_value = 120 mock_create.return_value = True self.lock_inst._lock_or_wait() mock_time.assert_called_once_with() mock_time_out.assert_called_once_with() @mock.patch('time.sleep') @mock.patch('logging.Logger.warning') @mock.patch('crmsh.lock.RemoteLock._get_online_nodelist') @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') def test_lock_or_wait_timed_out(self, mock_time, mock_time_out, mock_create, mock_get_nodelist, mock_warn, mock_sleep): mock_time.side_effect = [10000, 10121] mock_time_out.return_value = 120 mock_create.return_value = False mock_get_nodelist.return_value = ["node2"] with self.assertRaises(lock.ClaimLockError) as err: self.lock_inst._lock_or_wait() self.assertEqual("Timed out after 120 seconds. Cannot continue since the lock directory exists at the node (node1:{})".format(lock.Lock.LOCK_DIR_DEFAULT), str(err.exception)) mock_time.assert_has_calls([ mock.call(), mock.call()]) mock_time_out.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_create.assert_called_once_with() mock_get_nodelist.assert_called_once_with() mock_warn.assert_called_once_with('Might have unfinished process on other nodes, wait %ss...', 120) mock_sleep.assert_called_once_with(10) @mock.patch('time.sleep') @mock.patch('logging.Logger.warning') @mock.patch('crmsh.lock.RemoteLock._get_online_nodelist') @mock.patch('crmsh.lock.Lock._create_lock_dir') @mock.patch('crmsh.lock.RemoteLock.lock_timeout', new_callable=mock.PropertyMock) @mock.patch('time.time') def test_lock_or_wait_again(self, mock_time, mock_time_out, mock_create, mock_get_nodelist, mock_warn, mock_sleep): mock_time.side_effect = [10000, 10010, 10020] mock_time_out.side_effect = [120, 120, 120] mock_create.side_effect = [False, False, True] mock_get_nodelist.side_effect = [["node1"], ["node1", "node2"]] self.lock_inst._lock_or_wait() mock_time.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_time_out.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_create.assert_has_calls([mock.call(), mock.call(), mock.call()]) mock_get_nodelist.assert_has_calls([mock.call(), mock.call()]) mock_warn.assert_called_once_with('Might have unfinished process on other nodes, wait %ss...', 120) mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) @mock.patch('crmsh.lock.Lock._unlock') @mock.patch('crmsh.lock.RemoteLock._lock_or_wait') def test_lock_exception(self, mock_lock, mock_unlock): mock_lock.side_effect = lock.ClaimLockError with self.assertRaises(lock.ClaimLockError): with self.lock_inst.lock(): pass mock_lock.assert_called_once_with() mock_unlock.assert_called_once_with() @mock.patch('crmsh.lock.Lock._unlock') @mock.patch('crmsh.lock.RemoteLock._lock_or_wait') def test_lock(self, mock_lock, mock_unlock): with self.lock_inst.lock(): pass mock_lock.assert_called_once_with() mock_unlock.assert_called_once_with() @mock.patch('crmsh.lock.Lock._unlock') @mock.patch('crmsh.lock.RemoteLock._lock_or_fail') def test_lock_no_wait(self, mock_lock, mock_unlock): with self.lock_inst_no_wait.lock(): pass mock_lock.assert_called_once_with() mock_unlock.assert_called_once_with()