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