downgradedatabase: blackbox: MDB backend
[metze/samba/wip.git] / python / samba / tests / blackbox / downgradedatabase.py
1 # Blackbox tests for sambadowngradedatabase
2 #
3 # Copyright (C) Catalyst IT Ltd. 2019
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 from __future__ import print_function
19 from samba.tests import BlackboxTestCase
20 import os
21 import ldb
22 from subprocess import check_output
23 from samba.samdb import SamDB
24
25 COMMAND = os.path.join(os.path.dirname(__file__),
26                "../../../../../source4/scripting/bin/sambadowngradedatabase")
27
28
29 class DowngradeTestBase(BlackboxTestCase):
30     """Test that sambadowngradedatabase downgrades the samba database"""
31
32     def setUp(self):
33         super(DowngradeTestBase, self).setUp()
34         if not hasattr(self, "backend"):
35             self.fail("Subclass this class and set 'backend'")
36
37         # Don't assert on empty tempdir contents on tearDown
38         self.check_tempdir_empty = False
39
40         prov_cmd = "samba-tool domain provision " +\
41                    "--domain FOO --realm foo.example.com " +\
42                    "--targetdir {self.tempdir} " +\
43                    "--backend-store {self.backend} " +\
44                    "--host-name downgradetest " +\
45                    "--option=\"vfs objects=fake_acls xattr_tdb\""
46         prov_cmd = prov_cmd.format(self=self)
47         self.check_run(prov_cmd, "Provisioning for downgrade")
48
49         private_dir = os.path.join(self.tempdir, "private")
50         self.sam_path = os.path.join(private_dir, "sam.ldb")
51         self.ldb = ldb.Ldb(self.sam_path, options=["modules:"])
52
53         partitions = self.ldb.search(base="@PARTITION",
54                                        scope=ldb.SCOPE_BASE,
55                                        attrs=["partition"])
56         partitions = partitions[0]['partition']
57         partitions = [str(p).split(":")[1] for p in partitions]
58         self.dbs = [os.path.join(private_dir, p)
59                     for p in partitions]
60         self.dbs.append(self.sam_path)
61
62     # Parse out the comments above each record that ldbdump produces
63     # containing pack format version and KV level key for each record.
64     # Return all GUID keys and DN keys (without @attrs), and the set
65     # of all unique pack formats.
66     def ldbdump_keys_pack_formats(self):
67         # Get all comments from all partition dbs
68         comments = []
69         for db in self.dbs:
70             dump = check_output(["bin/ldbdump", "-i", db])
71             dump = dump.decode("utf-8")
72             dump = dump.split("\n")
73             comments += [s for s in dump if s.startswith("#")]
74
75         guid_key_tag = "# key: GUID="
76         guid_keys = {c[len(guid_key_tag):] for c in comments
77                      if c.startswith(guid_key_tag)}
78
79         dn_key_tag = "# key: DN="
80         dn_keys = {c[len(dn_key_tag):] for c in comments
81                    if c.startswith(dn_key_tag)}
82
83         # Ignore @ attributes, they are always DN keyed
84         dn_keys_no_at_attrs = {d for d in dn_keys if not d.startswith("@")}
85
86         pack_format_tag = "# pack format: "
87         pack_formats = {c[len(pack_format_tag):] for c in comments
88                         if c.startswith(pack_format_tag)}
89         pack_formats = [int(s, 16) for s in pack_formats]
90
91         return dn_keys_no_at_attrs, guid_keys, pack_formats
92
93     # Get a set of all distinct types in @ATTRIBUTES
94     def attribute_types(self):
95         at_attributes = self.ldb.search(base="@ATTRIBUTES",
96                                           scope=ldb.SCOPE_BASE,
97                                           attrs=["*"])
98         self.assertEqual(len(at_attributes), 1)
99         keys = at_attributes[0].keys()
100         attribute_types = {str(at_attributes[0].get(k)) for k in keys}
101
102         return attribute_types
103
104 class DowngradeTestTDB(DowngradeTestBase):
105     backend = 'tdb'
106
107     # Check that running sambadowngradedatabase with a TDB backend:
108     # * Replaces all GUID keys with DN keys
109     # * Removes ORDERED_INTEGER from @ATTRIBUTES
110     # * Repacks database with pack format version 1
111     def test_downgrade_database(self):
112         type_prefix = "LDB_SYNTAX_"
113         ordered_int_type = ldb.SYNTAX_ORDERED_INTEGER[len(type_prefix):]
114
115         dn_keys, guid_keys, pack_formats = self.ldbdump_keys_pack_formats()
116         self.assertGreater(len(guid_keys), 20)
117         self.assertEqual(len(dn_keys), 0)
118         self.assertTrue(ordered_int_type in self.attribute_types())
119         self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2])
120
121         num_guid_keys_before_downgrade = len(guid_keys)
122
123         self.check_run("%s -H %s" % (COMMAND, self.sam_path),
124                        msg="Running sambadowngradedatabase")
125
126         dn_keys, guid_keys, pack_formats = self.ldbdump_keys_pack_formats()
127         self.assertEqual(len(guid_keys), 0)
128         self.assertEqual(len(dn_keys), num_guid_keys_before_downgrade)
129         self.assertTrue(ordered_int_type not in self.attribute_types())
130         self.assertEqual(pack_formats, [ldb.PACKING_FORMAT])
131
132 class DowngradeTestMDB(DowngradeTestBase):
133     backend = 'mdb'
134
135     # Check that running sambadowngradedatabase with a TDB backend:
136     # * Does NOT replace GUID keys with DN keys
137     # * Removes ORDERED_INTEGER from @ATTRIBUTES
138     # * Repacks database with pack format version 1
139     def test_undo_guid(self):
140         type_prefix = "LDB_SYNTAX_"
141         ordered_int_type = ldb.SYNTAX_ORDERED_INTEGER[len(type_prefix):]
142
143         dn_keys, guid_keys, pack_formats = self.ldbdump_keys_pack_formats()
144         self.assertGreater(len(guid_keys), 20)
145         self.assertEqual(len(dn_keys), 0)
146         self.assertTrue(ordered_int_type in self.attribute_types())
147         self.assertEqual(pack_formats, [ldb.PACKING_FORMAT_V2])
148
149         num_guid_keys_before_downgrade = len(guid_keys)
150
151         self.check_run("%s -H %s" % (COMMAND, self.sam_path),
152                        msg="Running sambadowngradedatabase")
153
154         dn_keys, guid_keys, pack_formats = self.ldbdump_keys_pack_formats()
155         self.assertEqual(len(guid_keys), num_guid_keys_before_downgrade)
156         self.assertEqual(len(dn_keys), 0)
157         self.assertTrue(ordered_int_type not in self.attribute_types())
158         self.assertEqual(pack_formats, [ldb.PACKING_FORMAT])