testgres 1.14.4__tar.gz → 1.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testgres-1.14.4/testgres.egg-info → testgres-1.15.0}/PKG-INFO +2 -2
- {testgres-1.14.4 → testgres-1.15.0}/pyproject.toml +1 -1
- {testgres-1.14.4 → testgres-1.15.0}/src/__init__.py +3 -3
- {testgres-1.14.4 → testgres-1.15.0}/src/connection.py +2 -2
- {testgres-1.14.4 → testgres-1.15.0}/src/consts.py +4 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/defaults.py +24 -2
- {testgres-1.14.4 → testgres-1.15.0}/src/enums.py +1 -1
- testgres-1.15.0/src/impl/file_line_reader.py +80 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/internal_platform_utils.py +9 -0
- testgres-1.15.0/src/impl/platforms/linux/internal_platform_utils.py +429 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/win32/internal_platform_utils.py +10 -0
- testgres-1.15.0/src/impl/port_manager__generic2.py +166 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/port_manager__this_host.py +2 -1
- testgres-1.15.0/src/logger.py +116 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/node.py +197 -60
- {testgres-1.14.4 → testgres-1.15.0}/src/pubsub.py +154 -29
- {testgres-1.14.4 → testgres-1.15.0}/src/raise_error.py +37 -5
- {testgres-1.14.4 → testgres-1.15.0}/src/utils.py +43 -22
- {testgres-1.14.4 → testgres-1.15.0/testgres.egg-info}/PKG-INFO +2 -2
- {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/SOURCES.txt +2 -0
- {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/requires.txt +1 -1
- testgres-1.15.0/tests/test_os_ops_common.py +3906 -0
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_os_ops_local.py +42 -6
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_os_ops_remote.py +57 -13
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_common.py +183 -22
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_local.py +0 -35
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_testgres_remote.py +31 -9
- testgres-1.14.4/src/impl/platforms/linux/internal_platform_utils.py +0 -120
- testgres-1.14.4/src/logger.py +0 -49
- testgres-1.14.4/tests/test_os_ops_common.py +0 -1400
- {testgres-1.14.4 → testgres-1.15.0}/LICENSE +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/README.md +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/setup.cfg +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/api.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/backup.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/cache.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/config.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/decorators.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/exceptions.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/internal_utils.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/impl/port_manager__generic.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/node_app.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/port_manager.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/src/standby.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/dependency_links.txt +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/testgres.egg-info/top_level.txt +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_api.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_config.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_raise_error.py +0 -0
- {testgres-1.14.4 → testgres-1.15.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.0
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -27,7 +27,7 @@ Requires-Dist: port-for>=0.4
|
|
|
27
27
|
Requires-Dist: six>=1.9.0
|
|
28
28
|
Requires-Dist: psutil
|
|
29
29
|
Requires-Dist: packaging
|
|
30
|
-
Requires-Dist: testgres.os_ops<
|
|
30
|
+
Requires-Dist: testgres.os_ops<4.0.0,>=3.1.0
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
[](https://github.com/postgrespro/testgres/actions/workflows/ci.yml)
|
|
@@ -41,8 +41,8 @@ from .node_app import NodeApp
|
|
|
41
41
|
from .utils import \
|
|
42
42
|
reserve_port, \
|
|
43
43
|
release_port, \
|
|
44
|
-
bound_ports, \
|
|
45
44
|
get_bin_path, \
|
|
45
|
+
get_bin_dir, \
|
|
46
46
|
get_pg_config, \
|
|
47
47
|
get_pg_version, \
|
|
48
48
|
parse_pg_version
|
|
@@ -57,7 +57,7 @@ from testgres.operations.os_ops import OsOperations, ConnectionParams
|
|
|
57
57
|
from testgres.operations.local_ops import LocalOperations
|
|
58
58
|
from testgres.operations.remote_ops import RemoteOperations
|
|
59
59
|
|
|
60
|
-
__version__ = "1.
|
|
60
|
+
__version__ = "1.15.0"
|
|
61
61
|
|
|
62
62
|
__all__ = [
|
|
63
63
|
"get_new_node",
|
|
@@ -72,7 +72,7 @@ __all__ = [
|
|
|
72
72
|
"NodeApp",
|
|
73
73
|
"PostgresNode",
|
|
74
74
|
"PortManager",
|
|
75
|
-
"reserve_port", "release_port", "
|
|
75
|
+
"reserve_port", "release_port", "get_bin_path", "get_bin_dir", "get_pg_config", "get_pg_version", "parse_pg_version",
|
|
76
76
|
"First", "Any",
|
|
77
77
|
"OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams"
|
|
78
78
|
]
|
|
@@ -14,7 +14,7 @@ from .enums import IsolationLevel
|
|
|
14
14
|
|
|
15
15
|
from .defaults import \
|
|
16
16
|
default_dbname, \
|
|
17
|
-
|
|
17
|
+
default_username2
|
|
18
18
|
|
|
19
19
|
from .exceptions import QueryException
|
|
20
20
|
|
|
@@ -38,7 +38,7 @@ class NodeConnection(object):
|
|
|
38
38
|
|
|
39
39
|
# Set default arguments
|
|
40
40
|
dbname = dbname or default_dbname()
|
|
41
|
-
username = username or
|
|
41
|
+
username = username or default_username2(node.os_ops)
|
|
42
42
|
|
|
43
43
|
self._node = node
|
|
44
44
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import struct
|
|
3
3
|
import uuid
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from testgres.operations.os_ops import OsOperations
|
|
4
7
|
|
|
5
8
|
from .config import testgres_config as tconf
|
|
6
9
|
|
|
@@ -13,11 +16,30 @@ def default_dbname():
|
|
|
13
16
|
return 'postgres'
|
|
14
17
|
|
|
15
18
|
|
|
16
|
-
def default_username():
|
|
19
|
+
def default_username(os_ops: typing.Optional[OsOperations] = None) -> str:
|
|
17
20
|
"""
|
|
18
21
|
Return default username (current user).
|
|
19
22
|
"""
|
|
20
|
-
|
|
23
|
+
assert os_ops is None or isinstance(os_ops, OsOperations)
|
|
24
|
+
|
|
25
|
+
if os_ops is None:
|
|
26
|
+
os_ops = tconf.os_ops
|
|
27
|
+
|
|
28
|
+
assert isinstance(os_ops, OsOperations)
|
|
29
|
+
result = default_username2(os_ops)
|
|
30
|
+
assert type(result) is str
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def default_username2(os_ops: OsOperations) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Return default username (current user).
|
|
37
|
+
"""
|
|
38
|
+
assert isinstance(os_ops, OsOperations)
|
|
39
|
+
|
|
40
|
+
result = os_ops.get_user()
|
|
41
|
+
assert type(result) is str
|
|
42
|
+
return result
|
|
21
43
|
|
|
22
44
|
|
|
23
45
|
def generate_app_name():
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from testgres.operations.os_ops import OsOperations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileLineReader:
|
|
9
|
+
_os_ops: OsOperations
|
|
10
|
+
_file_name: str
|
|
11
|
+
_file_encoding: str
|
|
12
|
+
_file_pos: int
|
|
13
|
+
_buffer_pos: int
|
|
14
|
+
_buffer: bytes
|
|
15
|
+
|
|
16
|
+
# --------------------------------------------------------------------
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
os_ops: OsOperations,
|
|
20
|
+
file_name: str,
|
|
21
|
+
file_encoding: str = "utf-8",
|
|
22
|
+
):
|
|
23
|
+
assert isinstance(os_ops, OsOperations)
|
|
24
|
+
assert type(file_encoding) is str
|
|
25
|
+
self._os_ops = os_ops
|
|
26
|
+
self._file_name = file_name
|
|
27
|
+
self._file_encoding = file_encoding
|
|
28
|
+
self._file_pos = 0
|
|
29
|
+
self._buffer_pos = 0
|
|
30
|
+
self._buffer = b''
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# interface ----------------------------------------------------------
|
|
34
|
+
def read_line(self) -> typing.Optional[str]:
|
|
35
|
+
assert isinstance(self._os_ops, OsOperations)
|
|
36
|
+
assert type(self._buffer_pos) is int
|
|
37
|
+
assert type(self._buffer) is bytes
|
|
38
|
+
assert self._buffer_pos >= 0
|
|
39
|
+
assert self._buffer_pos <= len(self._buffer)
|
|
40
|
+
|
|
41
|
+
scan_pos = self._buffer_pos
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
sz1 = len(self._buffer)
|
|
45
|
+
|
|
46
|
+
if scan_pos == sz1:
|
|
47
|
+
block = self._os_ops.read_binary(
|
|
48
|
+
self._file_name,
|
|
49
|
+
self._file_pos,
|
|
50
|
+
)
|
|
51
|
+
assert type(block) is bytes
|
|
52
|
+
self._buffer += block
|
|
53
|
+
self._file_pos += len(block)
|
|
54
|
+
|
|
55
|
+
x = self._buffer.find(b'\n', scan_pos)
|
|
56
|
+
|
|
57
|
+
sz2 = len(self._buffer)
|
|
58
|
+
|
|
59
|
+
if x == -1:
|
|
60
|
+
if scan_pos == sz2:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
if self._buffer_pos == 0:
|
|
64
|
+
scan_pos = sz2
|
|
65
|
+
else:
|
|
66
|
+
assert self._buffer_pos > 0
|
|
67
|
+
self._buffer = self._buffer[self._buffer_pos:]
|
|
68
|
+
scan_pos = sz2 - self._buffer_pos
|
|
69
|
+
self._buffer_pos = 0
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
assert x >= 0
|
|
73
|
+
assert x < sz2
|
|
74
|
+
|
|
75
|
+
b = self._buffer[self._buffer_pos:(x+1)]
|
|
76
|
+
|
|
77
|
+
s = b.decode(self._file_encoding)
|
|
78
|
+
|
|
79
|
+
self._buffer_pos = x + 1
|
|
80
|
+
return s
|
|
@@ -60,3 +60,12 @@ class InternalPlatformUtils:
|
|
|
60
60
|
assert type(bin_dir) is str
|
|
61
61
|
assert type(data_dir) is str
|
|
62
62
|
raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.")
|
|
63
|
+
|
|
64
|
+
def ProcessIsZombi_soft_check(
|
|
65
|
+
self,
|
|
66
|
+
os_ops: OsOperations,
|
|
67
|
+
pid: int,
|
|
68
|
+
) -> typing.Optional[bool]:
|
|
69
|
+
assert isinstance(os_ops, OsOperations)
|
|
70
|
+
assert type(pid) is int
|
|
71
|
+
raise NotImplementedError("InternalPlatformUtils::ProcessIsZombi_soft_ver is not implemented.")
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .. import internal_platform_utils as base
|
|
4
|
+
from ... import internal_utils
|
|
5
|
+
from ....raise_error import RaiseError
|
|
6
|
+
|
|
7
|
+
from testgres.operations.os_ops import OsOperations
|
|
8
|
+
from testgres.operations.exceptions import ExecUtilException
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
import typing
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
17
|
+
C_MAX_FIND_POSTMASTER_ATTEMPTS = 5
|
|
18
|
+
C_BASH_EXE = "/bin/bash"
|
|
19
|
+
|
|
20
|
+
sm_exec_env = {
|
|
21
|
+
"LANG": "en_US.UTF-8",
|
|
22
|
+
"LC_ALL": "en_US.UTF-8",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# --------------------------------------------------------------------
|
|
26
|
+
def FindPostmaster(
|
|
27
|
+
self,
|
|
28
|
+
os_ops: OsOperations,
|
|
29
|
+
bin_dir: str,
|
|
30
|
+
data_dir: str
|
|
31
|
+
) -> InternalPlatformUtils.FindPostmasterResult:
|
|
32
|
+
assert isinstance(os_ops, OsOperations)
|
|
33
|
+
assert type(bin_dir) is str
|
|
34
|
+
assert type(data_dir) is str
|
|
35
|
+
assert type(__class__.C_BASH_EXE) is str
|
|
36
|
+
assert type(__class__.sm_exec_env) is dict
|
|
37
|
+
assert len(__class__.C_BASH_EXE) > 0
|
|
38
|
+
assert len(bin_dir) > 0
|
|
39
|
+
assert len(data_dir) > 0
|
|
40
|
+
|
|
41
|
+
failures: typing.List[Exception] = []
|
|
42
|
+
|
|
43
|
+
postmaster_pid: typing.Optional[int] = None
|
|
44
|
+
|
|
45
|
+
nAttempts = 0
|
|
46
|
+
|
|
47
|
+
while True:
|
|
48
|
+
nAttempts += 1
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
postmaster_pid = __class__._FindPostmaster(
|
|
52
|
+
os_ops,
|
|
53
|
+
bin_dir,
|
|
54
|
+
data_dir,
|
|
55
|
+
)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
failures.append(e)
|
|
58
|
+
|
|
59
|
+
log_msg = "FindPostmaster (bin_dir={!r}, data_dir={!r}) detects a problem. Exception {}:\n{}".format(
|
|
60
|
+
bin_dir,
|
|
61
|
+
data_dir,
|
|
62
|
+
type(e).__name__,
|
|
63
|
+
e,
|
|
64
|
+
)
|
|
65
|
+
internal_utils.send_log_debug(log_msg)
|
|
66
|
+
|
|
67
|
+
if nAttempts < __class__.C_MAX_FIND_POSTMASTER_ATTEMPTS:
|
|
68
|
+
time.sleep(0.05)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
__class__._find_postmaster__throw_error__fail(
|
|
72
|
+
bin_dir=bin_dir,
|
|
73
|
+
data_dir=data_dir,
|
|
74
|
+
failures=failures,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
if postmaster_pid is None:
|
|
80
|
+
return InternalPlatformUtils.FindPostmasterResult.create_not_found()
|
|
81
|
+
|
|
82
|
+
assert type(postmaster_pid) is int
|
|
83
|
+
|
|
84
|
+
return InternalPlatformUtils.FindPostmasterResult.create_ok(postmaster_pid)
|
|
85
|
+
|
|
86
|
+
# --------------------------------------------------------------------
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _FindPostmaster(
|
|
89
|
+
os_ops: OsOperations,
|
|
90
|
+
bin_dir: str,
|
|
91
|
+
data_dir: str
|
|
92
|
+
) -> typing.Optional[int]:
|
|
93
|
+
assert isinstance(os_ops, OsOperations)
|
|
94
|
+
assert type(bin_dir) is str
|
|
95
|
+
assert type(data_dir) is str
|
|
96
|
+
assert type(__class__.C_BASH_EXE) is str
|
|
97
|
+
assert type(__class__.sm_exec_env) is dict
|
|
98
|
+
assert len(__class__.C_BASH_EXE) > 0
|
|
99
|
+
assert len(bin_dir) > 0
|
|
100
|
+
assert len(data_dir) > 0
|
|
101
|
+
|
|
102
|
+
pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres"))
|
|
103
|
+
data_dir_e = re.escape(data_dir)
|
|
104
|
+
|
|
105
|
+
assert type(pg_path_e) is str
|
|
106
|
+
assert type(data_dir_e) is str
|
|
107
|
+
|
|
108
|
+
regexp = r"^\s*[0-9]+\s+[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?"
|
|
109
|
+
|
|
110
|
+
cmd = [
|
|
111
|
+
__class__.C_BASH_EXE,
|
|
112
|
+
"-c",
|
|
113
|
+
"ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
exec_r = os_ops.exec_command(
|
|
117
|
+
cmd=cmd,
|
|
118
|
+
ignore_errors=True,
|
|
119
|
+
verbose=True,
|
|
120
|
+
exec_env=__class__.sm_exec_env,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert type(exec_r) is tuple
|
|
124
|
+
assert len(exec_r) == 3
|
|
125
|
+
|
|
126
|
+
exit_status, output_b, error_b = exec_r
|
|
127
|
+
|
|
128
|
+
assert type(exit_status) is int
|
|
129
|
+
assert type(output_b) is bytes
|
|
130
|
+
assert type(error_b) is bytes
|
|
131
|
+
|
|
132
|
+
if exit_status == 1:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
output = output_b.decode("utf-8")
|
|
136
|
+
error = error_b.decode("utf-8")
|
|
137
|
+
|
|
138
|
+
assert type(output) is str
|
|
139
|
+
assert type(error) is str
|
|
140
|
+
|
|
141
|
+
if exit_status != 0:
|
|
142
|
+
errMsg = f"test command returned an unexpected exit code: {exit_status}"
|
|
143
|
+
raise ExecUtilException(
|
|
144
|
+
message=errMsg,
|
|
145
|
+
command=cmd,
|
|
146
|
+
exit_code=exit_status,
|
|
147
|
+
out=output,
|
|
148
|
+
error=error,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
lines = output.splitlines()
|
|
152
|
+
assert type(lines) is list
|
|
153
|
+
|
|
154
|
+
if len(lines) == 0:
|
|
155
|
+
# ACHTUNG!
|
|
156
|
+
raise RuntimeError("Command returns 0 error code without output.")
|
|
157
|
+
|
|
158
|
+
# parse result lines
|
|
159
|
+
pid_to_ppid: __class__.T_PID_TO_PPID = {}
|
|
160
|
+
|
|
161
|
+
for i_line in range(len(lines)):
|
|
162
|
+
assert type(lines[i_line]) is str
|
|
163
|
+
|
|
164
|
+
parts = lines[i_line].split()
|
|
165
|
+
assert type(parts) is list
|
|
166
|
+
|
|
167
|
+
if len(parts) < 2:
|
|
168
|
+
__class__._find_postmaster__throw_error__bad_line_format(
|
|
169
|
+
lines,
|
|
170
|
+
i_line,
|
|
171
|
+
"no usefull data",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if not parts[0].isdigit():
|
|
175
|
+
__class__._find_postmaster__throw_error__bad_line_format(
|
|
176
|
+
lines,
|
|
177
|
+
i_line,
|
|
178
|
+
"bad pid",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if not parts[1].isdigit():
|
|
182
|
+
__class__._find_postmaster__throw_error__bad_line_format(
|
|
183
|
+
lines,
|
|
184
|
+
i_line,
|
|
185
|
+
"bad ppid",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
pid = int(parts[0])
|
|
189
|
+
ppid = int(parts[1])
|
|
190
|
+
|
|
191
|
+
if pid not in pid_to_ppid:
|
|
192
|
+
pid_to_ppid[pid] = ppid
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
other_ppid = pid_to_ppid[pid]
|
|
196
|
+
assert type(other_ppid) is int
|
|
197
|
+
|
|
198
|
+
if ppid == other_ppid:
|
|
199
|
+
log_msg = "FindPostmaster (data_dir={!r}) get pid ({}) with ppid ({}) more than one time.".format(
|
|
200
|
+
data_dir,
|
|
201
|
+
pid,
|
|
202
|
+
ppid,
|
|
203
|
+
)
|
|
204
|
+
internal_utils.send_log_debug(log_msg)
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# ACTUNG ppid is changed --> restart
|
|
208
|
+
__class__._find_postmaster__throw_error__ppid_is_changed(
|
|
209
|
+
pid,
|
|
210
|
+
ppid,
|
|
211
|
+
other_ppid,
|
|
212
|
+
lines,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
assert len(pid_to_ppid) <= len(lines)
|
|
216
|
+
|
|
217
|
+
true_postmasters = [
|
|
218
|
+
pid for pid, ppid in pid_to_ppid.items()
|
|
219
|
+
if ppid not in pid_to_ppid
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
if len(true_postmasters) == 0:
|
|
223
|
+
__class__._find_postmaster__throw_error__cycle(
|
|
224
|
+
pid_to_ppid,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if len(true_postmasters) == 1:
|
|
228
|
+
true_pid = true_postmasters[0]
|
|
229
|
+
|
|
230
|
+
if len(pid_to_ppid) > 1:
|
|
231
|
+
msg = "Many processes like a postmaster for data dir [{}] are found ({}).".format(
|
|
232
|
+
data_dir,
|
|
233
|
+
len(true_postmasters),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
msg += " List (ppid->pid): {}.".format(
|
|
237
|
+
__class__._make_text_from_pid_to_ppid(pid_to_ppid),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
msg += " True postmaster PID is {}.".format(true_pid)
|
|
241
|
+
internal_utils.send_log_debug(msg)
|
|
242
|
+
|
|
243
|
+
return true_pid
|
|
244
|
+
|
|
245
|
+
assert len(true_postmasters) > 1
|
|
246
|
+
|
|
247
|
+
__class__._find_postmaster__throw_error__many_postmasters(
|
|
248
|
+
true_postmasters,
|
|
249
|
+
pid_to_ppid,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def ProcessIsZombi_soft_check(
|
|
253
|
+
self,
|
|
254
|
+
os_ops: OsOperations,
|
|
255
|
+
pid: int,
|
|
256
|
+
) -> typing.Optional[bool]:
|
|
257
|
+
assert isinstance(os_ops, OsOperations)
|
|
258
|
+
assert type(pid) is int
|
|
259
|
+
|
|
260
|
+
proc_stat_file = os_ops.build_path("/proc", str(pid), "stat")
|
|
261
|
+
|
|
262
|
+
if not os_ops.path_exists(proc_stat_file):
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
result: typing.Optional[bool] = None
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
# Read one line from /proc/PID/stat
|
|
269
|
+
stat_content = os_ops.read_binary(proc_stat_file, 0).decode("utf-8", errors="ignore")
|
|
270
|
+
|
|
271
|
+
# We look for the closing parenthesis of the process name to ensure that
|
|
272
|
+
# we start from it and not depend on spaces inside the parentheses!
|
|
273
|
+
r_paren_idx = stat_content.rfind(")")
|
|
274
|
+
|
|
275
|
+
if r_paren_idx == -1:
|
|
276
|
+
pass
|
|
277
|
+
elif len(stat_content) <= r_paren_idx + 2:
|
|
278
|
+
pass
|
|
279
|
+
else:
|
|
280
|
+
# The status goes exactly one space after the closing bracket
|
|
281
|
+
assert (r_paren_idx + 2) < len(stat_content)
|
|
282
|
+
proc_status = stat_content[r_paren_idx + 2]
|
|
283
|
+
result = proc_status == "Z"
|
|
284
|
+
except Exception as e:
|
|
285
|
+
# If the file disappeared right during reading, it means the process is completely erased
|
|
286
|
+
if __class__._is_file_not_found_exception(e):
|
|
287
|
+
result = False
|
|
288
|
+
|
|
289
|
+
return result
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _is_file_not_found_exception(e: Exception) -> bool:
|
|
293
|
+
if isinstance(e, FileNotFoundError):
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
if isinstance(e, ExecUtilException):
|
|
297
|
+
if e.exit_code == 2:
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
T_PID_TO_PPID = typing.Dict[int, int]
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def _make_text_from_pid_to_ppid(pid_to_ppid: T_PID_TO_PPID) -> str:
|
|
306
|
+
result = ""
|
|
307
|
+
sep = ""
|
|
308
|
+
for pid, ppid in pid_to_ppid.items():
|
|
309
|
+
result += sep + " {}->{}".format(ppid, pid)
|
|
310
|
+
sep = ", "
|
|
311
|
+
continue
|
|
312
|
+
return result
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def _find_postmaster__throw_error__bad_line_format(
|
|
316
|
+
lines: typing.List[str],
|
|
317
|
+
i_line: int,
|
|
318
|
+
hint: str,
|
|
319
|
+
) -> typing.NoReturn:
|
|
320
|
+
assert type(lines) is list
|
|
321
|
+
assert type(i_line) is int
|
|
322
|
+
assert type(hint) is int
|
|
323
|
+
|
|
324
|
+
error_lines: typing.List[str] = []
|
|
325
|
+
error_lines.append(
|
|
326
|
+
"Line {} has bad format. Hint: {}.".format(
|
|
327
|
+
i_line + 1,
|
|
328
|
+
hint,
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
error_lines.append(
|
|
332
|
+
"Problem line is:"
|
|
333
|
+
)
|
|
334
|
+
error_lines.append(
|
|
335
|
+
" " + repr(lines[i_line]),
|
|
336
|
+
)
|
|
337
|
+
error_lines.append(
|
|
338
|
+
"All the lines is:",
|
|
339
|
+
)
|
|
340
|
+
for i in range(len(lines)):
|
|
341
|
+
error_lines.append(
|
|
342
|
+
" {}. {!r}".format(i + 1, lines[i]),
|
|
343
|
+
)
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
raise RuntimeError("\n".join(error_lines))
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _find_postmaster__throw_error__ppid_is_changed(
|
|
350
|
+
pid: int,
|
|
351
|
+
ppid: int,
|
|
352
|
+
other_ppid: int,
|
|
353
|
+
lines: typing.List[str],
|
|
354
|
+
) -> typing.NoReturn:
|
|
355
|
+
assert type(pid) is int
|
|
356
|
+
assert type(ppid) is int
|
|
357
|
+
assert type(other_ppid) is int
|
|
358
|
+
assert type(lines) is list
|
|
359
|
+
|
|
360
|
+
error_lines: typing.List[str] = []
|
|
361
|
+
error_lines.append(
|
|
362
|
+
"Parent of process ({}) is changed from {} to {}.".format(
|
|
363
|
+
pid,
|
|
364
|
+
other_ppid,
|
|
365
|
+
ppid,
|
|
366
|
+
),
|
|
367
|
+
)
|
|
368
|
+
error_lines.append(
|
|
369
|
+
"All the lines is:",
|
|
370
|
+
)
|
|
371
|
+
for i in range(len(lines)):
|
|
372
|
+
error_lines.append(
|
|
373
|
+
" {}. {!r}".format(i + 1, lines[i]),
|
|
374
|
+
)
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
raise RuntimeError("\n".join(error_lines))
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
def _find_postmaster__throw_error__cycle(
|
|
381
|
+
pid_to_ppid: T_PID_TO_PPID,
|
|
382
|
+
) -> typing.NoReturn:
|
|
383
|
+
msg = "Cycle in processes postgres process tree. "
|
|
384
|
+
|
|
385
|
+
msg += " List (ppid->pid): {},".format(
|
|
386
|
+
__class__._make_text_from_pid_to_ppid(pid_to_ppid),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
msg += " List size is {}.".format(
|
|
390
|
+
len(pid_to_ppid),
|
|
391
|
+
)
|
|
392
|
+
raise RuntimeError(msg)
|
|
393
|
+
|
|
394
|
+
@staticmethod
|
|
395
|
+
def _find_postmaster__throw_error__many_postmasters(
|
|
396
|
+
postmaster_pids: typing.List[int],
|
|
397
|
+
pid_to_ppid: T_PID_TO_PPID,
|
|
398
|
+
) -> typing.NoReturn:
|
|
399
|
+
msg = "Many processes like a postmaster are found ({}): {}.".format(
|
|
400
|
+
len(postmaster_pids),
|
|
401
|
+
", ".join(map(str, postmaster_pids)),
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
msg += " Trees (ppid->pid): {}.".format(
|
|
405
|
+
__class__._make_text_from_pid_to_ppid(pid_to_ppid),
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
msg += " Total process count is {}.".format(len(pid_to_ppid))
|
|
409
|
+
raise RuntimeError(msg)
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _find_postmaster__throw_error__fail(
|
|
413
|
+
bin_dir: str,
|
|
414
|
+
data_dir: str,
|
|
415
|
+
failures: typing.List[Exception],
|
|
416
|
+
) -> typing.NoReturn:
|
|
417
|
+
assert type(bin_dir) is str
|
|
418
|
+
assert type(data_dir) is str
|
|
419
|
+
assert type(failures) is list
|
|
420
|
+
|
|
421
|
+
method_name = "InternalPlatformUtils::FindPostmaster(bin_dir={!r}, data_dir={!r})".format(
|
|
422
|
+
bin_dir,
|
|
423
|
+
data_dir,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
RaiseError.function_did_multiple_attempts_without_stable_result(
|
|
427
|
+
method_name,
|
|
428
|
+
failures,
|
|
429
|
+
)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from .. import internal_platform_utils as base
|
|
4
4
|
from testgres.operations.os_ops import OsOperations
|
|
5
|
+
import typing
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
@@ -15,3 +16,12 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
15
16
|
assert type(bin_dir) is str
|
|
16
17
|
assert type(data_dir) is str
|
|
17
18
|
return __class__.FindPostmasterResult.create_not_implemented()
|
|
19
|
+
|
|
20
|
+
def ProcessIsZombi_soft_check(
|
|
21
|
+
self,
|
|
22
|
+
os_ops: OsOperations,
|
|
23
|
+
pid: int,
|
|
24
|
+
) -> typing.Optional[bool]:
|
|
25
|
+
assert isinstance(os_ops, OsOperations)
|
|
26
|
+
assert type(pid) is int
|
|
27
|
+
return None
|