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