2 # Unix SMB/CIFS implementation.
4 # Copyright © 2024 Stefan Metzmacher <metze@samba.org>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 sys.path.insert(0, "bin/python")
24 os.environ["PYTHONUNBUFFERED"] = "1"
29 from samba.credentials import Credentials
30 from samba.ndr import ndr_print
31 from samba.dcerpc import witness
32 from samba.tests import DynamicTestCase, BlackboxTestCase
33 from samba.common import get_string
34 from samba import werror, WERRORError
37 class RpcdWitnessSambaTests(BlackboxTestCase):
39 def setUpDynamicTestCases(cls):
40 cls.num_nodes = int(samba.tests.env_get_var_value('NUM_NODES'))
42 def _define_tests(idx1, idx2, ndr64=False):
43 cls._define_GetInterfaceList_test(idx1, idx2, ndr64)
44 if idx1 == 0 and idx2 != -1:
45 cls._define_ResourceChangeCTDB_tests(idx1, idx2, ndr64)
47 for idx1 in range(0, cls.num_nodes):
48 _define_tests(idx1, -1, ndr64=False)
49 _define_tests(idx1, -1, ndr64=True)
50 for idx2 in range(0, cls.num_nodes):
51 _define_tests(idx1, idx2, ndr64=False)
52 _define_tests(idx1, idx2, ndr64=True)
57 # ctdb/tests/local_daemons.sh doesn't like CTDB_SOCKET to be set already
58 # and it doesn't need CTDB_BASE, so we stash them away
59 self.saved_CTDB_SOCKET = samba.tests.env_get_var_value('CTDB_SOCKET',
61 if self.saved_CTDB_SOCKET is not None:
62 del os.environ["CTDB_SOCKET"]
63 self.saved_CTDB_BASE = samba.tests.env_get_var_value('CTDB_BASE',
65 if self.saved_CTDB_BASE is not None:
66 del os.environ["CTDB_BASE"]
68 self.disabled_idx = -1
70 # set this to True in order to get verbose output
73 self.ctdb_prefix = samba.tests.env_get_var_value('CTDB_PREFIX')
75 self.cluster_share = samba.tests.env_get_var_value('CLUSTER_SHARE')
77 self.lp = self.get_loadparm(s3=True)
78 self.remote_domain = samba.tests.env_get_var_value('DOMAIN')
79 self.remote_user = samba.tests.env_get_var_value('USERNAME')
80 self.remote_password = samba.tests.env_get_var_value('PASSWORD')
81 self.remote_creds = Credentials()
82 self.remote_creds.guess(self.lp)
83 self.remote_creds.set_username(self.remote_user)
84 self.remote_creds.set_domain(self.remote_domain)
85 self.remote_creds.set_password(self.remote_password)
87 self.server_hostname = samba.tests.env_get_var_value('SERVER_HOSTNAME')
88 self.interface_group_name = samba.tests.env_get_var_value('INTERFACE_GROUP_NAME')
90 common_binding_args = "spnego,sign,target_hostname=%s" % (
93 common_binding_args += ",print"
95 common_binding_args32 = common_binding_args
96 common_binding_args64 = common_binding_args + ",ndr64"
99 for node_idx in range(0, self.num_nodes):
102 name_var = 'CTDB_SERVER_NAME_NODE%u' % node_idx
103 node["name"] = samba.tests.env_get_var_value(name_var)
105 ip_var = 'CTDB_IFACE_IP_NODE%u' % node_idx
106 node["ip"] = samba.tests.env_get_var_value(ip_var)
108 node["binding_string32"] = "ncacn_ip_tcp:%s[%s]" % (
109 node["ip"], common_binding_args32)
110 node["binding_string64"] = "ncacn_ip_tcp:%s[%s]" % (
111 node["ip"], common_binding_args64)
112 self.nodes.append(node)
115 if self.disabled_idx != -1:
116 self.enable_node(self.disabled_idx)
118 if self.saved_CTDB_SOCKET is not None:
119 os.environ["CTDB_SOCKET"] = self.saved_CTDB_SOCKET
120 self.saved_CTDB_SOCKET = None
121 if self.saved_CTDB_BASE is not None:
122 os.environ["CTDB_BASE"] = self.saved_CTDB_BASE
123 self.saved_CTDB_BASE = None
127 def call_onnode(self, nodes, cmd):
128 COMMAND = "ctdb/tests/local_daemons.sh"
130 argv = "%s '%s' onnode %s '%s'" % (COMMAND, self.ctdb_prefix, nodes, cmd)
134 print("Calling: %s" % argv)
135 out = self.check_output(argv)
136 except samba.tests.BlackboxProcessError as e:
137 self.fail("Error calling [%s]: %s" % (argv, e))
139 out_str = get_string(out)
142 def dump_ctdb_status_all(self):
143 for node_idx in range(0, self.num_nodes):
144 print("%s" % self.call_onnode(str(node_idx), "ctdb status"))
146 def disable_node(self, node_idx, dump_status=False):
148 self.dump_ctdb_status_all()
150 self.assertEqual(self.disabled_idx, -1)
151 self.call_onnode(str(node_idx), "ctdb disable")
152 self.disabled_idx = node_idx
155 self.dump_ctdb_status_all()
157 def enable_node(self, node_idx, dump_status=False):
159 self.dump_ctdb_status_all()
161 self.assertEqual(self.disabled_idx, node_idx)
162 self.call_onnode(str(node_idx), "ctdb enable")
163 self.disabled_idx = -1
166 self.dump_ctdb_status_all()
169 def _define_GetInterfaceList_test(cls, conn_idx, disable_idx, ndr64=False):
170 if disable_idx != -1:
171 disable_name = "%u_disabled" % disable_idx
173 disable_name = "all_enabled"
180 name = "Node%u_%s_%s" % (conn_idx, disable_name, ndr_name)
182 'conn_idx': conn_idx,
183 'disable_idx': disable_idx,
186 cls.generate_dynamic_test('test_GetInterfaceList', name, args)
188 def _test_GetInterfaceList_with_args(self, args):
189 conn_idx = args.pop('conn_idx')
190 disable_idx = args.pop('disable_idx')
191 ndr64 = args.pop('ndr64')
192 self.assertEqual(len(args.keys()), 0)
194 conn_node = self.nodes[conn_idx]
196 binding_string = conn_node["binding_string64"]
198 binding_string = conn_node["binding_string32"]
200 if disable_idx != -1:
201 self.disable_node(disable_idx)
203 conn = witness.witness(binding_string, self.lp, self.remote_creds)
204 interface_list = conn.GetInterfaceList()
206 if disable_idx != -1:
207 self.enable_node(disable_idx)
209 self.assertIsNotNone(interface_list)
210 self.assertEqual(interface_list.num_interfaces, len(self.nodes))
211 for idx in range(0, interface_list.num_interfaces):
212 iface = interface_list.interfaces[idx]
213 node = self.nodes[idx]
216 expected_flags |= witness.WITNESS_INFO_IPv4_VALID
218 expected_flags |= witness.WITNESS_INFO_WITNESS_IF
220 if disable_idx == idx:
221 expected_state = witness.WITNESS_STATE_UNAVAILABLE
223 expected_state = witness.WITNESS_STATE_AVAILABLE
225 self.assertIsNotNone(iface.group_name)
226 self.assertEqual(iface.group_name, self.interface_group_name)
228 self.assertEqual(iface.version, witness.WITNESS_V2)
229 self.assertEqual(iface.state, expected_state)
231 self.assertIsNotNone(iface.ipv4)
232 self.assertEqual(iface.ipv4, node["ip"])
234 self.assertIsNotNone(iface.ipv6)
235 self.assertEqual(iface.ipv6,
236 "0000:0000:0000:0000:0000:0000:0000:0000")
238 self.assertEqual(iface.flags, expected_flags)
240 def assertResourceChanges(self, response, expected_resource_changes):
241 self.assertIsNotNone(response)
242 self.assertEqual(response.type,
243 witness.WITNESS_NOTIFY_RESOURCE_CHANGE)
244 self.assertEqual(response.num, len(expected_resource_changes))
245 self.assertEqual(len(response.messages), len(expected_resource_changes))
246 for ri in range(0, len(expected_resource_changes)):
247 expected_resource_change = expected_resource_changes[ri]
248 resource_change = response.messages[ri]
249 self.assertIsNotNone(resource_change)
251 expected_type = witness.WITNESS_RESOURCE_STATE_UNAVAILABLE
252 expected_type = expected_resource_change.get('type', expected_type)
254 expected_name = expected_resource_change.get('name')
256 self.assertEqual(resource_change.type, expected_type)
257 self.assertIsNotNone(resource_change.name)
258 self.assertEqual(resource_change.name, expected_name)
260 def assertResourceChange(self, response, expected_type, expected_name):
261 expected_resource_change = {
262 'type': expected_type,
263 'name': expected_name,
265 expected_resource_changes = [expected_resource_change]
266 self.assertResourceChanges(response, expected_resource_changes)
269 def _define_ResourceChangeCTDB_tests(cls, conn_idx, monitor_idx, ndr64=False):
275 name_suffix = "WNode%u_RNode%u_%s" % (conn_idx, monitor_idx, ndr_name)
277 'conn_idx': conn_idx,
278 'monitor_idx': monitor_idx,
282 name = "v1_disabled_after_%s" % name_suffix
283 args = base_args.copy()
284 args['reg_v1'] = True
285 args['disable_after_reg'] = True
286 args['explicit_unregister'] = False
287 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
289 name = "v1_disabled_after_enabled_after_%s" % name_suffix
290 args = base_args.copy()
291 args['reg_v1'] = True
292 args['disable_after_reg'] = True
293 args['enable_after_reg'] = True
294 args['explicit_unregister'] = False
295 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
297 name = "v2_disabled_before_enable_after_%s" % name_suffix
298 args = base_args.copy()
299 args['disable_before_reg'] = True
300 args['enable_after_reg'] = True
301 args['wait_for_timeout'] = True
303 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
305 name = "v2_disabled_after_%s" % name_suffix
306 args = base_args.copy()
307 args['disable_after_reg'] = True
308 args['wait_for_not_found'] = True
309 args['explicit_unregister'] = False
310 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
312 name = "v2_disabled_after_enabled_after_%s" % name_suffix
313 args = base_args.copy()
314 args['disable_after_reg'] = True
315 args['enable_after_reg'] = True
316 args['wait_for_not_found'] = True
317 args['explicit_unregister'] = False
318 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
320 name = "share_v2_disabled_before_enable_after_%s" % name_suffix
321 args = base_args.copy()
322 args['share_reg'] = True
323 args['disable_before_reg'] = True
324 args['enable_after_reg'] = True
325 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
327 name = "share_v2_disabled_after_%s" % name_suffix
328 args = base_args.copy()
329 args['share_reg'] = True
330 args['disable_after_reg'] = True
331 args['explicit_unregister'] = False
332 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
334 name = "share_v2_disabled_after_enabled_after_%s" % name_suffix
335 args = base_args.copy()
336 args['share_reg'] = True
337 args['disable_after_reg'] = True
338 args['enable_after_reg'] = True
339 args['explicit_unregister'] = False
340 cls.generate_dynamic_test('test_ResourceChangeCTDB', name, args)
342 def _test_ResourceChangeCTDB_with_args(self, args):
343 conn_idx = args.pop('conn_idx')
344 monitor_idx = args.pop('monitor_idx')
345 ndr64 = args.pop('ndr64')
346 timeout = int(args.pop('timeout', 15))
347 reg_v1 = args.pop('reg_v1', False)
348 share_reg = args.pop('share_reg', False)
349 disable_before_reg = args.pop('disable_before_reg', False)
350 disable_after_reg = args.pop('disable_after_reg', False)
351 enable_after_reg = args.pop('enable_after_reg', False)
352 explicit_unregister = args.pop('explicit_unregister', True)
353 wait_for_not_found = args.pop('wait_for_not_found', False)
354 wait_for_timeout = args.pop('wait_for_timeout', False)
355 self.assertEqual(len(args.keys()), 0)
357 conn_node = self.nodes[conn_idx]
359 binding_string = conn_node["binding_string64"]
361 binding_string = conn_node["binding_string32"]
362 monitor_node = self.nodes[monitor_idx]
364 computer_name = "test-rpcd-witness-samba-only-client-computer"
366 conn = witness.witness(binding_string, self.lp, self.remote_creds)
368 if disable_before_reg:
369 self.assertFalse(disable_after_reg)
370 self.disable_node(monitor_idx)
373 self.assertFalse(wait_for_timeout)
374 self.assertFalse(share_reg)
376 reg_context = conn.Register(witness.WITNESS_V1,
377 self.server_hostname,
382 share_name = self.cluster_share
386 reg_context = conn.RegisterEx(witness.WITNESS_V2,
387 self.server_hostname,
391 witness.WITNESS_REGISTER_NONE,
394 if disable_after_reg:
395 self.assertFalse(disable_before_reg)
396 self.disable_node(monitor_idx)
399 self.enable_node(monitor_idx)
401 if disable_after_reg:
402 response_unavailable = conn.AsyncNotify(reg_context)
403 self.assertResourceChange(response_unavailable,
404 witness.WITNESS_RESOURCE_STATE_UNAVAILABLE,
408 response_available = conn.AsyncNotify(reg_context)
409 self.assertResourceChange(response_available,
410 witness.WITNESS_RESOURCE_STATE_AVAILABLE,
414 self.assertFalse(wait_for_not_found)
415 self.assertFalse(disable_after_reg)
417 _ = conn.AsyncNotify(reg_context)
419 except WERRORError as e:
420 (num, string) = e.args
421 if num != werror.WERR_TIMEOUT:
424 if wait_for_not_found:
425 self.assertFalse(wait_for_timeout)
426 self.assertTrue(disable_after_reg)
427 self.assertFalse(explicit_unregister)
429 _ = conn.AsyncNotify(reg_context)
431 except WERRORError as e:
432 (num, string) = e.args
433 if num != werror.WERR_NOT_FOUND:
436 if not explicit_unregister:
439 conn.UnRegister(reg_context)
442 _ = conn.AsyncNotify(reg_context)
444 except WERRORError as e:
445 (num, string) = e.args
446 if num != werror.WERR_NOT_FOUND:
450 conn.UnRegister(reg_context)
452 except WERRORError as e:
453 (num, string) = e.args
454 if num != werror.WERR_NOT_FOUND:
457 if __name__ == "__main__":