Skip to content

Commit 6440a6a

Browse files
[#329] get_pg_node_state looks for postmaster to avoid "PID file is empty" (#339)
* os_ops v2.1.0 us used * [#329] get_pg_node_state looks for postmaster to avoid "PID file is empty" error This patch suggests a solution to fix an error "PID file is empty" during getting a server state data. When pg_ctl returns this error, we are trying to find postmaster process for our data directory. If we find it then we wait a few seconds and do the next attempt to get server state data. Closes #329. * run_tests.sh is updated (-vvv) * linux.InternalPlatformUtils is refactored (normalization)
1 parent 3ed08c4 commit 6440a6a

File tree

15 files changed

+498
-82
lines changed

15 files changed

+498
-82
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ dependencies = [
6262
"six>=1.9.0",
6363
"psutil",
6464
"packaging",
65-
"testgres.os_ops>=2.0.2,<3.0.0",
65+
"testgres.os_ops>=2.1.0,<3.0.0",
6666
]
6767

6868
[project.urls]

run_tests.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,21 @@ rm -f $COVERAGE_FILE
2626
pip install coverage
2727

2828
# run tests (PATH)
29-
time coverage run -a -m pytest -l -v -n 4 -k "${TEST_FILTER}"
29+
time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}"
3030

3131
# run tests (PG_BIN)
3232
PG_BIN=$(pg_config --bindir) \
33-
time coverage run -a -m pytest -l -v -n 4 -k "${TEST_FILTER}"
33+
time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}"
3434

3535
# run tests (PG_CONFIG)
3636
PG_CONFIG=$(pg_config --bindir)/pg_config \
37-
time coverage run -a -m pytest -l -v -n 4 -k "${TEST_FILTER}"
37+
time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}"
3838

3939
# test pg8000
4040
pip uninstall -y psycopg2
4141
pip install pg8000
4242
PG_CONFIG=$(pg_config --bindir)/pg_config \
43-
time coverage run -a -m pytest -l -v -n 4 -k "${TEST_FILTER}"
43+
time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}"
4444

4545
# show coverage
4646
coverage report

