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