mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-01-27 18:20:05 +00:00
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.
104 lines
4.7 KiB
Python
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(''))
|