1 # Tests for process restarting in the pre-fork process model
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """Tests process restarting in the pre-fork process model.
19 NOTE: As this test kills samba processes it won't play nicely with other
20 tests, so needs to be run in it's own environment.
29 from samba
.tests
import TestCase
, delete_force
30 from samba
.dcerpc
import echo
, netlogon
31 from samba
.messaging
import Messaging
32 from samba
.samdb
import SamDB
33 from samba
.credentials
import Credentials
, DONT_USE_KERBEROS
34 from samba
.common
import get_string
35 from samba
.dsdb
import (
36 UF_WORKSTATION_TRUST_ACCOUNT
,
38 from samba
.dcerpc
.misc
import SEC_CHAN_WKSTA
39 from samba
.auth
import system_session
45 class PreforkProcessRestartTests(TestCase
):
49 lp_ctx
= self
.get_loadparm()
50 self
.msg_ctx
= Messaging(lp_ctx
=lp_ctx
)
52 def get_process_data(self
):
53 services
= self
.msg_ctx
.irpc_all_servers()
56 for service
in services
:
57 for id in service
.ids
:
58 processes
.append((service
.name
, id.pid
))
61 def get_process(self
, name
):
62 processes
= self
.get_process_data()
63 for pname
, pid
in processes
:
68 def get_worker_pids(self
, name
, workers
):
70 for x
in range(workers
):
71 process_name
= "prefork-worker-{0}-{1}".format(name
, x
)
72 pids
.append(self
.get_process(process_name
))
73 self
.assertIsNotNone(pids
[x
])
76 def wait_for_workers(self
, name
, workers
):
77 num_workers
= len(workers
)
78 for x
in range(num_workers
):
79 process_name
= "prefork-worker-{0}-{1}".format(name
, x
)
80 self
.wait_for_process(process_name
, workers
[x
], 0, 1, 30)
82 def wait_for_process(self
, name
, pid
, initial_delay
, wait
, timeout
):
83 time
.sleep(initial_delay
)
85 while delay
< timeout
:
86 p
= self
.get_process(name
)
87 if p
is not None and p
!= pid
:
88 # process has restarted
92 self
.fail("Times out after {0} seconds waiting for {1} to restart".
95 def check_for_duplicate_processes(self
):
96 processes
= self
.get_process_data()
98 for name
, p
in processes
:
99 if (name
.startswith("prefork-") or
100 name
.endswith("_server") or
101 name
.endswith("srv")):
103 if name
in process_map
:
104 if p
!= process_map
[name
]:
106 "Duplicate process for {0}, pids {1} and {2}".
107 format(name
, p
, process_map
[name
]))
109 def simple_bind(self
):
110 creds
= self
.insta_creds(template
=self
.get_credentials())
111 creds
.set_bind_dn("%s\\%s" % (creds
.get_domain(),
112 creds
.get_username()))
114 self
.samdb
= SamDB(url
="ldaps://%s" % os
.environ
["SERVER"],
115 lp
=self
.get_loadparm(),
119 conn
= echo
.rpcecho("ncalrpc:", self
.get_loadparm())
120 self
.assertEqual([1, 2, 3], conn
.EchoData([1, 2, 3]))
123 server
= os
.environ
["SERVER"]
124 host
= os
.environ
["SERVER_IP"]
125 lp
= self
.get_loadparm()
127 credentials
= self
.get_credentials()
129 session
= system_session()
130 ldb
= SamDB(url
="ldap://%s" % host
,
131 session_info
=session
,
132 credentials
=credentials
,
134 machine_pass
= samba
.generate_random_password(32, 32)
135 machine_name
= MACHINE_NAME
136 machine_dn
= "cn=%s,%s" % (machine_name
, ldb
.domain_dn())
138 delete_force(ldb
, machine_dn
)
140 utf16pw
= ('"%s"' % get_string(machine_pass
)).encode('utf-16-le')
143 "objectclass": "computer",
144 "sAMAccountName": "%s$" % machine_name
,
145 "userAccountControl":
146 str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD
),
147 "unicodePwd": utf16pw
})
149 machine_creds
= Credentials()
150 machine_creds
.guess(lp
)
151 machine_creds
.set_secure_channel_type(SEC_CHAN_WKSTA
)
152 machine_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
153 machine_creds
.set_password(machine_pass
)
154 machine_creds
.set_username(machine_name
+ "$")
155 machine_creds
.set_workstation(machine_name
)
158 "ncacn_ip_tcp:%s[schannel,seal]" % server
,
162 delete_force(ldb
, machine_dn
)
164 def test_ldap_master_restart(self
):
165 # check ldap connection, do a simple bind
168 # get ldap master process
169 pid
= self
.get_process("prefork-master-ldap")
170 self
.assertIsNotNone(pid
)
172 # Get the worker processes
173 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
176 os
.kill(pid
, signal
.SIGTERM
)
178 # wait for the process to restart
179 self
.wait_for_process("prefork-master-ldap", pid
, 1, 1, 30)
181 # restarting the master restarts the workers as well, so make sure
182 # they have finished restarting
183 self
.wait_for_workers("ldap", workers
)
185 # get ldap master process
186 new_pid
= self
.get_process("prefork-master-ldap")
187 self
.assertIsNotNone(new_pid
)
189 # check that the pid has changed
190 self
.assertNotEqual(pid
, new_pid
)
192 # check that the worker processes have restarted
193 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
194 for x
in range(NUM_WORKERS
):
195 self
.assertNotEqual(workers
[x
], new_workers
[x
])
197 # check that the previous server entries have been removed.
198 self
.check_for_duplicate_processes()
200 # check ldap connection, another simple bind
203 def test_ldap_worker_restart(self
):
204 # check ldap connection, do a simple bind
207 # get ldap master process
208 pid
= self
.get_process("prefork-master-ldap")
209 self
.assertIsNotNone(pid
)
211 # Get the worker processes
212 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
215 os
.kill(workers
[0], signal
.SIGTERM
)
217 # wait for the process to restart
218 self
.wait_for_process("prefork-worker-ldap-0", pid
, 1, 1, 30)
220 # get ldap master process
221 new_pid
= self
.get_process("prefork-master-ldap")
222 self
.assertIsNotNone(new_pid
)
224 # check that the pid has not changed
225 self
.assertEqual(pid
, new_pid
)
227 # check that the worker processes have restarted
228 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
229 # process 0 should have a new pid the others should be unchanged
230 self
.assertNotEqual(workers
[0], new_workers
[0])
231 self
.assertEqual(workers
[1], new_workers
[1])
232 self
.assertEqual(workers
[2], new_workers
[2])
233 self
.assertEqual(workers
[3], new_workers
[3])
235 # check that the previous server entries have been removed.
236 self
.check_for_duplicate_processes()
238 # check ldap connection, another simple bind
242 # Kill all the ldap worker processes and ensure that they are restarted
245 def test_ldap_all_workers_restart(self
):
246 # check ldap connection, do a simple bind
249 # get ldap master process
250 pid
= self
.get_process("prefork-master-ldap")
251 self
.assertIsNotNone(pid
)
253 # Get the worker processes
254 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
256 # kill all the worker processes
258 os
.kill(x
, signal
.SIGTERM
)
260 # wait for the worker processes to restart
261 self
.wait_for_workers("ldap", workers
)
263 # get ldap master process
264 new_pid
= self
.get_process("prefork-master-ldap")
265 self
.assertIsNotNone(new_pid
)
267 # check that the pid has not changed
268 self
.assertEqual(pid
, new_pid
)
270 # check that the worker processes have restarted
271 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
272 for x
in range(NUM_WORKERS
):
273 self
.assertNotEqual(workers
[x
], new_workers
[x
])
275 # check that the previous server entries have been removed.
276 self
.check_for_duplicate_processes()
278 # check ldap connection, another simple bind
281 def test_rpc_master_restart(self
):
282 # check rpc connection, make a rpc echo request
285 # get rpc master process
286 pid
= self
.get_process("prefork-master-rpc")
287 self
.assertIsNotNone(pid
)
289 # Get the worker processes
290 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
293 os
.kill(pid
, signal
.SIGTERM
)
295 # wait for the process to restart
296 self
.wait_for_process("prefork-master-rpc", pid
, 1, 1, 30)
298 # wait for workers to restart as well
299 self
.wait_for_workers("rpc", workers
)
301 # get ldap master process
302 new_pid
= self
.get_process("prefork-master-rpc")
303 self
.assertIsNotNone(new_pid
)
305 # check that the pid has changed
306 self
.assertNotEqual(pid
, new_pid
)
308 # check that the worker processes have restarted
309 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
310 for x
in range(NUM_WORKERS
):
311 self
.assertNotEqual(workers
[x
], new_workers
[x
])
313 # check that the previous server entries have been removed.
314 self
.check_for_duplicate_processes()
316 # check rpc connection, another rpc echo request
319 def test_rpc_worker_zero_restart(self
):
320 # check rpc connection, make a rpc echo request and a netlogon request
324 # get rpc master process
325 pid
= self
.get_process("prefork-master-rpc")
326 self
.assertIsNotNone(pid
)
328 # Get the worker processes
329 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
332 os
.kill(workers
[0], signal
.SIGTERM
)
334 # wait for the process to restart
335 self
.wait_for_process("prefork-worker-rpc-0", workers
[0], 1, 1, 30)
337 # get rpc master process
338 new_pid
= self
.get_process("prefork-master-rpc")
339 self
.assertIsNotNone(new_pid
)
341 # check that the pid has not changed
342 self
.assertEqual(pid
, new_pid
)
344 # check that the worker processes have restarted
345 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
346 # process 0 should have a new pid the others should be unchanged
347 self
.assertNotEqual(workers
[0], new_workers
[0])
348 self
.assertEqual(workers
[1], new_workers
[1])
349 self
.assertEqual(workers
[2], new_workers
[2])
350 self
.assertEqual(workers
[3], new_workers
[3])
352 # check that the previous server entries have been removed.
353 self
.check_for_duplicate_processes()
355 # check rpc connection, another rpc echo request, and netlogon request
359 def test_rpc_all_workers_restart(self
):
360 # check rpc connection, make a rpc echo request, and a netlogon request
364 # get rpc master process
365 pid
= self
.get_process("prefork-master-rpc")
366 self
.assertIsNotNone(pid
)
368 # Get the worker processes
369 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
371 # kill all the worker processes
373 os
.kill(x
, signal
.SIGTERM
)
375 # wait for the worker processes to restart
376 for x
in range(NUM_WORKERS
):
377 self
.wait_for_process(
378 "prefork-worker-rpc-{0}".format(x
), workers
[x
], 0, 1, 30)
380 # get rpc master process
381 new_pid
= self
.get_process("prefork-master-rpc")
382 self
.assertIsNotNone(new_pid
)
384 # check that the pid has not changed
385 self
.assertEqual(pid
, new_pid
)
387 # check that the worker processes have restarted
388 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
389 for x
in range(NUM_WORKERS
):
390 self
.assertNotEqual(workers
[x
], new_workers
[x
])
392 # check that the previous server entries have been removed.
393 self
.check_for_duplicate_processes()
395 # check rpc connection, another rpc echo request and netlogon
399 def test_master_restart_backoff(self
):
401 # get kdc master process
402 pid
= self
.get_process("prefork-master-echo")
403 self
.assertIsNotNone(pid
)
406 # Check that the processes get backed off as expected
408 # have prefork backoff increment = 5
409 # prefork maximum backoff = 10
410 backoff_increment
= 5
411 for expected
in [0, 5, 10, 10]:
412 # Get the worker processes
413 workers
= self
.get_worker_pids("kdc", NUM_WORKERS
)
415 process
= self
.get_process("prefork-master-echo")
416 os
.kill(process
, signal
.SIGTERM
)
417 # wait for the process to restart
419 self
.wait_for_process("prefork-master-echo", process
, 0, 1, 30)
420 # wait for the workers to restart as well
421 self
.wait_for_workers("echo", workers
)
423 duration
= end
- start
425 # process restart will take some time. Check that the elapsed
426 # duration falls somewhere in the expected range, i.e. we haven't
427 # taken longer than the backoff increment
428 self
.assertLess(duration
, expected
+ backoff_increment
)
429 self
.assertGreaterEqual(duration
, expected
)
431 # check that the worker processes have restarted
432 new_workers
= self
.get_worker_pids("echo", NUM_WORKERS
)
433 for x
in range(NUM_WORKERS
):
434 self
.assertNotEqual(workers
[x
], new_workers
[x
])
436 # check that the previous server entries have been removed.
437 self
.check_for_duplicate_processes()
439 def test_worker_restart_backoff(self
):
441 # Check that the processes get backed off as expected
443 # have prefork backoff increment = 5
444 # prefork maximum backoff = 10
445 backoff_increment
= 5
446 for expected
in [0, 5, 10, 10]:
447 process
= self
.get_process("prefork-worker-echo-2")
448 self
.assertIsNotNone(process
)
449 os
.kill(process
, signal
.SIGTERM
)
450 # wait for the process to restart
452 self
.wait_for_process("prefork-worker-echo-2", process
, 0, 1, 30)
454 duration
= end
- start
456 # process restart will take some time. Check that the elapsed
457 # duration falls somewhere in the expected range, i.e. we haven't
458 # taken longer than the backoff increment
459 self
.assertLess(duration
, expected
+ backoff_increment
)
460 self
.assertGreaterEqual(duration
, expected
)
462 self
.check_for_duplicate_processes()