Files
patroni/tests/test_postmaster.py
Alexander Kukushkin 5296336f4a BUGFIX: postmaster start can fail if pid from postmaster.pid is alive (#681)
Upon start postmaster process performs various safety checks if there is a postmaster.pid file in the data directory. Although Patroni already detected that the running process corresponding to the postmaster.pid is not a postmaster, the new postmaster might fail to start, because it thinks that postmaster.pid is already locked.
Important!!! Unlink of postmaster.pid isn't an option in this case, because it has a lot of nasty race conditions.
Luckily there is a workaround to this problem, we can pass the pid from postmaster.pid in the `PG_GRANDPARENT_PID` environment variable and postmaster will ignore it.

More likely to hit such problem if you run Patroni and postgres in the docker container.
2018-05-18 11:18:27 +02:00

104 lines
4.7 KiB
Python

import psutil
import unittest
from mock import Mock, patch, mock_open
from patroni.postmaster import PostmasterProcess
from six.moves import builtins
class TestPostmasterProcess(unittest.TestCase):
@patch('psutil.Process.__init__', Mock())
def test_init(self):
proc = PostmasterProcess(-123)
self.assertTrue(proc.is_single_user)
@patch('psutil.Process.create_time')
@patch('psutil.Process.__init__')
@patch('patroni.postmaster.PostmasterProcess._read_postmaster_pidfile')
def test_from_pidfile(self, mock_read, mock_init, mock_create_time):
mock_init.side_effect = psutil.NoSuchProcess(123)
mock_read.return_value = {}
self.assertIsNone(PostmasterProcess.from_pidfile(''))
mock_read.return_value = {"pid": "foo"}
self.assertIsNone(PostmasterProcess.from_pidfile(''))
mock_read.return_value = {"pid": "123"}
self.assertIsNone(PostmasterProcess.from_pidfile(''))
mock_init.side_effect = None
with patch.object(psutil.Process, 'pid', 123), \
patch.object(psutil.Process, 'ppid', return_value=124), \
patch('os.getpid', return_value=125) as mock_ospid, \
patch('os.getppid', return_value=126):
self.assertIsNotNone(PostmasterProcess.from_pidfile(''))
mock_create_time.return_value = 100000
mock_read.return_value = {"pid": "123", "start_time": "200000"}
self.assertIsNone(PostmasterProcess.from_pidfile(''))
mock_read.return_value = {"pid": "123", "start_time": "foobar"}
self.assertIsNotNone(PostmasterProcess.from_pidfile(''))
mock_ospid.return_value = 123
mock_read.return_value = {"pid": "123", "start_time": "100000"}
self.assertIsNone(PostmasterProcess.from_pidfile(''))
@patch('psutil.Process.__init__')
def test_from_pid(self, mock_init):
mock_init.side_effect = psutil.NoSuchProcess(123)
self.assertEquals(PostmasterProcess.from_pid(123), None)
mock_init.side_effect = None
self.assertNotEquals(PostmasterProcess.from_pid(123), None)
@patch('psutil.Process.__init__', Mock())
@patch('psutil.Process.send_signal')
@patch('psutil.Process.pid', Mock(return_value=123))
def test_signal_stop(self, mock_send_signal):
proc = PostmasterProcess(-123)
self.assertEquals(proc.signal_stop('immediate'), False)
mock_send_signal.side_effect = [None, psutil.NoSuchProcess(123), psutil.AccessDenied()]
proc = PostmasterProcess(123)
self.assertEquals(proc.signal_stop('immediate'), None)
self.assertEquals(proc.signal_stop('immediate'), True)
self.assertEquals(proc.signal_stop('immediate'), False)
@patch('psutil.Process.__init__', Mock())
@patch('psutil.wait_procs')
def test_wait_for_user_backends_to_close(self, mock_wait):
c1 = Mock()
c1.cmdline = Mock(return_value=["postgres: startup process"])
c2 = Mock()
c2.cmdline = Mock(return_value=["postgres: postgres postgres [local] idle"])
c3 = Mock()
c3.cmdline = Mock(side_effect=psutil.NoSuchProcess(123))
with patch('psutil.Process.children', Mock(return_value=[c1, c2, c3])):
proc = PostmasterProcess(123)
self.assertIsNone(proc.wait_for_user_backends_to_close())
mock_wait.assert_called_with([c2])
c3.cmdline = Mock(side_effect=psutil.AccessDenied(123))
with patch('psutil.Process.children', Mock(return_value=[c3])):
proc = PostmasterProcess(123)
self.assertIsNone(proc.wait_for_user_backends_to_close())
@patch('subprocess.Popen')
@patch.object(PostmasterProcess, 'from_pid')
@patch.object(PostmasterProcess, '_from_pidfile')
def test_start(self, mock_frompidfile, mock_frompid, mock_popen):
mock_frompidfile.return_value._is_postmaster_process.return_value = False
mock_frompid.return_value = "proc 123"
mock_popen.return_value.stdout.readline.return_value = '123'
self.assertEquals(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123")
mock_frompid.assert_called_with(123)
mock_frompidfile.side_effect = psutil.NoSuchProcess(123)
self.assertEquals(PostmasterProcess.start('true', '/tmp', '/tmp/test.conf', []), "proc 123")
@patch('psutil.Process.__init__', Mock(side_effect=psutil.NoSuchProcess(123)))
def test_read_postmaster_pidfile(self):
with patch.object(builtins, 'open', Mock(side_effect=IOError)):
self.assertIsNone(PostmasterProcess.from_pidfile(''))
with patch.object(builtins, 'open', mock_open(read_data='123\n')):
self.assertIsNone(PostmasterProcess.from_pidfile(''))