xref: /unit/test/test_procman.py (revision 2765:5e31d3ff500a)
1import re
2import shutil
3import subprocess
4import time
5
6import pytest
7
8from unit.applications.lang.python import ApplicationPython
9from unit.option import option
10
11prerequisites = {'modules': {'python': 'any'}}
12
13client = ApplicationPython()
14
15
16@pytest.fixture(autouse=True)
17def setup_method_fixture(temp_dir):
18    client.app_name = f'app-{temp_dir.split("/")[-1]}'
19    client.app_proc = f'applications/{client.app_name}/processes'
20    client.load('empty', client.app_name)
21
22
23def pids_for_process():
24    time.sleep(0.2)
25
26    output = subprocess.check_output(['ps', 'ax'])
27
28    pids = set()
29    for m in re.findall(
30        fr'.*unit: "{client.app_name}" application', output.decode()
31    ):
32        pids.add(re.search(r'^\s*(\d+)', m).group(1))
33
34    return pids
35
36
37def conf_proc(conf, path=None):
38    if path is None:
39        path = client.app_proc
40
41    assert 'success' in client.conf(conf, path), 'configure processes'
42
43
44def stop_all():
45    assert 'success' in client.conf({"listeners": {}, "applications": {}})
46
47    assert len(pids_for_process()) == 0, 'stop all'
48
49
50@pytest.mark.skip('not yet')
51def test_python_processes_idle_timeout_zero():
52    conf_proc({"spare": 0, "max": 2, "idle_timeout": 0})
53
54    client.get()
55    assert len(pids_for_process()) == 0, 'idle timeout 0'
56
57
58def test_python_prefork():
59    conf_proc('2')
60
61    pids = pids_for_process()
62    assert len(pids) == 2, 'prefork 2'
63
64    client.get()
65    assert pids_for_process() == pids, 'prefork still 2'
66
67    conf_proc('4')
68
69    pids = pids_for_process()
70    assert len(pids) == 4, 'prefork 4'
71
72    client.get()
73    assert pids_for_process() == pids, 'prefork still 4'
74
75    stop_all()
76
77
78@pytest.mark.skip('not yet')
79def test_python_prefork_same_processes():
80    conf_proc('2')
81    pids = pids_for_process()
82
83    conf_proc('4')
84    pids_new = pids_for_process()
85
86    assert pids.issubset(pids_new), 'prefork same processes'
87
88
89def test_python_ondemand():
90    conf_proc({"spare": 0, "max": 8, "idle_timeout": 1})
91
92    assert len(pids_for_process()) == 0, 'on-demand 0'
93
94    client.get()
95    pids = pids_for_process()
96    assert len(pids) == 1, 'on-demand 1'
97
98    client.get()
99    assert pids_for_process() == pids, 'on-demand still 1'
100
101    time.sleep(1)
102
103    assert len(pids_for_process()) == 0, 'on-demand stop idle'
104
105    stop_all()
106
107
108def test_python_scale_updown():
109    conf_proc({"spare": 2, "max": 8, "idle_timeout": 1})
110
111    pids = pids_for_process()
112    assert len(pids) == 2, 'updown 2'
113
114    client.get()
115    pids_new = pids_for_process()
116    assert len(pids_new) == 3, 'updown 3'
117    assert pids.issubset(pids_new), 'updown 3 only 1 new'
118
119    client.get()
120    assert pids_for_process() == pids_new, 'updown still 3'
121
122    time.sleep(1)
123
124    pids = pids_for_process()
125    assert len(pids) == 2, 'updown stop idle'
126
127    client.get()
128    pids_new = pids_for_process()
129    assert len(pids_new) == 3, 'updown again 3'
130    assert pids.issubset(pids_new), 'updown again 3 only 1 new'
131
132    stop_all()
133
134
135def test_python_reconfigure():
136    conf_proc({"spare": 2, "max": 6, "idle_timeout": 1})
137
138    pids = pids_for_process()
139    assert len(pids) == 2, 'reconf 2'
140
141    client.get()
142    pids_new = pids_for_process()
143    assert len(pids_new) == 3, 'reconf 3'
144    assert pids.issubset(pids_new), 'reconf 3 only 1 new'
145
146    conf_proc('6', f'{client.app_proc}/spare')
147
148    pids = pids_for_process()
149    assert len(pids) == 6, 'reconf 6'
150
151    client.get()
152    assert pids_for_process() == pids, 'reconf still 6'
153
154    stop_all()
155
156
157def test_python_idle_timeout():
158    conf_proc({"spare": 0, "max": 6, "idle_timeout": 2})
159
160    client.get()
161    pids = pids_for_process()
162    assert len(pids) == 1, 'idle timeout 1'
163
164    time.sleep(1)
165
166    client.get()
167
168    time.sleep(1)
169
170    pids_new = pids_for_process()
171    assert len(pids_new) == 1, 'idle timeout still 1'
172    assert pids_for_process() == pids, 'idle timeout still 1 same pid'
173
174    time.sleep(1)
175
176    assert len(pids_for_process()) == 0, 'idle timed out'
177
178
179def test_python_processes_connection_keepalive():
180    conf_proc({"spare": 0, "max": 6, "idle_timeout": 2})
181
182    (_, sock) = client.get(
183        headers={'Host': 'localhost', 'Connection': 'keep-alive'},
184        start=True,
185        read_timeout=1,
186    )
187    assert len(pids_for_process()) == 1, 'keepalive connection 1'
188
189    time.sleep(2)
190
191    assert len(pids_for_process()) == 0, 'keepalive connection 0'
192
193    sock.close()
194
195
196def test_python_processes_access():
197    conf_proc('1')
198
199    path = f'/{client.app_proc}'
200    assert 'error' in client.conf_get(f'{path}/max')
201    assert 'error' in client.conf_get(f'{path}/spare')
202    assert 'error' in client.conf_get(f'{path}/idle_timeout')
203
204
205def test_python_processes_invalid():
206    assert 'error' in client.conf(
207        {"spare": -1}, client.app_proc
208    ), 'negative spare'
209    assert 'error' in client.conf({"max": -1}, client.app_proc), 'negative max'
210    assert 'error' in client.conf(
211        {"idle_timeout": -1}, client.app_proc
212    ), 'negative idle_timeout'
213    assert 'error' in client.conf(
214        {"spare": 2}, client.app_proc
215    ), 'spare gt max default'
216    assert 'error' in client.conf(
217        {"spare": 2, "max": 1}, client.app_proc
218    ), 'spare gt max'
219    assert 'error' in client.conf(
220        {"spare": 0, "max": 0}, client.app_proc
221    ), 'max zero'
222
223
224def test_python_restart(temp_dir):
225    shutil.copyfile(
226        f'{option.test_dir}/python/restart/v1.py', f'{temp_dir}/wsgi.py'
227    )
228
229    client.load(
230        temp_dir,
231        name=client.app_name,
232        processes=1,
233        environment={'PYTHONDONTWRITEBYTECODE': '1'},
234    )
235
236    b = client.get()['body']
237    assert b == "v1", 'process started'
238
239    shutil.copyfile(
240        f'{option.test_dir}/python/restart/v2.py', f'{temp_dir}/wsgi.py'
241    )
242
243    b = client.get()['body']
244    assert b == "v1", 'still old process'
245
246    assert 'success' in client.conf_get(
247        f'/control/applications/{client.app_name}/restart'
248    ), 'restart processes'
249
250    b = client.get()['body']
251    assert b == "v2", 'new process started'
252
253    assert 'error' in client.conf_get(
254        '/control/applications/blah/restart'
255    ), 'application incorrect'
256
257    assert 'error' in client.conf_delete(
258        f'/control/applications/{client.app_name}/restart'
259    ), 'method incorrect'
260
261
262def test_python_restart_multi():
263    conf_proc('2')
264
265    pids = pids_for_process()
266    assert len(pids) == 2, 'restart 2 started'
267
268    assert 'success' in client.conf_get(
269        f'/control/applications/{client.app_name}/restart'
270    ), 'restart processes'
271
272    new_pids = pids_for_process()
273    assert len(new_pids) == 2, 'restart still 2'
274
275    assert len(new_pids.intersection(pids)) == 0, 'restart all new'
276
277
278def test_python_restart_longstart():
279    client.load(
280        'restart',
281        name=client.app_name,
282        module="longstart",
283        processes={"spare": 1, "max": 2, "idle_timeout": 5},
284    )
285
286    assert len(pids_for_process()) == 1, 'longstarts == 1'
287
288    client.get()
289
290    pids = pids_for_process()
291    assert len(pids) == 2, 'longstarts == 2'
292
293    assert 'success' in client.conf_get(
294        f'/control/applications/{client.app_name}/restart'
295    ), 'restart processes'
296
297    # wait for longstarted app
298    time.sleep(2)
299
300    new_pids = pids_for_process()
301    assert len(new_pids) == 1, 'restart 1'
302
303    assert len(new_pids.intersection(pids)) == 0, 'restart all new'
304