]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
selftest: Add unit tests of the DC startup FL check/update code
authorAndrew Bartlett <abartlet@samba.org>
Thu, 8 Jun 2023 21:17:39 +0000 (09:17 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 14 Jun 2023 22:57:34 +0000 (22:57 +0000)
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
python/samba/tests/dsdb.py
python/samba/tests/samba_startup_fl_change.py [new file with mode: 0644]
source4/selftest/tests.py

index 9f937824ee062011424ca0319e7680f97d45005e..59d946cd6a673555b4941100b806b521f78b455f 100644 (file)
@@ -24,7 +24,8 @@ from samba.tests import TestCase
 from samba.tests import delete_force
 from samba.ndr import ndr_unpack, ndr_pack
 from samba.dcerpc import drsblobs, security, misc
-from samba import dsdb
+from samba.param import LoadParm
+from samba import dsdb, functional_level
 from samba import werror
 import ldb
 import samba
@@ -1181,3 +1182,42 @@ class DsdbFullScanTests(TestCase):
         except ldb.LdbError as err:
             estr = err.args[1]
             self.fail("sam.ldb required a full scan to start up")
+
+class DsdbStartUpTests(TestCase):
+    def setUp(self):
+        super().setUp()
+        lp = samba.tests.env_loadparm()
+        path = lp.configfile
+
+        # This is to avoid a tatoo of the global state
+        self.lp = LoadParm(filename_for_non_global_lp=path)
+        self.creds = Credentials()
+        self.creds.guess(self.lp)
+        self.session = system_session()
+        self.samdb = SamDB(session_info=self.session,
+                           credentials=self.creds,
+                           lp=self.lp)
+
+    def test_correct_fl(self):
+        res = self.samdb.search(base="",
+                                 scope=ldb.SCOPE_BASE,
+                                 attrs=["domainFunctionality"])
+        # This confirms the domain is in FL 2016 by default, this is
+        # important to verify the original state
+        self.assertEqual(int(res[0]["domainFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+        self.assertEqual(functional_level.dc_level_from_lp(self.lp),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+        dsdb.check_and_update_fl(self.samdb, self.lp)
+
+    def test_lower_smb_conf_fl(self):
+        old_lp_fl = self.lp.get("ad dc functional level")
+        self.lp.set("ad dc functional level",
+                    "2008_R2")
+        self.addCleanup(self.lp.set, "ad dc functional level", old_lp_fl)
+        try:
+            dsdb.check_and_update_fl(self.samdb, self.lp)
+            self.fail("Should have failed to start DC with 2008 R2 FL in 2016 domain")
+        except ldb.LdbError as err:
+            (errno, estr) = err.args
+            self.assertEqual(errno, ldb.ERR_CONSTRAINT_VIOLATION)
diff --git a/python/samba/tests/samba_startup_fl_change.py b/python/samba/tests/samba_startup_fl_change.py
new file mode 100644 (file)
index 0000000..1fb97d5
--- /dev/null
@@ -0,0 +1,181 @@
+# Unix SMB/CIFS implementation. Tests for dsdb
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2023
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""Tests for samba.dsdb."""
+
+from samba.credentials import Credentials
+from samba.samdb import SamDB
+from samba.auth import system_session
+from samba.tests import TestCase
+from samba.param import LoadParm
+from samba import dsdb, functional_level
+import ldb, samba
+
+
+from samba.tests.samba_tool.base import SambaToolCmdTest
+import os
+import shutil
+import tempfile
+
+class SambaFLStartUpTests(SambaToolCmdTest):
+    """Test the samba binary sets the DC FL on startup for RW DCs"""
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.classtempdir = tempfile.mkdtemp()
+        cls.tempsambadir = os.path.join(cls.classtempdir, "samba")
+
+        command = (
+                "samba-tool " +
+                "domain provision " +
+                "--realm=foo.example.com " +
+                "--domain=FOO " +
+                ("--targetdir=%s " % cls.tempsambadir) +
+                "--use-ntvfs"
+        )
+
+        (result, out, err) = cls.run_command(command)
+        if (result != 0):
+            raise AssertionError
+
+    @classmethod
+    def tearDownClass(cls):
+        super().tearDownClass()
+        shutil.rmtree(cls.tempsambadir)
+
+    def setUp(self):
+        super().setUp()
+        path = os.path.join(self.tempsambadir, "etc/smb.conf")
+        self.lp = LoadParm(filename_for_non_global_lp=path)
+        self.creds = Credentials()
+        self.creds.guess(self.lp)
+        self.session = system_session()
+        self.samdb = SamDB(session_info=self.session,
+                           credentials=self.creds,
+                           lp=self.lp)
+
+
+    def test_initial_db_fl_state(self):
+        server_dn = self.samdb.get_dsServiceName()
+        res = self.samdb.search(base=server_dn,
+                                 scope=ldb.SCOPE_BASE,
+                                 attrs=["msDS-Behavior-Version"])
+        # This confirms the domain is in FL 2008 R2 by default, this is
+        # important to verify the original state
+        self.assertEqual(int(res[0]["msDS-Behavior-Version"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+    def test_initial_rootdse_domain_fl_state(self):
+        res = self.samdb.search(base="",
+                                scope=ldb.SCOPE_BASE,
+                                attrs=["domainControllerFunctionality"])
+        self.assertEqual(int(res[0]["domainControllerFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+    def test_initial_rootdse_dc_fl_state(self):
+        res = self.samdb.search(base="",
+                                scope=ldb.SCOPE_BASE,
+                                attrs=["domainFunctionality"])
+        self.assertEqual(int(res[0]["domainFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+    def test_initial_lp_fl_state(self):
+        lp_fl = self.lp.get("ad dc functional level")
+        # This confirms the domain is in FL 2008 R2 by default, this is
+        # important to verify the original state
+        self.assertEqual(lp_fl, "2008_R2")
+
+    def test_initial_lp_fl_state_mapped(self):
+        # Confirm the same via the dc_level_from_lp wrapper
+        self.assertEqual(functional_level.dc_level_from_lp(self.lp),
+                         dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+    def fixup_fl(self, dn, fl):
+        msg = ldb.Message()
+        msg.dn = dn
+        msg["msDS-Behavior-Version"] = (
+            ldb.MessageElement(str(fl),
+                               ldb.FLAG_MOD_REPLACE,
+                               "msDS-Behavior-Version"))
+        self.samdb.modify(msg)
+
+    def test_change_db_dc_fl(self):
+        server_dn = ldb.Dn(self.samdb, self.samdb.get_dsServiceName())
+        msg = ldb.Message()
+        msg.dn = server_dn
+        msg["msDS-Behavior-Version"] = (
+                ldb.MessageElement(str(dsdb.DS_DOMAIN_FUNCTION_2012_R2),
+                                   ldb.FLAG_MOD_REPLACE,
+                                   "msDS-Behavior-Version"))
+        self.samdb.modify(msg)
+        self.addCleanup(self.fixup_fl, msg.dn, dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+        samdb2 = SamDB(session_info=self.session,
+                       credentials=self.creds,
+                       lp=self.lp)
+
+        # Check that the DB set to 2012_R2 has got as far as the rootDSE handler on a new connection
+        res = samdb2.search(base="",
+                            scope=ldb.SCOPE_BASE,
+                            attrs=["domainControllerFunctionality"])
+        self.assertEqual(int(res[0]["domainControllerFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2012_R2)
+
+    def test_incorrect_db_dc_fl(self):
+        server_dn = ldb.Dn(self.samdb, self.samdb.get_dsServiceName())
+        self.addCleanup(self.fixup_fl, server_dn, dsdb.DS_DOMAIN_FUNCTION_2008_R2)
+
+        old_lp_fl = self.lp.get("ad dc functional level")
+        self.lp.set("ad dc functional level",
+                    "2016")
+        self.addCleanup(self.lp.set, "ad dc functional level", old_lp_fl)
+
+        dsdb.check_and_update_fl(self.samdb, self.lp)
+
+        # Check this has been set to 2016 per the smb.conf setting
+        res = self.samdb.search(base="",
+                                 scope=ldb.SCOPE_BASE,
+                                 attrs=["domainControllerFunctionality"])
+        self.assertEqual(int(res[0]["domainControllerFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+
+        samdb3 = SamDB(session_info=self.session,
+                       credentials=self.creds,
+                       lp=self.lp)
+
+        # Check this is still set on re-read (not just the opaque)
+        res = samdb3.search(base="",
+                            scope=ldb.SCOPE_BASE,
+                            attrs=["domainControllerFunctionality"])
+        self.assertEqual(int(res[0]["domainControllerFunctionality"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+
+        res = self.samdb.search(base=server_dn,
+                                 scope=ldb.SCOPE_BASE,
+                                 attrs=["msDS-Behavior-Version"])
+        self.assertEqual(int(res[0]["msDS-Behavior-Version"][0]),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+
+        self.assertEqual(functional_level.dc_level_from_lp(self.lp),
+                         dsdb.DS_DOMAIN_FUNCTION_2016)
+        self.assertEqual(self.lp.get("ad dc functional level"),
+                         "2016")
+
+if __name__ == "__main__":
+    import unittest
+    unittest.main()
index e8c933e1ad7648421a11837eeeb93aa52516df10..51b5e1ac4f621fcb142d2dba15235ee1bdadd5e1 100755 (executable)
@@ -1079,6 +1079,7 @@ planoldpythontestsuite("ad_dc_default:local", "samba.tests.gensec", extra_args=[
 planoldpythontestsuite("none", "simple", extra_path=["%s/lib/tdb/python/tests" % srcdir()], name="tdb.python")
 planpythontestsuite("ad_dc_default:local", "samba.tests.dcerpc.sam")
 planpythontestsuite("ad_dc_default:local", "samba.tests.dsdb")
+planpythontestsuite("none", "samba.tests.samba_startup_fl_change")
 planpythontestsuite("none", "samba.tests.dsdb_lock")
 planpythontestsuite("ad_dc_default:local", "samba.tests.dcerpc.bare")
 planpythontestsuite("ad_dc_default:local", "samba.tests.dcerpc.lsa")