testgres 1.16.2__tar.gz → 1.16.4__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.16.2/testgres.egg-info → testgres-1.16.4}/PKG-INFO +1 -2
- {testgres-1.16.2 → testgres-1.16.4}/pyproject.toml +0 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/__init__.py +1 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/backup.py +2 -4
- {testgres-1.16.2 → testgres-1.16.4}/src/cache.py +3 -5
- {testgres-1.16.2 → testgres-1.16.4}/src/config.py +1 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/connection.py +1 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/consts.py +3 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/decorators.py +2 -3
- {testgres-1.16.2 → testgres-1.16.4}/src/enums.py +1 -2
- {testgres-1.16.2 → testgres-1.16.4}/src/exceptions.py +2 -5
- testgres-1.16.4/src/impl/internal_node_utils.py +221 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/internal_utils.py +58 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/platforms/darwin/internal_platform_utils.py +1 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/platforms/internal_platform_utils.py +5 -3
- testgres-1.16.4/src/impl/platforms/internal_platform_utils_provider.py +32 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/platforms/linux/internal_platform_utils.py +12 -5
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/platforms/win32/internal_platform_utils.py +1 -1
- {testgres-1.16.2 → testgres-1.16.4}/src/node.py +23 -22
- testgres-1.16.4/src/node_state.py +20 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/port_manager.py +5 -2
- {testgres-1.16.2 → testgres-1.16.4}/src/pubsub.py +3 -5
- {testgres-1.16.2 → testgres-1.16.4}/src/raise_error.py +14 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/standby.py +6 -8
- {testgres-1.16.2 → testgres-1.16.4}/src/utils.py +17 -271
- {testgres-1.16.2 → testgres-1.16.4/testgres.egg-info}/PKG-INFO +1 -2
- {testgres-1.16.2 → testgres-1.16.4}/testgres.egg-info/SOURCES.txt +3 -0
- {testgres-1.16.2 → testgres-1.16.4}/testgres.egg-info/requires.txt +0 -1
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_testgres_common.py +341 -31
- {testgres-1.16.2 → testgres-1.16.4}/LICENSE +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/README.md +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/setup.cfg +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/api.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/defaults.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/file_line_reader.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/platforms/internal_platform_utils_factory.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/port_manager__generic.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/port_manager__generic2.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/impl/port_manager__this_host.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/logger.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/src/node_app.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/testgres.egg-info/dependency_links.txt +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/testgres.egg-info/top_level.txt +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_api.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_config.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_os_ops_common.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_os_ops_local.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_os_ops_remote.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_raise_error.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_testgres_local.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_testgres_remote.py +0 -0
- {testgres-1.16.2 → testgres-1.16.4}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testgres
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.4
|
|
4
4
|
Summary: Testing utility for PostgreSQL and its extensions
|
|
5
5
|
Author-email: Postgres Professional <testgres@postgrespro.ru>
|
|
6
6
|
License: PostgreSQL
|
|
@@ -24,7 +24,6 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: pg8000
|
|
26
26
|
Requires-Dist: port-for>=0.4
|
|
27
|
-
Requires-Dist: six>=1.9.0
|
|
28
27
|
Requires-Dist: psutil
|
|
29
28
|
Requires-Dist: packaging
|
|
30
29
|
Requires-Dist: testgres.os_ops<4.0.0,>=3.3.3
|
|
@@ -60,7 +60,7 @@ from testgres.operations.os_ops import OsProcessController
|
|
|
60
60
|
from testgres.operations.local_ops import LocalOperations
|
|
61
61
|
from testgres.operations.remote_ops import RemoteOperations
|
|
62
62
|
|
|
63
|
-
__version__ = "1.16.
|
|
63
|
+
__version__ = "1.16.4"
|
|
64
64
|
|
|
65
65
|
__all__ = [
|
|
66
66
|
"get_new_node",
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
from six import raise_from
|
|
4
|
-
|
|
5
3
|
from .enums import XLogMethod
|
|
6
4
|
|
|
7
5
|
from .consts import \
|
|
@@ -22,7 +20,7 @@ from .utils import \
|
|
|
22
20
|
clean_on_error
|
|
23
21
|
|
|
24
22
|
|
|
25
|
-
class NodeBackup
|
|
23
|
+
class NodeBackup:
|
|
26
24
|
"""
|
|
27
25
|
Smart object responsible for backups
|
|
28
26
|
"""
|
|
@@ -131,7 +129,7 @@ class NodeBackup(object):
|
|
|
131
129
|
# Copy backup to new data dir
|
|
132
130
|
self.os_ops.copytree(data1, data2)
|
|
133
131
|
except Exception as e:
|
|
134
|
-
|
|
132
|
+
raise BackupException('Failed to copy files') from e
|
|
135
133
|
else:
|
|
136
134
|
dest_base_dir = self.base_dir
|
|
137
135
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
from six import raise_from
|
|
4
|
-
|
|
5
3
|
from .config import testgres_config
|
|
6
4
|
|
|
7
5
|
from .consts import XLOG_CONTROL_FILE
|
|
@@ -49,7 +47,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = No
|
|
|
49
47
|
log,
|
|
50
48
|
)
|
|
51
49
|
except ExecUtilException as e:
|
|
52
|
-
|
|
50
|
+
raise InitNodeException("Failed to run initdb") from e
|
|
53
51
|
|
|
54
52
|
if params or not testgres_config.cache_initdb or not cached:
|
|
55
53
|
call_initdb(data_dir, logfile)
|
|
@@ -89,7 +87,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = No
|
|
|
89
87
|
|
|
90
88
|
except ExecUtilException as e:
|
|
91
89
|
msg = "Failed to reset WAL for system id"
|
|
92
|
-
|
|
90
|
+
raise InitNodeException(msg) from e
|
|
93
91
|
|
|
94
92
|
except Exception as e:
|
|
95
|
-
|
|
93
|
+
raise InitNodeException("Failed to spawn a node") from e
|
|
@@ -17,7 +17,7 @@ log_format = os.getenv('LOGGING_FORMAT', '%(asctime)s - %(levelname)s - %(messag
|
|
|
17
17
|
logging.basicConfig(level=log_level, format=log_format)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class GlobalConfig
|
|
20
|
+
class GlobalConfig:
|
|
21
21
|
"""
|
|
22
22
|
Global configuration object which allows user to override default settings.
|
|
23
23
|
"""
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# binary names
|
|
4
4
|
BINARY_NAME__POSTGRES = "postgres"
|
|
5
|
+
BINARY_NAME__PG_CTL = "pg_ctl"
|
|
5
6
|
|
|
6
7
|
# names for dirs in base_dir
|
|
7
8
|
DATA_DIR = "data"
|
|
@@ -44,5 +45,7 @@ MAX_WAL_SENDERS = 10
|
|
|
44
45
|
LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS = 60
|
|
45
46
|
|
|
46
47
|
PG_CTL__STATUS__OK = 0
|
|
48
|
+
|
|
49
|
+
PG_CTL__STATUS__FAILED = 1
|
|
47
50
|
PG_CTL__STATUS__NODE_IS_STOPPED = 3
|
|
48
51
|
PG_CTL__STATUS__BAD_DATADIR = 4
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import six
|
|
2
1
|
import functools
|
|
3
2
|
|
|
4
3
|
|
|
@@ -18,7 +17,7 @@ def positional_args_hack(*special_cases):
|
|
|
18
17
|
|
|
19
18
|
for case in special_cases:
|
|
20
19
|
k = len(case)
|
|
21
|
-
assert k not in
|
|
20
|
+
assert k not in cases, 'len must be unique'
|
|
22
21
|
cases[k] = case
|
|
23
22
|
|
|
24
23
|
def decorator(function):
|
|
@@ -26,7 +25,7 @@ def positional_args_hack(*special_cases):
|
|
|
26
25
|
def wrapper(*args, **kwargs):
|
|
27
26
|
k = len(args)
|
|
28
27
|
|
|
29
|
-
if k in
|
|
28
|
+
if k in cases:
|
|
30
29
|
case = cases[k]
|
|
31
30
|
|
|
32
31
|
for i in range(0, k):
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from enum import Enum, IntEnum
|
|
2
|
-
from six import iteritems
|
|
3
2
|
from psutil import NoSuchProcess
|
|
4
3
|
|
|
5
4
|
|
|
@@ -83,7 +82,7 @@ class ProcessType(Enum):
|
|
|
83
82
|
if cmdline.startswith(ptype.value.replace(' ', '')):
|
|
84
83
|
return ptype
|
|
85
84
|
|
|
86
|
-
for ptype, names in
|
|
85
|
+
for ptype, names in alternative_names.items():
|
|
87
86
|
for name in names:
|
|
88
87
|
if cmdline.startswith(name.replace(' ', '')):
|
|
89
88
|
return ptype
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
import six
|
|
4
3
|
import typing
|
|
5
4
|
|
|
6
5
|
from testgres.operations.exceptions import TestgresException
|
|
@@ -44,7 +43,6 @@ class PortForException(TestgresException):
|
|
|
44
43
|
return result
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
@six.python_2_unicode_compatible
|
|
48
46
|
class QueryException(TestgresException):
|
|
49
47
|
_description: typing.Optional[str]
|
|
50
48
|
_query: typing.Optional[str]
|
|
@@ -76,7 +74,7 @@ class QueryException(TestgresException):
|
|
|
76
74
|
if self._query:
|
|
77
75
|
msg.append(u'Query: {}'.format(self._query))
|
|
78
76
|
|
|
79
|
-
r =
|
|
77
|
+
r = '\n'.join(msg)
|
|
80
78
|
assert type(r) is str
|
|
81
79
|
return r
|
|
82
80
|
|
|
@@ -162,7 +160,6 @@ class CatchUpException(TestgresException):
|
|
|
162
160
|
return result
|
|
163
161
|
|
|
164
162
|
|
|
165
|
-
@six.python_2_unicode_compatible
|
|
166
163
|
class StartNodeException(TestgresException):
|
|
167
164
|
_description: typing.Optional[str]
|
|
168
165
|
_files: typing.Optional[typing.Iterable]
|
|
@@ -196,7 +193,7 @@ class StartNodeException(TestgresException):
|
|
|
196
193
|
assert type(lines) in [str, bytes]
|
|
197
194
|
msg.append(u'{}\n----\n{}\n'.format(f, lines))
|
|
198
195
|
|
|
199
|
-
return
|
|
196
|
+
return '\n'.join(msg)
|
|
200
197
|
|
|
201
198
|
@property
|
|
202
199
|
def description(self) -> typing.Optional[str]:
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import internal_utils
|
|
4
|
+
from . platforms.internal_platform_utils_provider import InternalPlaformUtilsProvider
|
|
5
|
+
|
|
6
|
+
from .. import consts
|
|
7
|
+
from ..node_state import PostgresNodeState
|
|
8
|
+
from ..enums import NodeStatus
|
|
9
|
+
from ..raise_error import RaiseError
|
|
10
|
+
from ..exceptions import ExecUtilException
|
|
11
|
+
|
|
12
|
+
from testgres.operations.os_ops import OsOperations
|
|
13
|
+
|
|
14
|
+
import typing
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InternalNodeUtils:
|
|
19
|
+
T_PLATFORM_UTILS = InternalPlaformUtilsProvider.T_PLATFORM_UTILS
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def get_pg_node_state(
|
|
23
|
+
os_ops: OsOperations,
|
|
24
|
+
bin_dir: str,
|
|
25
|
+
data_dir: str,
|
|
26
|
+
utils_log_file: typing.Optional[str],
|
|
27
|
+
) -> PostgresNodeState:
|
|
28
|
+
assert isinstance(os_ops, OsOperations)
|
|
29
|
+
assert type(bin_dir) is str
|
|
30
|
+
assert type(data_dir) is str
|
|
31
|
+
assert utils_log_file is None or type(utils_log_file) is str
|
|
32
|
+
|
|
33
|
+
C_MAX_ATTEMPTS = 3
|
|
34
|
+
C_SLEEP_TIME1 = 1
|
|
35
|
+
C_SLEEP_TIME_MULT = 2
|
|
36
|
+
|
|
37
|
+
pg_ctl_params = [
|
|
38
|
+
os_ops.build_path(bin_dir, consts.BINARY_NAME__PG_CTL),
|
|
39
|
+
"-D",
|
|
40
|
+
data_dir,
|
|
41
|
+
"status",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
attempt = 0
|
|
45
|
+
sleep_time = C_SLEEP_TIME1
|
|
46
|
+
|
|
47
|
+
platform_utils_provider = InternalPlaformUtilsProvider(
|
|
48
|
+
os_ops,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
while True:
|
|
52
|
+
assert type(attempt) is int
|
|
53
|
+
assert attempt >= 0
|
|
54
|
+
assert attempt < C_MAX_ATTEMPTS
|
|
55
|
+
|
|
56
|
+
attempt += 1
|
|
57
|
+
|
|
58
|
+
if attempt > 1:
|
|
59
|
+
internal_utils.send_log_debug("Sleep {} second(s) before an attempt #{}".format(
|
|
60
|
+
sleep_time,
|
|
61
|
+
attempt,
|
|
62
|
+
))
|
|
63
|
+
time.sleep(sleep_time)
|
|
64
|
+
sleep_time = sleep_time * C_SLEEP_TIME_MULT
|
|
65
|
+
|
|
66
|
+
exec_r = internal_utils.execute_utility3(
|
|
67
|
+
os_ops,
|
|
68
|
+
pg_ctl_params,
|
|
69
|
+
utils_log_file,
|
|
70
|
+
check=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
status_code = exec_r.returncode
|
|
74
|
+
out = exec_r.stdout
|
|
75
|
+
error = exec_r.stderr
|
|
76
|
+
|
|
77
|
+
assert type(status_code) is int
|
|
78
|
+
assert type(out) is str
|
|
79
|
+
assert type(error) is str
|
|
80
|
+
|
|
81
|
+
# -----------------
|
|
82
|
+
if status_code == consts.PG_CTL__STATUS__NODE_IS_STOPPED:
|
|
83
|
+
return PostgresNodeState(NodeStatus.Stopped, None)
|
|
84
|
+
|
|
85
|
+
# -----------------
|
|
86
|
+
if status_code == consts.PG_CTL__STATUS__BAD_DATADIR:
|
|
87
|
+
return PostgresNodeState(NodeStatus.Uninitialized, None)
|
|
88
|
+
|
|
89
|
+
# -----------------
|
|
90
|
+
if status_code == consts.PG_CTL__STATUS__OK:
|
|
91
|
+
pid = __class__._parse_pid(
|
|
92
|
+
out,
|
|
93
|
+
pg_ctl_params,
|
|
94
|
+
)
|
|
95
|
+
assert type(pid) is int
|
|
96
|
+
assert pid != 0
|
|
97
|
+
|
|
98
|
+
# ----------------- detect zombie
|
|
99
|
+
if platform_utils_provider.get().ProcessIsZombie_soft_check(os_ops, pid) is True:
|
|
100
|
+
internal_utils.send_log_debug("Postmaster process {} is a zombie.".format(
|
|
101
|
+
pid,
|
|
102
|
+
))
|
|
103
|
+
return PostgresNodeState(NodeStatus.Zombie, pid)
|
|
104
|
+
|
|
105
|
+
# -----------------
|
|
106
|
+
return PostgresNodeState(NodeStatus.Running, pid)
|
|
107
|
+
|
|
108
|
+
assert status_code != consts.PG_CTL__STATUS__OK
|
|
109
|
+
|
|
110
|
+
errMsg = "Getting of a node status [data_dir is {0}] failed.".format(
|
|
111
|
+
data_dir,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
e1 = ExecUtilException(
|
|
115
|
+
message=errMsg,
|
|
116
|
+
command=pg_ctl_params,
|
|
117
|
+
exit_code=status_code,
|
|
118
|
+
out=out,
|
|
119
|
+
error=error,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if status_code == consts.PG_CTL__STATUS__FAILED:
|
|
123
|
+
internal_utils.send_log_debug(
|
|
124
|
+
"pg_ctl fails with an error: {}".format(
|
|
125
|
+
exec_r.stderr,
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
find_postmaster_r = platform_utils_provider.get().FindPostmaster(
|
|
130
|
+
os_ops,
|
|
131
|
+
bin_dir,
|
|
132
|
+
data_dir,
|
|
133
|
+
)
|
|
134
|
+
except Exception as e2:
|
|
135
|
+
raise e2 from e1
|
|
136
|
+
|
|
137
|
+
assert type(find_postmaster_r) is __class__.T_PLATFORM_UTILS.FindPostmasterResult
|
|
138
|
+
|
|
139
|
+
if find_postmaster_r.code == __class__.T_PLATFORM_UTILS.FindPostmasterResultCode.ok:
|
|
140
|
+
# Postmaster is alive. Let's wait a few seconds and check its status again.
|
|
141
|
+
internal_utils.send_log_debug(
|
|
142
|
+
"Postmaster is found and has PID {}.".format(
|
|
143
|
+
find_postmaster_r.pid,
|
|
144
|
+
))
|
|
145
|
+
|
|
146
|
+
if attempt < C_MAX_ATTEMPTS:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
raise e1
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _parse_pid(
|
|
153
|
+
out: str,
|
|
154
|
+
pg_ctl_params,
|
|
155
|
+
) -> int:
|
|
156
|
+
assert type(out) is str
|
|
157
|
+
|
|
158
|
+
if out == "":
|
|
159
|
+
RaiseError.pg_ctl_returns_an_empty_string(
|
|
160
|
+
pg_ctl_params,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
C_PID_PREFIX = "(PID: "
|
|
164
|
+
|
|
165
|
+
i = out.find(C_PID_PREFIX)
|
|
166
|
+
|
|
167
|
+
if i == -1:
|
|
168
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
169
|
+
out,
|
|
170
|
+
pg_ctl_params,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
assert i > 0
|
|
174
|
+
assert i < len(out)
|
|
175
|
+
assert len(C_PID_PREFIX) <= len(out)
|
|
176
|
+
assert i <= len(out) - len(C_PID_PREFIX)
|
|
177
|
+
|
|
178
|
+
i += len(C_PID_PREFIX)
|
|
179
|
+
start_pid_s = i
|
|
180
|
+
|
|
181
|
+
while True:
|
|
182
|
+
if i == len(out):
|
|
183
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
184
|
+
out,
|
|
185
|
+
pg_ctl_params,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
ch = out[i]
|
|
189
|
+
|
|
190
|
+
if ch == ")":
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
if ch.isdigit():
|
|
194
|
+
i += 1
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
198
|
+
out,
|
|
199
|
+
pg_ctl_params,
|
|
200
|
+
)
|
|
201
|
+
assert False
|
|
202
|
+
|
|
203
|
+
if i == start_pid_s:
|
|
204
|
+
RaiseError.pg_ctl_returns_an_unexpected_string(
|
|
205
|
+
out,
|
|
206
|
+
pg_ctl_params,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# TODO: Let's verify a length of pid string.
|
|
210
|
+
|
|
211
|
+
pid = int(out[start_pid_s:i])
|
|
212
|
+
|
|
213
|
+
if pid == 0:
|
|
214
|
+
RaiseError.pg_ctl_returns_a_zero_pid(
|
|
215
|
+
out,
|
|
216
|
+
pg_ctl_params,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
assert pid != 0
|
|
220
|
+
|
|
221
|
+
return pid
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
from testgres.operations.os_ops import OsOperations
|
|
2
|
+
from testgres.operations.os_ops import OsCommandResult
|
|
3
|
+
from testgres.operations.types import T_OS_CMD
|
|
4
|
+
from testgres.operations.types import T_OS_EXEC_ENV
|
|
5
|
+
from testgres.operations.helpers import Helpers as OsHelpers
|
|
6
|
+
|
|
7
|
+
from ..exceptions import ExecUtilException
|
|
2
8
|
|
|
3
9
|
import logging
|
|
4
10
|
import typing
|
|
@@ -90,3 +96,55 @@ def read_line_to_pos__bin(
|
|
|
90
96
|
assert type(result) is bytes
|
|
91
97
|
assert len(result) <= (position - read_position)
|
|
92
98
|
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def execute_utility3(
|
|
102
|
+
os_ops: OsOperations,
|
|
103
|
+
args: T_OS_CMD,
|
|
104
|
+
logfile: typing.Optional[str] = None,
|
|
105
|
+
check: bool = True,
|
|
106
|
+
exec_env: typing.Optional[T_OS_EXEC_ENV] = None,
|
|
107
|
+
) -> OsCommandResult:
|
|
108
|
+
assert os_ops is not None
|
|
109
|
+
assert isinstance(os_ops, OsOperations)
|
|
110
|
+
assert type(check) is bool
|
|
111
|
+
assert exec_env is None or type(exec_env) is dict
|
|
112
|
+
|
|
113
|
+
exec_r = os_ops.run(
|
|
114
|
+
args,
|
|
115
|
+
check=check,
|
|
116
|
+
encoding=OsHelpers.GetDefaultEncoding(),
|
|
117
|
+
exec_env=exec_env,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert type(exec_r) is OsCommandResult
|
|
121
|
+
|
|
122
|
+
# write new log entry if possible
|
|
123
|
+
if logfile:
|
|
124
|
+
try:
|
|
125
|
+
log_lines = [
|
|
126
|
+
os_ops.join_command_arguments(args),
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
if exec_r.stdout is None:
|
|
130
|
+
log_lines.append("# #NONE#")
|
|
131
|
+
else:
|
|
132
|
+
# comment-out lines
|
|
133
|
+
assert type(exec_r.stdout) is str
|
|
134
|
+
log_lines += ['# ' + line for line in exec_r.stdout.splitlines()]
|
|
135
|
+
|
|
136
|
+
log_lines.append("")
|
|
137
|
+
|
|
138
|
+
os_ops.write(
|
|
139
|
+
filename=logfile,
|
|
140
|
+
data="\n".join(log_lines),
|
|
141
|
+
truncate=False,
|
|
142
|
+
)
|
|
143
|
+
except IOError:
|
|
144
|
+
raise ExecUtilException(
|
|
145
|
+
"Problem with writing to logfile `{}` during run command `{}`".format(
|
|
146
|
+
logfile,
|
|
147
|
+
args,
|
|
148
|
+
))
|
|
149
|
+
|
|
150
|
+
return exec_r
|
|
@@ -248,7 +248,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
248
248
|
)
|
|
249
249
|
|
|
250
250
|
# --------------------------------------------------------------------
|
|
251
|
-
def
|
|
251
|
+
def ProcessIsZombie_soft_check(
|
|
252
252
|
self,
|
|
253
253
|
os_ops: OsOperations,
|
|
254
254
|
pid: int,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from ...raise_error import RaiseError
|
|
4
|
+
|
|
3
5
|
import enum
|
|
4
6
|
import typing
|
|
5
7
|
|
|
@@ -59,13 +61,13 @@ class InternalPlatformUtils:
|
|
|
59
61
|
assert isinstance(os_ops, OsOperations)
|
|
60
62
|
assert type(bin_dir) is str
|
|
61
63
|
assert type(data_dir) is str
|
|
62
|
-
|
|
64
|
+
RaiseError.method_is_not_implemented(__class__, "FindPostmaster")
|
|
63
65
|
|
|
64
|
-
def
|
|
66
|
+
def ProcessIsZombie_soft_check(
|
|
65
67
|
self,
|
|
66
68
|
os_ops: OsOperations,
|
|
67
69
|
pid: int,
|
|
68
70
|
) -> typing.Optional[bool]:
|
|
69
71
|
assert isinstance(os_ops, OsOperations)
|
|
70
72
|
assert type(pid) is int
|
|
71
|
-
|
|
73
|
+
RaiseError.method_is_not_implemented(__class__, "ProcessIsZombie_soft_check")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from .internal_platform_utils_factory import create_internal_platform_utils
|
|
2
|
+
from .internal_platform_utils_factory import InternalPlatformUtils
|
|
3
|
+
|
|
4
|
+
from testgres.operations.os_ops import OsOperations
|
|
5
|
+
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InternalPlaformUtilsProvider:
|
|
10
|
+
T_PLATFORM_UTILS = InternalPlatformUtils
|
|
11
|
+
|
|
12
|
+
_os_ops: OsOperations
|
|
13
|
+
_platform_utils: typing.Optional[T_PLATFORM_UTILS] = None
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
os_ops: OsOperations,
|
|
18
|
+
):
|
|
19
|
+
assert isinstance(os_ops, OsOperations)
|
|
20
|
+
self._os_ops = os_ops
|
|
21
|
+
self._platform_utils = None
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
def get(self) -> T_PLATFORM_UTILS:
|
|
25
|
+
if self._platform_utils is None:
|
|
26
|
+
self._platform_utils = create_internal_platform_utils(
|
|
27
|
+
self._os_ops,
|
|
28
|
+
)
|
|
29
|
+
assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS)
|
|
30
|
+
|
|
31
|
+
assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS)
|
|
32
|
+
return self._platform_utils
|
|
@@ -247,7 +247,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
247
247
|
)
|
|
248
248
|
|
|
249
249
|
# --------------------------------------------------------------------
|
|
250
|
-
def
|
|
250
|
+
def ProcessIsZombie_soft_check(
|
|
251
251
|
self,
|
|
252
252
|
os_ops: OsOperations,
|
|
253
253
|
pid: int,
|
|
@@ -264,7 +264,10 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
264
264
|
|
|
265
265
|
try:
|
|
266
266
|
# Read one line from /proc/PID/stat
|
|
267
|
-
|
|
267
|
+
stat_content_b = os_ops.read_binary(proc_stat_file, 0)
|
|
268
|
+
assert type(stat_content_b) is bytes
|
|
269
|
+
|
|
270
|
+
stat_content = stat_content_b.decode("utf-8", errors="ignore")
|
|
268
271
|
|
|
269
272
|
# We look for the closing parenthesis of the process name to ensure that
|
|
270
273
|
# we start from it and not depend on spaces inside the parentheses!
|
|
@@ -281,19 +284,23 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
281
284
|
result = proc_status == "Z"
|
|
282
285
|
except Exception as e:
|
|
283
286
|
# If the file disappeared right during reading, it means the process is completely erased
|
|
284
|
-
if __class__.
|
|
287
|
+
if __class__._is_zombie_file_exception(e):
|
|
285
288
|
result = False
|
|
286
289
|
|
|
287
290
|
return result
|
|
288
291
|
|
|
289
292
|
@staticmethod
|
|
290
|
-
def
|
|
293
|
+
def _is_zombie_file_exception(e: Exception) -> bool:
|
|
291
294
|
if isinstance(e, FileNotFoundError):
|
|
292
295
|
return True
|
|
293
296
|
|
|
297
|
+
if isinstance(e, ProcessLookupError):
|
|
298
|
+
return True
|
|
299
|
+
|
|
294
300
|
if isinstance(e, ExecUtilException):
|
|
295
|
-
if e.exit_code ==
|
|
301
|
+
if e.exit_code == 1:
|
|
296
302
|
return True
|
|
303
|
+
return False
|
|
297
304
|
|
|
298
305
|
return False
|
|
299
306
|
|
|
@@ -17,7 +17,7 @@ class InternalPlatformUtils(base.InternalPlatformUtils):
|
|
|
17
17
|
assert type(data_dir) is str
|
|
18
18
|
return __class__.FindPostmasterResult.create_not_implemented()
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def ProcessIsZombie_soft_check(
|
|
21
21
|
self,
|
|
22
22
|
os_ops: OsOperations,
|
|
23
23
|
pid: int,
|