src/impl/internal_utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import logging
2+
3+
4+
def send_log(level: int, msg: str) -> None:
5+
assert type(level) == int # noqa: E721
6+
assert type(msg) == str # noqa: E721
7+
8+
return logging.log(level, "[testgres] " + msg)
9+
10+
11+
def send_log_info(msg: str) -> None:
12+
assert type(msg) == str # noqa: E721
13+
14+
return send_log(logging.INFO, msg)
15+
16+
17+
def send_log_debug(msg: str) -> None:
18+
assert type(msg) == str # noqa: E721
19+
20+
return send_log(logging.DEBUG, msg)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from __future__ import annotations
2+
3+
import enum
4+
import typing
5+
6+
from testgres.operations.os_ops import OsOperations
7+
8+
9+
class InternalPlatformUtils:
10+
class FindPostmasterResultCode(enum.Enum):
11+
ok = 0
12+
not_found = 1,
13+
not_implemented = 2
14+
many_processes = 3
15+
has_problems = 4
16+
17+
class FindPostmasterResult:
18+
code: InternalPlatformUtils.FindPostmasterResultCode
19+
pid: typing.Optional[int]
20+
21+
def __init__(
22+
self,
23+
code: InternalPlatformUtils.FindPostmasterResultCode,
24+
pid: typing.Optional[int]
25+
):
26+
assert type(code) == InternalPlatformUtils.FindPostmasterResultCode # noqa: E721
27+
assert pid is None or type(pid) == int # noqa: E721
28+
self.code = code
29+
self.pid = pid
30+
return
31+
32+
@staticmethod
33+
def create_ok(pid: int) -> InternalPlatformUtils.FindPostmasterResult:
34+
assert type(pid) == int # noqa: E721
35+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.ok, pid)
36+
37+
@staticmethod
38+
def create_not_found() -> InternalPlatformUtils.FindPostmasterResult:
39+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.not_found, None)
40+
41+
@staticmethod
42+
def create_not_implemented() -> InternalPlatformUtils.FindPostmasterResult:
43+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.not_implemented, None)
44+
45+
@staticmethod
46+
def create_many_processes() -> InternalPlatformUtils.FindPostmasterResult:
47+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.many_processes, None)
48+
49+
@staticmethod
50+
def create_has_problems() -> InternalPlatformUtils.FindPostmasterResult:
51+
return __class__(InternalPlatformUtils.FindPostmasterResultCode.has_problems, None)
52+
53+
def FindPostmaster(
54+
self,
55+
os_ops: OsOperations,
56+
bin_dir: str,
57+
data_dir: str
58+
) -> FindPostmasterResult:
59+
assert isinstance(os_ops, OsOperations)
60+
assert type(bin_dir) == str # noqa: E721
61+
assert type(data_dir) == str # noqa: E721
62+
raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from .internal_platform_utils import InternalPlatformUtils
2+
3+
from testgres.operations.os_ops import OsOperations
4+
5+
6+
def create_internal_platform_utils(
7+
os_ops: OsOperations
8+
) -> InternalPlatformUtils:
9+
assert isinstance(os_ops, OsOperations)
10+
11+
platform_name = os_ops.get_platform()
12+
assert type(platform_name) == str # noqa: E721
13+
14+
if platform_name == "linux":
15+
from .linux import internal_platform_utils as x
16+
return x.InternalPlatformUtils()
17+
18+
if platform_name == "win32":
19+
from .win32 import internal_platform_utils as x
20+
return x.InternalPlatformUtils()
21+
22+
# not implemented
23+
return InternalPlatformUtils()
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from __future__ import annotations
2+
3+
from .. import internal_platform_utils as base
4+
from ... import internal_utils
5+
6+
from testgres.operations.os_ops import OsOperations
7+
from testgres.operations.exceptions import ExecUtilException
8+
9+
import re
10+
import shlex
11+
12+
13+
class InternalPlatformUtils(base.InternalPlatformUtils):
14+
C_BASH_EXE = "/bin/bash"
15+
16+
sm_exec_env = {
17+
"LANG": "en_US.UTF-8",
18+
"LC_ALL": "en_US.UTF-8",
19+
}
20+
21+
# --------------------------------------------------------------------
22+
def FindPostmaster(
23+
self,
24+
os_ops: OsOperations,
25+
bin_dir: str,
26+
data_dir: str
27+
) -> InternalPlatformUtils.FindPostmasterResult:
28+
assert isinstance(os_ops, OsOperations)
29+
assert type(bin_dir) == str # noqa: E721
30+
assert type(data_dir) == str # noqa: E721
31+
assert type(__class__.C_BASH_EXE) == str # noqa: E721
32+
assert type(__class__.sm_exec_env) == dict # noqa: E721
33+
assert len(__class__.C_BASH_EXE) > 0
34+
assert len(bin_dir) > 0
35+
assert len(data_dir) > 0
36+
37+
pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres"))
38+
data_dir_e = re.escape(data_dir)
39+
40+
assert type(pg_path_e) == str # noqa: E721
41+
assert type(data_dir_e) == str # noqa: E721
42+
43+
regexp = r"^\s*[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?"
44+
45+
cmd = [
46+
__class__.C_BASH_EXE,
47+
"-c",
48+
"ps -ewwo \"pid=,args=\" | grep -E " + shlex.quote(regexp),
49+
]
50+
51+
exit_status, output_b, error_b = os_ops.exec_command(
52+
cmd=cmd,
53+
ignore_errors=True,
54+
verbose=True,
55+
exec_env=__class__.sm_exec_env,
56+
)
57+
58+
assert type(output_b) == bytes # noqa: E721
59+
assert type(error_b) == bytes # noqa: E721
60+
61+
output = output_b.decode("utf-8")
62+
error = error_b.decode("utf-8")
63+
64+
assert type(output) == str # noqa: E721
65+
assert type(error) == str # noqa: E721
66+
67+
if exit_status == 1:
68+
return __class__.FindPostmasterResult.create_not_found()
69+
70+
if exit_status != 0:
71+
errMsg = f"test command returned an unexpected exit code: {exit_status}"
72+
raise ExecUtilException(
73+
message=errMsg,
74+
command=cmd,
75+
exit_code=exit_status,
76+
out=output,
77+
error=error,
78+
)
79+
80+
lines = output.splitlines()
81+
assert type(lines) == list # noqa: E721
82+
83+
if len(lines) == 0:
84+
return __class__.FindPostmasterResult.create_not_found()
85+
86+
if len(lines) > 1:
87+
msgs = []
88+
msgs.append("Many processes like a postmaster are found: {}.".format(len(lines)))
89+
90+
for i in range(len(lines)):
91+
assert type(lines[i]) == str # noqa: E721
92+
lines.append("[{}] '{}'".format(i, lines[i]))
93+
continue
94+
95+
internal_utils.send_log_debug("\n".join(lines))
96+
return __class__.FindPostmasterResult.create_many_processes()
97+
98+
def is_space_or_tab(ch) -> bool:
99+
assert type(ch) == str # noqa: E721
100+
return ch == " " or ch == "\t"
101+
102+
line = lines[0]
103+
start = 0
104+
while start < len(line) and is_space_or_tab(line[start]):
105+
start += 1
106+
107+
pos = start
108+
while pos < len(line) and line[pos].isnumeric():
109+
pos += 1
110+
111+
if pos == start:
112+
return __class__.FindPostmasterResult.create_has_problems()
113+
114+
if pos != len(line) and not line[pos].isspace():
115+
return __class__.FindPostmasterResult.create_has_problems()
116+
117+
pid = int(line[start:pos])
118+
assert type(pid) == int # noqa: E721
119+
120+
return __class__.FindPostmasterResult.create_ok(pid)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import annotations
2+
3+
from .. import internal_platform_utils as base
4+
from testgres.operations.os_ops import OsOperations
5+
6+
7+
class InternalPlatformUtils(base.InternalPlatformUtils):
8+
def FindPostmaster(
9+
self,
10+
os_ops: OsOperations,
11+
bin_dir: str,
12+
data_dir: str
13+
) -> InternalPlatformUtils.FindPostmasterResult:
14+
assert isinstance(os_ops, OsOperations)
15+
assert type(bin_dir) == str # noqa: E721
16+
assert type(data_dir) == str # noqa: E721
17+
return __class__.FindPostmasterResult.create_not_implemented()

0 commit comments

Comments
 (0)