userver: en/testsuite/environment/service.py Source File
Loading...
Searching...
No Matches
service.py
1import logging
2import os
3import pathlib
4import time
5import typing
6
7from testsuite import types
8from testsuite.utils import traceback
9
10from . import shell, utils
11
12logger = logging.getLogger(__name__)
13
14_TESTSUITE_LIB_UTILS = pathlib.Path(__file__).parent.joinpath(
15 'scripts/utils.sh',
16)
17COMMAND_START = 'start'
18COMMAND_STOP = 'stop'
19
20
21class BaseError(Exception):
22 pass
23
24
29__tracebackhide__ = traceback.hide(BaseError)
30
31
33 def __init__(
34 self,
35 *,
36 service_name: str,
37 script_path: str,
38 working_dir: str,
39 check_host: str = 'localhost',
40 check_ports: list[int],
41 environment: dict[str, str] | None = None,
42 prestart_hook: typing.Callable | None = None,
43 start_timeout: float = 2.0,
44 ) -> None:
45 self._service_name = service_name
46 self._script_path = script_path
47 self._environment = environment
48 self._check_host = check_host
49 self._check_ports = check_ports
50 self._prestart_hook = prestart_hook
51 self._start_timeout = start_timeout
52 self._started_mark = StartedMark(working_dir)
53
54 def ensure_started(self, *, verbose: int) -> None:
55 self._started_mark.delete()
56 self.stop(verbose=0)
57 if self._prestart_hook:
58 self._prestart_hook()
59 if verbose:
60 logger.info('Starting %s service...', self._service_name)
61 self._command(COMMAND_START, verbose)
62 if not self._wait_for_ports():
64 f'Service {self._service_name} failed to start within {self._start_timeout} seconds.'
65 )
66 if verbose:
67 logger.info('Service %s started.', self._service_name)
68 self._started_mark.create()
69
70 def stop(self, *, verbose: int) -> None:
71 self._started_mark.delete()
72 if verbose:
73 logger.info('Stopping %s services...', self._service_name)
74 self._command(COMMAND_STOP, verbose)
75 if verbose:
76 logger.info('Service %s stopped.', self._service_name)
77
78 def is_running(self) -> bool:
79 if not self._started_mark.exists():
80 return False
81 return all(
82 utils.test_tcp_connection(self._check_host, port)
83 for port in self._check_ports
84 )
85
86 def _command(self, command: str, verbose: int) -> None:
87 env = os.environ.copy()
88 env['TESTSUITE_LIB_UTILS'] = str(_TESTSUITE_LIB_UTILS)
89 if self._environment:
90 env.update(self._environment)
91 args = [self._script_path, command]
92 shell.execute(
93 args,
94 env=env,
95 verbose=verbose,
96 command_alias=f'env/{self._service_name}/{command}',
97 )
98
99 def _wait_for_ports(self) -> bool:
100 start_time = time.perf_counter()
101 for port in self._check_ports:
102 time_passed = time.perf_counter() - start_time
103 if time_passed >= self._start_timeout:
104 return False
105
106 if not utils.wait_tcp_connection(
107 host=self._check_host,
108 port=port,
109 timeout=self._start_timeout - time_passed,
110 ):
111 return False
112
113 return True
114
115
117 def __init__(self, working_dir: types.PathOrStr) -> None:
118 self._path = pathlib.Path(working_dir) / '.started'
119
120 def create(self) -> None:
121 self._path.parent.mkdir(exist_ok=True, parents=True)
122 self._path.write_text('')
123
124 def delete(self) -> None:
125 try:
126 self._path.unlink()
127 except FileNotFoundError:
128 pass
129
130 def exists(self) -> bool:
131 return self._path.exists()