xref: /unit/test/test_python_procman.py (revision 2478:c08289a08a73)
1516Szelenkov@nginx.comimport re
21926Smax.romanov@nginx.comimport shutil
31477Szelenkov@nginx.comimport subprocess
4507Smax.romanov@nginx.comimport time
51477Szelenkov@nginx.com
61635Szelenkov@nginx.comimport pytest
71019Szelenkov@nginx.comfrom unit.applications.lang.python import TestApplicationPython
81730Szelenkov@nginx.comfrom unit.option import option
9507Smax.romanov@nginx.com
101017Szelenkov@nginx.com
111019Szelenkov@nginx.comclass TestPythonProcman(TestApplicationPython):
121467Szelenkov@nginx.com    prerequisites = {'modules': {'python': 'any'}}
13507Smax.romanov@nginx.com
14*2478Szelenkov@nginx.com    @pytest.fixture(autouse=True)
15*2478Szelenkov@nginx.com    def setup_method_fixture(self, temp_dir):
16*2478Szelenkov@nginx.com        self.app_name = f'app-{temp_dir.split("/")[-1]}'
172330Szelenkov@nginx.com        self.app_proc = f'applications/{self.app_name}/processes'
181416Szelenkov@nginx.com        self.load('empty', self.app_name)
191416Szelenkov@nginx.com
20552Szelenkov@nginx.com    def pids_for_process(self):
21516Szelenkov@nginx.com        time.sleep(0.2)
22516Szelenkov@nginx.com
23516Szelenkov@nginx.com        output = subprocess.check_output(['ps', 'ax'])
24507Smax.romanov@nginx.com
25516Szelenkov@nginx.com        pids = set()
261999Smax.romanov@nginx.com        for m in re.findall(
272330Szelenkov@nginx.com            fr'.*unit: "{self.app_name}" application', output.decode()
281999Smax.romanov@nginx.com        ):
291596Szelenkov@nginx.com            pids.add(re.search(r'^\s*(\d+)', m).group(1))
30507Smax.romanov@nginx.com
31516Szelenkov@nginx.com        return pids
32507Smax.romanov@nginx.com
331416Szelenkov@nginx.com    def conf_proc(self, conf, path=None):
341416Szelenkov@nginx.com        if path is None:
351416Szelenkov@nginx.com            path = self.app_proc
36555Szelenkov@nginx.com
371596Szelenkov@nginx.com        assert 'success' in self.conf(conf, path), 'configure processes'
38517Szelenkov@nginx.com
391596Szelenkov@nginx.com    @pytest.mark.skip('not yet')
40517Szelenkov@nginx.com    def test_python_processes_idle_timeout_zero(self):
411416Szelenkov@nginx.com        self.conf_proc({"spare": 0, "max": 2, "idle_timeout": 0})
42517Szelenkov@nginx.com
43517Szelenkov@nginx.com        self.get()
441596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'idle timeout 0'
45517Szelenkov@nginx.com
46516Szelenkov@nginx.com    def test_python_prefork(self):
471416Szelenkov@nginx.com        self.conf_proc('2')
48516Szelenkov@nginx.com
49516Szelenkov@nginx.com        pids = self.pids_for_process()
501596Szelenkov@nginx.com        assert len(pids) == 2, 'prefork 2'
51516Szelenkov@nginx.com
52516Szelenkov@nginx.com        self.get()
531596Szelenkov@nginx.com        assert self.pids_for_process() == pids, 'prefork still 2'
54516Szelenkov@nginx.com
551416Szelenkov@nginx.com        self.conf_proc('4')
56516Szelenkov@nginx.com
57516Szelenkov@nginx.com        pids = self.pids_for_process()
581596Szelenkov@nginx.com        assert len(pids) == 4, 'prefork 4'
59507Smax.romanov@nginx.com
60507Smax.romanov@nginx.com        self.get()
611596Szelenkov@nginx.com        assert self.pids_for_process() == pids, 'prefork still 4'
62516Szelenkov@nginx.com
63516Szelenkov@nginx.com        self.stop_all()
64516Szelenkov@nginx.com
651596Szelenkov@nginx.com    @pytest.mark.skip('not yet')
66517Szelenkov@nginx.com    def test_python_prefork_same_processes(self):
671416Szelenkov@nginx.com        self.conf_proc('2')
68517Szelenkov@nginx.com        pids = self.pids_for_process()
69517Szelenkov@nginx.com
701416Szelenkov@nginx.com        self.conf_proc('4')
71517Szelenkov@nginx.com        pids_new = self.pids_for_process()
72517Szelenkov@nginx.com
731596Szelenkov@nginx.com        assert pids.issubset(pids_new), 'prefork same processes'
74517Szelenkov@nginx.com
75516Szelenkov@nginx.com    def test_python_ondemand(self):
761416Szelenkov@nginx.com        self.conf_proc({"spare": 0, "max": 8, "idle_timeout": 1})
77516Szelenkov@nginx.com
781596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'on-demand 0'
79507Smax.romanov@nginx.com
80507Smax.romanov@nginx.com        self.get()
81516Szelenkov@nginx.com        pids = self.pids_for_process()
821596Szelenkov@nginx.com        assert len(pids) == 1, 'on-demand 1'
83507Smax.romanov@nginx.com
84516Szelenkov@nginx.com        self.get()
851596Szelenkov@nginx.com        assert self.pids_for_process() == pids, 'on-demand still 1'
86516Szelenkov@nginx.com
87516Szelenkov@nginx.com        time.sleep(1)
88516Szelenkov@nginx.com
891596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'on-demand stop idle'
90507Smax.romanov@nginx.com
91515Szelenkov@nginx.com        self.stop_all()
92507Smax.romanov@nginx.com
93507Smax.romanov@nginx.com    def test_python_scale_updown(self):
941416Szelenkov@nginx.com        self.conf_proc({"spare": 2, "max": 8, "idle_timeout": 1})
95507Smax.romanov@nginx.com
96516Szelenkov@nginx.com        pids = self.pids_for_process()
971596Szelenkov@nginx.com        assert len(pids) == 2, 'updown 2'
98507Smax.romanov@nginx.com
99507Smax.romanov@nginx.com        self.get()
100516Szelenkov@nginx.com        pids_new = self.pids_for_process()
1011596Szelenkov@nginx.com        assert len(pids_new) == 3, 'updown 3'
1021596Szelenkov@nginx.com        assert pids.issubset(pids_new), 'updown 3 only 1 new'
103507Smax.romanov@nginx.com
104507Smax.romanov@nginx.com        self.get()
1051596Szelenkov@nginx.com        assert self.pids_for_process() == pids_new, 'updown still 3'
106507Smax.romanov@nginx.com
107516Szelenkov@nginx.com        time.sleep(1)
108516Szelenkov@nginx.com
109516Szelenkov@nginx.com        pids = self.pids_for_process()
1101596Szelenkov@nginx.com        assert len(pids) == 2, 'updown stop idle'
111507Smax.romanov@nginx.com
112507Smax.romanov@nginx.com        self.get()
113516Szelenkov@nginx.com        pids_new = self.pids_for_process()
1141596Szelenkov@nginx.com        assert len(pids_new) == 3, 'updown again 3'
1151596Szelenkov@nginx.com        assert pids.issubset(pids_new), 'updown again 3 only 1 new'
116507Smax.romanov@nginx.com
117515Szelenkov@nginx.com        self.stop_all()
118507Smax.romanov@nginx.com
119507Smax.romanov@nginx.com    def test_python_reconfigure(self):
1201416Szelenkov@nginx.com        self.conf_proc({"spare": 2, "max": 6, "idle_timeout": 1})
121507Smax.romanov@nginx.com
122516Szelenkov@nginx.com        pids = self.pids_for_process()
1231596Szelenkov@nginx.com        assert len(pids) == 2, 'reconf 2'
124507Smax.romanov@nginx.com
125507Smax.romanov@nginx.com        self.get()
126516Szelenkov@nginx.com        pids_new = self.pids_for_process()
1271596Szelenkov@nginx.com        assert len(pids_new) == 3, 'reconf 3'
1281596Szelenkov@nginx.com        assert pids.issubset(pids_new), 'reconf 3 only 1 new'
129507Smax.romanov@nginx.com
1302330Szelenkov@nginx.com        self.conf_proc('6', f'{self.app_proc}/spare')
131515Szelenkov@nginx.com
132516Szelenkov@nginx.com        pids = self.pids_for_process()
1331596Szelenkov@nginx.com        assert len(pids) == 6, 'reconf 6'
134507Smax.romanov@nginx.com
135507Smax.romanov@nginx.com        self.get()
1361596Szelenkov@nginx.com        assert self.pids_for_process() == pids, 'reconf still 6'
137507Smax.romanov@nginx.com
138515Szelenkov@nginx.com        self.stop_all()
139515Szelenkov@nginx.com
140517Szelenkov@nginx.com    def test_python_idle_timeout(self):
1411416Szelenkov@nginx.com        self.conf_proc({"spare": 0, "max": 6, "idle_timeout": 2})
142517Szelenkov@nginx.com
143517Szelenkov@nginx.com        self.get()
144517Szelenkov@nginx.com        pids = self.pids_for_process()
1451596Szelenkov@nginx.com        assert len(pids) == 1, 'idle timeout 1'
146517Szelenkov@nginx.com
147517Szelenkov@nginx.com        time.sleep(1)
148517Szelenkov@nginx.com
149517Szelenkov@nginx.com        self.get()
150517Szelenkov@nginx.com
151517Szelenkov@nginx.com        time.sleep(1)
152517Szelenkov@nginx.com
153517Szelenkov@nginx.com        pids_new = self.pids_for_process()
1541596Szelenkov@nginx.com        assert len(pids_new) == 1, 'idle timeout still 1'
1551596Szelenkov@nginx.com        assert self.pids_for_process() == pids, 'idle timeout still 1 same pid'
156517Szelenkov@nginx.com
157517Szelenkov@nginx.com        time.sleep(1)
158517Szelenkov@nginx.com
1591596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'idle timed out'
160517Szelenkov@nginx.com
161517Szelenkov@nginx.com    def test_python_processes_connection_keepalive(self):
1621416Szelenkov@nginx.com        self.conf_proc({"spare": 0, "max": 6, "idle_timeout": 2})
163517Szelenkov@nginx.com
1642477Szelenkov@nginx.com        (_, sock) = self.get(
1651017Szelenkov@nginx.com            headers={'Host': 'localhost', 'Connection': 'keep-alive'},
1661017Szelenkov@nginx.com            start=True,
1671017Szelenkov@nginx.com            read_timeout=1,
1681017Szelenkov@nginx.com        )
1691596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 1, 'keepalive connection 1'
170517Szelenkov@nginx.com
171517Szelenkov@nginx.com        time.sleep(2)
172517Szelenkov@nginx.com
1731596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'keepalive connection 0'
174517Szelenkov@nginx.com
175517Szelenkov@nginx.com        sock.close()
176517Szelenkov@nginx.com
1771416Szelenkov@nginx.com    def test_python_processes_access(self):
1781416Szelenkov@nginx.com        self.conf_proc('1')
1791416Szelenkov@nginx.com
1802330Szelenkov@nginx.com        path = f'/{self.app_proc}'
1812330Szelenkov@nginx.com        assert 'error' in self.conf_get(f'{path}/max')
1822330Szelenkov@nginx.com        assert 'error' in self.conf_get(f'{path}/spare')
1832330Szelenkov@nginx.com        assert 'error' in self.conf_get(f'{path}/idle_timeout')
1841416Szelenkov@nginx.com
1851416Szelenkov@nginx.com    def test_python_processes_invalid(self):
1861596Szelenkov@nginx.com        assert 'error' in self.conf(
1871596Szelenkov@nginx.com            {"spare": -1}, self.app_proc
1881596Szelenkov@nginx.com        ), 'negative spare'
1891596Szelenkov@nginx.com        assert 'error' in self.conf({"max": -1}, self.app_proc), 'negative max'
1901596Szelenkov@nginx.com        assert 'error' in self.conf(
1911596Szelenkov@nginx.com            {"idle_timeout": -1}, self.app_proc
1921596Szelenkov@nginx.com        ), 'negative idle_timeout'
1931596Szelenkov@nginx.com        assert 'error' in self.conf(
1941596Szelenkov@nginx.com            {"spare": 2}, self.app_proc
1951596Szelenkov@nginx.com        ), 'spare gt max default'
1961596Szelenkov@nginx.com        assert 'error' in self.conf(
1971596Szelenkov@nginx.com            {"spare": 2, "max": 1}, self.app_proc
1981596Szelenkov@nginx.com        ), 'spare gt max'
1991596Szelenkov@nginx.com        assert 'error' in self.conf(
2001596Szelenkov@nginx.com            {"spare": 0, "max": 0}, self.app_proc
2011596Szelenkov@nginx.com        ), 'max zero'
2021416Szelenkov@nginx.com
203515Szelenkov@nginx.com    def stop_all(self):
2041775Szelenkov@nginx.com        assert 'success' in self.conf({"listeners": {}, "applications": {}})
205507Smax.romanov@nginx.com
2061596Szelenkov@nginx.com        assert len(self.pids_for_process()) == 0, 'stop all'
2071926Smax.romanov@nginx.com
2081926Smax.romanov@nginx.com    def test_python_restart(self, temp_dir):
2091926Smax.romanov@nginx.com        shutil.copyfile(
2102330Szelenkov@nginx.com            f'{option.test_dir}/python/restart/v1.py', f'{temp_dir}/wsgi.py'
2111926Smax.romanov@nginx.com        )
2121926Smax.romanov@nginx.com
2131926Smax.romanov@nginx.com        self.load(
2141926Smax.romanov@nginx.com            temp_dir,
2151926Smax.romanov@nginx.com            name=self.app_name,
2161926Smax.romanov@nginx.com            processes=1,
2171926Smax.romanov@nginx.com            environment={'PYTHONDONTWRITEBYTECODE': '1'},
2181926Smax.romanov@nginx.com        )
2191926Smax.romanov@nginx.com
2201926Smax.romanov@nginx.com        b = self.get()['body']
2211926Smax.romanov@nginx.com        assert b == "v1", 'process started'
2221926Smax.romanov@nginx.com
2231926Smax.romanov@nginx.com        shutil.copyfile(
2242330Szelenkov@nginx.com            f'{option.test_dir}/python/restart/v2.py', f'{temp_dir}/wsgi.py'
2251926Smax.romanov@nginx.com        )
2261926Smax.romanov@nginx.com
2271926Smax.romanov@nginx.com        b = self.get()['body']
2281926Smax.romanov@nginx.com        assert b == "v1", 'still old process'
2291926Smax.romanov@nginx.com
2301926Smax.romanov@nginx.com        assert 'success' in self.conf_get(
2312330Szelenkov@nginx.com            f'/control/applications/{self.app_name}/restart'
2321926Smax.romanov@nginx.com        ), 'restart processes'
2331926Smax.romanov@nginx.com
2341926Smax.romanov@nginx.com        b = self.get()['body']
2351926Smax.romanov@nginx.com        assert b == "v2", 'new process started'
2361926Smax.romanov@nginx.com
2371926Smax.romanov@nginx.com        assert 'error' in self.conf_get(
2381926Smax.romanov@nginx.com            '/control/applications/blah/restart'
2391926Smax.romanov@nginx.com        ), 'application incorrect'
2401926Smax.romanov@nginx.com
2411926Smax.romanov@nginx.com        assert 'error' in self.conf_delete(
2422330Szelenkov@nginx.com            f'/control/applications/{self.app_name}/restart'
2431926Smax.romanov@nginx.com        ), 'method incorrect'
2441926Smax.romanov@nginx.com
2451926Smax.romanov@nginx.com    def test_python_restart_multi(self):
2461926Smax.romanov@nginx.com        self.conf_proc('2')
2471926Smax.romanov@nginx.com
2481926Smax.romanov@nginx.com        pids = self.pids_for_process()
2491926Smax.romanov@nginx.com        assert len(pids) == 2, 'restart 2 started'
2501926Smax.romanov@nginx.com
2511926Smax.romanov@nginx.com        assert 'success' in self.conf_get(
2522330Szelenkov@nginx.com            f'/control/applications/{self.app_name}/restart'
2531926Smax.romanov@nginx.com        ), 'restart processes'
2541926Smax.romanov@nginx.com
2551926Smax.romanov@nginx.com        new_pids = self.pids_for_process()
2561926Smax.romanov@nginx.com        assert len(new_pids) == 2, 'restart still 2'
2571926Smax.romanov@nginx.com
2581926Smax.romanov@nginx.com        assert len(new_pids.intersection(pids)) == 0, 'restart all new'
2591926Smax.romanov@nginx.com
2601926Smax.romanov@nginx.com    def test_python_restart_longstart(self):
2611926Smax.romanov@nginx.com        self.load(
2621926Smax.romanov@nginx.com            'restart',
2631926Smax.romanov@nginx.com            name=self.app_name,
2641926Smax.romanov@nginx.com            module="longstart",
2651926Smax.romanov@nginx.com            processes={"spare": 1, "max": 2, "idle_timeout": 5},
2661926Smax.romanov@nginx.com        )
2671926Smax.romanov@nginx.com
2681926Smax.romanov@nginx.com        assert len(self.pids_for_process()) == 1, 'longstarts == 1'
2691926Smax.romanov@nginx.com
2701971Szelenkov@nginx.com        self.get()
2711971Szelenkov@nginx.com
2721926Smax.romanov@nginx.com        pids = self.pids_for_process()
2731926Smax.romanov@nginx.com        assert len(pids) == 2, 'longstarts == 2'
2741926Smax.romanov@nginx.com
2751926Smax.romanov@nginx.com        assert 'success' in self.conf_get(
2762330Szelenkov@nginx.com            f'/control/applications/{self.app_name}/restart'
2771926Smax.romanov@nginx.com        ), 'restart processes'
2781926Smax.romanov@nginx.com
2791926Smax.romanov@nginx.com        # wait for longstarted app
2801926Smax.romanov@nginx.com        time.sleep(2)
2811926Smax.romanov@nginx.com
2821926Smax.romanov@nginx.com        new_pids = self.pids_for_process()
2831926Smax.romanov@nginx.com        assert len(new_pids) == 1, 'restart 1'
2841926Smax.romanov@nginx.com
2851926Smax.romanov@nginx.com        assert len(new_pids.intersection(pids)) == 0, 'restart all new'
286