Skip to content

Commit 9515b02

Browse files
PostgresNode::child_processes works on a running node only (#303)
* PostgresNode::child_processes works on a running node only We will raise an exception InvalidOperationException if PostgresNode::child_processes is called on a stopped or uninitialized node.
1 parent 8fb947e commit 9515b02

4 files changed

Lines changed: 249 additions & 2 deletions

File tree

src/node.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
options_string, \
100100
clean_on_error
101101

102+
from .raise_error import RaiseError
103+
102104
from .backup import NodeBackup
103105

104106
from testgres.operations.os_ops import ConnectionParams
@@ -406,14 +408,32 @@ def is_aux(process):
406408
return list(filter(is_aux, self.child_processes))
407409

408410
@property
409-
def child_processes(self):
411+
def child_processes(self) -> typing.List[ProcessProxy]:
410412
"""
411413
Returns a list of all child processes.
412414
Each process is represented by :class:`.ProcessProxy` object.
413415
"""
414416

415417
# get a list of postmaster's children
416-
children = self.os_ops.get_process_children(self.pid)
418+
x = self._get_node_state()
419+
assert type(x) == utils.PostgresNodeState # noqa: E721
420+
421+
if x.pid is None:
422+
assert x.node_status != NodeStatus.Running
423+
RaiseError.node_err__cant_enumerate_child_processes(
424+
x.node_status
425+
)
426+
427+
assert x.node_status == NodeStatus.Running
428+
assert type(x.pid) == int # noqa: E721
429+
return self._get_child_processes(x.pid)
430+
431+
def _get_child_processes(self, pid: int) -> typing.List[ProcessProxy]:
432+
assert type(pid) == int # noqa: E721
433+
assert isinstance(self._os_ops, OsOperations)
434+
435+
# get a list of postmaster's children
436+
children = self._os_ops.get_process_children(pid)
417437

418438
return [ProcessProxy(p) for p in children]
419439

src/raise_error.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from .exceptions import InvalidOperationException
2+
from .enums import NodeStatus
3+
4+
import typing
15

26

37
class RaiseError:
@@ -25,3 +29,42 @@ def pg_ctl_returns_a_zero_pid(out, _params):
2529
errLines.append("------------")
2630
errLines.append("Command line is {0}".format(_params))
2731
raise RuntimeError("\n".join(errLines))
32+
33+
@staticmethod
34+
def node_err__cant_enumerate_child_processes(
35+
node_status: NodeStatus
36+
):
37+
assert type(node_status) == NodeStatus # noqa: E721
38+
39+
msg = "Can't enumerate node child processes. {}.".format(
40+
__class__._map_node_status_to_reason(
41+
node_status,
42+
None,
43+
)
44+
)
45+
46+
raise InvalidOperationException(msg)
47+
48+
@staticmethod
49+
def _map_node_status_to_reason(
50+
node_status: NodeStatus,
51+
node_pid: typing.Optional[int],
52+
) -> str:
53+
assert type(node_status) == NodeStatus # noqa: E721
54+
assert node_pid is None or type(node_pid) == int # noqa: E721
55+
56+
if node_status == NodeStatus.Uninitialized:
57+
return "Node is not initialized"
58+
59+
if node_status == NodeStatus.Stopped:
60+
return "Node is not running"
61+
62+
if node_status == NodeStatus.Running:
63+
return "Node is running (pid: {})".format(
64+
node_pid
65+
)
66+
67+
# assert False
68+
return "Node has unknown status {}".format(
69+
node_status
70+
)

tests/test_raise_error.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from src import InvalidOperationException
2+
from src import NodeStatus
3+
from src.raise_error import RaiseError
4+
5+
import pytest
6+
import typing
7+
8+
9+
class TestRaiseError:
10+
class tagData001__NodeErr_CantEnumerateChildProcesses:
11+
node_status: NodeStatus
12+
expected_msg: str
13+
14+
def __init__(
15+
self,
16+
node_status: NodeStatus,
17+
expected_msg: str,
18+
):
19+
assert type(node_status) == NodeStatus # noqa: E721
20+
assert type(expected_msg) == str # noqa: E721
21+
self.node_status = node_status
22+
self.expected_msg = expected_msg
23+
return
24+
25+
@property
26+
def sign(self) -> str:
27+
assert type(self.node_status) == NodeStatus # noqa: E721
28+
29+
msg = "status: {}".format(self.node_status)
30+
return msg
31+
32+
sm_Data001: typing.List[tagData001__NodeErr_CantEnumerateChildProcesses] = [
33+
tagData001__NodeErr_CantEnumerateChildProcesses(
34+
NodeStatus.Uninitialized,
35+
"Can't enumerate node child processes. Node is not initialized.",
36+
),
37+
tagData001__NodeErr_CantEnumerateChildProcesses(
38+
NodeStatus.Stopped,
39+
"Can't enumerate node child processes. Node is not running.",
40+
),
41+
]
42+
43+
@pytest.fixture(
44+
params=sm_Data001,
45+
ids=[x.sign for x in sm_Data001],
46+
)
47+
def data001(self, request: pytest.FixtureRequest) -> tagData001__NodeErr_CantEnumerateChildProcesses:
48+
assert isinstance(request, pytest.FixtureRequest)
49+
assert type(request.param).__name__ == "tagData001__NodeErr_CantEnumerateChildProcesses"
50+
return request.param
51+
52+
def test_001__node_err__cant_enumerate_child_processes(
53+
self,
54+
data001: tagData001__NodeErr_CantEnumerateChildProcesses,
55+
):
56+
assert type(data001) == __class__.tagData001__NodeErr_CantEnumerateChildProcesses # noqa: E721
57+
58+
with pytest.raises(expected_exception=InvalidOperationException) as x:
59+
RaiseError.node_err__cant_enumerate_child_processes(
60+
data001.node_status
61+
)
62+
63+
assert x is not None
64+
assert str(x.value) == data001.expected_msg
65+
return

tests/test_testgres_common.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from src.node import PostgresNode
1010
from src.node import PostgresNodeLogReader
1111
from src.node import PostgresNodeUtils
12+
from src.node import ProcessProxy
1213
from src.utils import get_pg_version2
1314
from src.utils import file_tail
1415
from src.utils import get_bin_path2
@@ -44,6 +45,7 @@
4445
import re
4546
import subprocess
4647
import typing
48+
import types
4749

4850

4951
@contextmanager
@@ -293,6 +295,123 @@ def test_status(self, node_svc: PostgresNodeService):
293295
assert (node.pid == 0)
294296
assert (node.status() == NodeStatus.Uninitialized)
295297

298+
def test_child_processes__is_not_initialized(
299+
self,
300+
node_svc: PostgresNodeService
301+
):
302+
assert isinstance(node_svc, PostgresNodeService)
303+
304+
with __class__.helper__get_node(node_svc) as node:
305+
assert isinstance(node, PostgresNode)
306+
assert (node.pid == 0)
307+
assert (node.status() == NodeStatus.Uninitialized)
308+
309+
with pytest.raises(expected_exception=InvalidOperationException) as x:
310+
node.child_processes
311+
312+
assert x is not None
313+
assert str(x.value) == "Can't enumerate node child processes. Node is not initialized."
314+
return
315+
316+
def test_child_processes__is_not_running(
317+
self,
318+
node_svc: PostgresNodeService
319+
):
320+
assert isinstance(node_svc, PostgresNodeService)
321+
322+
with __class__.helper__get_node(node_svc) as node:
323+
assert isinstance(node, PostgresNode)
324+
assert (node.pid == 0)
325+
assert (node.status() == NodeStatus.Uninitialized)
326+
327+
node.init()
328+
329+
try:
330+
with pytest.raises(expected_exception=InvalidOperationException) as x:
331+
node.child_processes
332+
333+
assert x is not None
334+
assert str(x.value) == "Can't enumerate node child processes. Node is not running."
335+
finally:
336+
try:
337+
node.cleanup(release_resources=True)
338+
except Exception as e:
339+
logging.error("Exception ({}): {}".format(
340+
type(e).__name__,
341+
e,
342+
))
343+
return
344+
345+
def test_child_processes__ok(
346+
self,
347+
node_svc: PostgresNodeService
348+
):
349+
assert isinstance(node_svc, PostgresNodeService)
350+
351+
with __class__.helper__get_node(node_svc) as node:
352+
assert isinstance(node, PostgresNode)
353+
assert (node.pid == 0)
354+
assert (node.status() == NodeStatus.Uninitialized)
355+
356+
node.init()
357+
358+
try:
359+
node.slow_start()
360+
361+
children = node.child_processes
362+
assert children is not None
363+
assert type(children) == list # noqa: E721
364+
365+
logging.info("Children count is {}".format(len(children)))
366+
logging.info("")
367+
368+
def LOCAL__safe_call_cmdline(p: ProcessProxy) -> str:
369+
assert type(p) == ProcessProxy # noqa: E721
370+
try:
371+
return p.cmdline()
372+
except Exception as e:
373+
return "Exception ({}): {}".format(
374+
type(e).__name__,
375+
e,
376+
)
377+
378+
for i in range(len(children)):
379+
logging.info("------ check child [{}]".format(i))
380+
child = children[i]
381+
382+
try:
383+
assert child is not None
384+
assert type(child) == ProcessProxy # noqa: E721
385+
assert hasattr(child, "process")
386+
assert hasattr(child, "ptype")
387+
assert hasattr(child, "pid")
388+
assert hasattr(child, "cmdline")
389+
assert child.process is not None
390+
assert child.ptype is not None
391+
assert child.pid is not None
392+
assert type(child.ptype) == ProcessType # noqa: E721
393+
assert type(child.pid) == int # noqa: E721
394+
assert type(child.cmdline) == types.MethodType # noqa: E721
395+
396+
logging.info("ptype is {}".format(child.ptype))
397+
logging.info("pid is {}".format(child.pid))
398+
logging.info("cmdline is [{}]".format(LOCAL__safe_call_cmdline(child)))
399+
except Exception as e:
400+
logging.error("Exception ({}): {}".format(
401+
type(e).__name__,
402+
e,
403+
))
404+
continue
405+
finally:
406+
try:
407+
node.cleanup(release_resources=True)
408+
except Exception as e:
409+
logging.error("Exception ({}): {}".format(
410+
type(e).__name__,
411+
e,
412+
))
413+
return
414+
296415
def test_child_pids(self, node_svc: PostgresNodeService):
297416
assert isinstance(node_svc, PostgresNodeService)
298417

0 commit comments

Comments
 (0)