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
):
48 super(PreforkProcessRestartTests
, self
).setUp()
49 lp_ctx
= self
.get_loadparm()
50 self
.msg_ctx
= Messaging(lp_ctx
=lp_ctx
)
53 super(PreforkProcessRestartTests
, self
).tearDown()
55 def get_process_data(self
):
56 services
= self
.msg_ctx
.irpc_all_servers()
59 for service
in services
:
60 for id in service
.ids
:
61 processes
.append((service
.name
, id.pid
))
64 def get_process(self
, name
):
65 processes
= self
.get_process_data()
66 for pname
, pid
in processes
:
71 def get_worker_pids(self
, name
, workers
):
73 for x
in range(workers
):
74 process_name
= "prefork-worker-{0}-{1}".format(name
, x
)
75 pids
.append(self
.get_process(process_name
))
76 self
.assertIsNotNone(pids
[x
])
79 def wait_for_workers(self
, name
, workers
):
80 num_workers
= len(workers
)
81 for x
in range(num_workers
):
82 process_name
= "prefork-worker-{0}-{1}".format(name
, x
)
83 self
.wait_for_process(process_name
, workers
[x
], 0, 1, 30)
85 def wait_for_process(self
, name
, pid
, initial_delay
, wait
, timeout
):
86 time
.sleep(initial_delay
)
88 while delay
< timeout
:
89 p
= self
.get_process(name
)
90 if p
is not None and p
!= pid
:
91 # process has restarted
95 self
.fail("Times out after {0} seconds waiting for {1} to restart".
98 def check_for_duplicate_processes(self
):
99 processes
= self
.get_process_data()
101 for name
, p
in processes
:
102 if (name
.startswith("prefork-") or
103 name
.endswith("_server") or
104 name
.endswith("srv")):
106 if name
in process_map
:
107 if p
!= process_map
[name
]:
109 "Duplicate process for {0}, pids {1} and {2}".
110 format(name
, p
, process_map
[name
]))
112 def simple_bind(self
):
113 creds
= self
.insta_creds(template
=self
.get_credentials())
114 creds
.set_bind_dn("%s\\%s" % (creds
.get_domain(),
115 creds
.get_username()))
117 self
.samdb
= SamDB(url
="ldaps://%s" % os
.environ
["SERVER"],
118 lp
=self
.get_loadparm(),
122 conn
= echo
.rpcecho("ncalrpc:", self
.get_loadparm())
123 self
.assertEqual([1, 2, 3], conn
.EchoData([1, 2, 3]))
126 server
= os
.environ
["SERVER"]
127 host
= os
.environ
["SERVER_IP"]
128 lp
= self
.get_loadparm()
130 credentials
= self
.get_credentials()
132 session
= system_session()
133 ldb
= SamDB(url
="ldap://%s" % host
,
134 session_info
=session
,
135 credentials
=credentials
,
137 machine_pass
= samba
.generate_random_password(32, 32)
138 machine_name
= MACHINE_NAME
139 machine_dn
= "cn=%s,%s" % (machine_name
, ldb
.domain_dn())
141 delete_force(ldb
, machine_dn
)
143 utf16pw
= ('"%s"' % get_string(machine_pass
)).encode('utf-16-le')
146 "objectclass": "computer",
147 "sAMAccountName": "%s$" % machine_name
,
148 "userAccountControl":
149 str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD
),
150 "unicodePwd": utf16pw
})
152 machine_creds
= Credentials()
153 machine_creds
.guess(lp
)
154 machine_creds
.set_secure_channel_type(SEC_CHAN_WKSTA
)
155 machine_creds
.set_kerberos_state(DONT_USE_KERBEROS
)
156 machine_creds
.set_password(machine_pass
)
157 machine_creds
.set_username(machine_name
+ "$")
158 machine_creds
.set_workstation(machine_name
)
161 "ncacn_ip_tcp:%s[schannel,seal]" % server
,
165 delete_force(ldb
, machine_dn
)
167 def test_ldap_master_restart(self
):
168 # check ldap connection, do a simple bind
171 # get ldap master process
172 pid
= self
.get_process("prefork-master-ldap")
173 self
.assertIsNotNone(pid
)
175 # Get the worker processes
176 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
179 os
.kill(pid
, signal
.SIGTERM
)
181 # wait for the process to restart
182 self
.wait_for_process("prefork-master-ldap", pid
, 1, 1, 30)
184 # restarting the master restarts the workers as well, so make sure
185 # they have finished restarting
186 self
.wait_for_workers("ldap", workers
)
188 # get ldap master process
189 new_pid
= self
.get_process("prefork-master-ldap")
190 self
.assertIsNotNone(new_pid
)
192 # check that the pid has changed
193 self
.assertNotEquals(pid
, new_pid
)
195 # check that the worker processes have restarted
196 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
197 for x
in range(NUM_WORKERS
):
198 self
.assertNotEquals(workers
[x
], new_workers
[x
])
200 # check that the previous server entries have been removed.
201 self
.check_for_duplicate_processes()
203 # check ldap connection, another simple bind
206 def test_ldap_worker_restart(self
):
207 # check ldap connection, do a simple bind
210 # get ldap master process
211 pid
= self
.get_process("prefork-master-ldap")
212 self
.assertIsNotNone(pid
)
214 # Get the worker processes
215 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
218 os
.kill(workers
[0], signal
.SIGTERM
)
220 # wait for the process to restart
221 self
.wait_for_process("prefork-worker-ldap-0", pid
, 1, 1, 30)
223 # get ldap master process
224 new_pid
= self
.get_process("prefork-master-ldap")
225 self
.assertIsNotNone(new_pid
)
227 # check that the pid has not changed
228 self
.assertEqual(pid
, new_pid
)
230 # check that the worker processes have restarted
231 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
232 # process 0 should have a new pid the others should be unchanged
233 self
.assertNotEquals(workers
[0], new_workers
[0])
234 self
.assertEqual(workers
[1], new_workers
[1])
235 self
.assertEqual(workers
[2], new_workers
[2])
236 self
.assertEqual(workers
[3], new_workers
[3])
238 # check that the previous server entries have been removed.
239 self
.check_for_duplicate_processes()
241 # check ldap connection, another simple bind
245 # Kill all the ldap worker processes and ensure that they are restarted
248 def test_ldap_all_workers_restart(self
):
249 # check ldap connection, do a simple bind
252 # get ldap master process
253 pid
= self
.get_process("prefork-master-ldap")
254 self
.assertIsNotNone(pid
)
256 # Get the worker processes
257 workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
259 # kill all the worker processes
261 os
.kill(x
, signal
.SIGTERM
)
263 # wait for the worker processes to restart
264 self
.wait_for_workers("ldap", workers
)
266 # get ldap master process
267 new_pid
= self
.get_process("prefork-master-ldap")
268 self
.assertIsNotNone(new_pid
)
270 # check that the pid has not changed
271 self
.assertEqual(pid
, new_pid
)
273 # check that the worker processes have restarted
274 new_workers
= self
.get_worker_pids("ldap", NUM_WORKERS
)
275 for x
in range(NUM_WORKERS
):
276 self
.assertNotEquals(workers
[x
], new_workers
[x
])
278 # check that the previous server entries have been removed.
279 self
.check_for_duplicate_processes()
281 # check ldap connection, another simple bind
284 def test_rpc_master_restart(self
):
285 # check rpc connection, make a rpc echo request
288 # get rpc master process
289 pid
= self
.get_process("prefork-master-rpc")
290 self
.assertIsNotNone(pid
)
292 # Get the worker processes
293 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
296 os
.kill(pid
, signal
.SIGTERM
)
298 # wait for the process to restart
299 self
.wait_for_process("prefork-master-rpc", pid
, 1, 1, 30)
301 # wait for workers to restart as well
302 self
.wait_for_workers("rpc", workers
)
304 # get ldap master process
305 new_pid
= self
.get_process("prefork-master-rpc")
306 self
.assertIsNotNone(new_pid
)
308 # check that the pid has changed
309 self
.assertNotEquals(pid
, new_pid
)
311 # check that the worker processes have restarted
312 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
313 for x
in range(NUM_WORKERS
):
314 self
.assertNotEquals(workers
[x
], new_workers
[x
])
316 # check that the previous server entries have been removed.
317 self
.check_for_duplicate_processes()
319 # check rpc connection, another rpc echo request
322 def test_rpc_worker_zero_restart(self
):
323 # check rpc connection, make a rpc echo request and a netlogon request
327 # get rpc master process
328 pid
= self
.get_process("prefork-master-rpc")
329 self
.assertIsNotNone(pid
)
331 # Get the worker processes
332 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
335 os
.kill(workers
[0], signal
.SIGTERM
)
337 # wait for the process to restart
338 self
.wait_for_process("prefork-worker-rpc-0", workers
[0], 1, 1, 30)
340 # get rpc master process
341 new_pid
= self
.get_process("prefork-master-rpc")
342 self
.assertIsNotNone(new_pid
)
344 # check that the pid has not changed
345 self
.assertEqual(pid
, new_pid
)
347 # check that the worker processes have restarted
348 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
349 # process 0 should have a new pid the others should be unchanged
350 self
.assertNotEquals(workers
[0], new_workers
[0])
351 self
.assertEqual(workers
[1], new_workers
[1])
352 self
.assertEqual(workers
[2], new_workers
[2])
353 self
.assertEqual(workers
[3], new_workers
[3])
355 # check that the previous server entries have been removed.
356 self
.check_for_duplicate_processes()
358 # check rpc connection, another rpc echo request, and netlogon request
362 def test_rpc_all_workers_restart(self
):
363 # check rpc connection, make a rpc echo request, and a netlogon request
367 # get rpc master process
368 pid
= self
.get_process("prefork-master-rpc")
369 self
.assertIsNotNone(pid
)
371 # Get the worker processes
372 workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
374 # kill all the worker processes
376 os
.kill(x
, signal
.SIGTERM
)
378 # wait for the worker processes to restart
379 for x
in range(NUM_WORKERS
):
380 self
.wait_for_process(
381 "prefork-worker-rpc-{0}".format(x
), workers
[x
], 0, 1, 30)
383 # get rpc master process
384 new_pid
= self
.get_process("prefork-master-rpc")
385 self
.assertIsNotNone(new_pid
)
387 # check that the pid has not changed
388 self
.assertEqual(pid
, new_pid
)
390 # check that the worker processes have restarted
391 new_workers
= self
.get_worker_pids("rpc", NUM_WORKERS
)
392 for x
in range(NUM_WORKERS
):
393 self
.assertNotEquals(workers
[x
], new_workers
[x
])
395 # check that the previous server entries have been removed.
396 self
.check_for_duplicate_processes()
398 # check rpc connection, another rpc echo request and netlogon
402 def test_master_restart_backoff(self
):
404 # get kdc master process
405 pid
= self
.get_process("prefork-master-echo")
406 self
.assertIsNotNone(pid
)
409 # Check that the processes get backed off as expected
411 # have prefork backoff increment = 5
412 # prefork maximum backoff = 10
413 backoff_increment
= 5
414 for expected
in [0, 5, 10, 10]:
415 # Get the worker processes
416 workers
= self
.get_worker_pids("kdc", NUM_WORKERS
)
418 process
= self
.get_process("prefork-master-echo")
419 os
.kill(process
, signal
.SIGTERM
)
420 # wait for the process to restart
422 self
.wait_for_process("prefork-master-echo", process
, 0, 1, 30)
423 # wait for the workers to restart as well
424 self
.wait_for_workers("echo", workers
)
426 duration
= end
- start
428 # process restart will take some time. Check that the elapsed
429 # duration falls somewhere in the expected range, i.e. we haven't
430 # taken longer than the backoff increment
431 self
.assertLess(duration
, expected
+ backoff_increment
)
432 self
.assertGreaterEqual(duration
, expected
)
434 # check that the worker processes have restarted
435 new_workers
= self
.get_worker_pids("echo", NUM_WORKERS
)
436 for x
in range(NUM_WORKERS
):
437 self
.assertNotEquals(workers
[x
], new_workers
[x
])
439 # check that the previous server entries have been removed.
440 self
.check_for_duplicate_processes()
442 def test_worker_restart_backoff(self
):
444 # Check that the processes get backed off as expected
446 # have prefork backoff increment = 5
447 # prefork maximum backoff = 10
448 backoff_increment
= 5
449 for expected
in [0, 5, 10, 10]:
450 process
= self
.get_process("prefork-worker-echo-2")
451 self
.assertIsNotNone(process
)
452 os
.kill(process
, signal
.SIGTERM
)
453 # wait for the process to restart
455 self
.wait_for_process("prefork-worker-echo-2", process
, 0, 1, 30)
457 duration
= end
- start
459 # process restart will take some time. Check that the elapsed
460 # duration falls somewhere in the expected range, i.e. we haven't
461 # taken longer than the backoff increment
462 self
.assertLess(duration
, expected
+ backoff_increment
)
463 self
.assertGreaterEqual(duration
, expected
)
465 self
.check_for_duplicate_processes()