]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/cephfs/test_fragment.py
import 15.2.5
[ceph.git] / ceph / qa / tasks / cephfs / test_fragment.py
CommitLineData
7c673cae
FG
1
2
3from tasks.cephfs.cephfs_test_case import CephFSTestCase
4from teuthology.orchestra import run
5
6import logging
7log = logging.getLogger(__name__)
8
9
10class TestFragmentation(CephFSTestCase):
11 CLIENTS_REQUIRED = 1
12 MDSS_REQUIRED = 1
13
14 def get_splits(self):
15 return self.fs.mds_asok(['perf', 'dump', 'mds'])['mds']['dir_split']
16
17 def get_merges(self):
18 return self.fs.mds_asok(['perf', 'dump', 'mds'])['mds']['dir_merge']
19
20 def get_dir_ino(self, path):
21 dir_cache = self.fs.read_cache(path, 0)
22 dir_ino = None
23 dir_inono = self.mount_a.path_to_ino(path.strip("/"))
24 for ino in dir_cache:
25 if ino['ino'] == dir_inono:
26 dir_ino = ino
27 break
28 self.assertIsNotNone(dir_ino)
29 return dir_ino
30
31 def _configure(self, **kwargs):
32 """
33 Apply kwargs as MDS configuration settings, enable dirfrags
34 and restart the MDSs.
35 """
7c673cae
FG
36
37 for k, v in kwargs.items():
38 self.ceph_cluster.set_ceph_conf("mds", k, v.__str__())
39
7c673cae
FG
40 self.mds_cluster.mds_fail_restart()
41 self.fs.wait_for_daemons()
42
43 def test_oversize(self):
44 """
45 That a directory is split when it becomes too large.
46 """
47
48 split_size = 20
49 merge_size = 5
50
51 self._configure(
52 mds_bal_split_size=split_size,
53 mds_bal_merge_size=merge_size,
54 mds_bal_split_bits=1
55 )
56
57 self.assertEqual(self.get_splits(), 0)
58
59 self.mount_a.create_n_files("splitdir/file", split_size + 1)
60
61 self.wait_until_true(
62 lambda: self.get_splits() == 1,
63 timeout=30
64 )
65
66 frags = self.get_dir_ino("/splitdir")['dirfrags']
67 self.assertEqual(len(frags), 2)
d2e6a577
FG
68 self.assertEqual(frags[0]['dirfrag'], "0x10000000000.0*")
69 self.assertEqual(frags[1]['dirfrag'], "0x10000000000.1*")
7c673cae
FG
70 self.assertEqual(
71 sum([len(f['dentries']) for f in frags]),
72 split_size + 1
73 )
74
75 self.assertEqual(self.get_merges(), 0)
76
77 self.mount_a.run_shell(["rm", "-f", run.Raw("splitdir/file*")])
78
79 self.wait_until_true(
80 lambda: self.get_merges() == 1,
81 timeout=30
82 )
83
84 self.assertEqual(len(self.get_dir_ino("/splitdir")["dirfrags"]), 1)
85
86 def test_rapid_creation(self):
87 """
88 That the fast-splitting limit of 1.5x normal limit is
89 applied when creating dentries quickly.
90 """
91
92 split_size = 100
93 merge_size = 1
94
95 self._configure(
96 mds_bal_split_size=split_size,
97 mds_bal_merge_size=merge_size,
98 mds_bal_split_bits=3,
31f18b77 99 mds_bal_fragment_size_max=int(split_size * 1.5 + 2)
7c673cae
FG
100 )
101
102 # We test this only at a single split level. If a client was sending
103 # IO so fast that it hit a second split before the first split
104 # was complete, it could violate mds_bal_fragment_size_max -- there
105 # is a window where the child dirfrags of a split are unfrozen
106 # (so they can grow), but still have STATE_FRAGMENTING (so they
107 # can't be split).
108
109 # By writing 4x the split size when the split bits are set
110 # to 3 (i.e. 4-ways), I am reasonably sure to see precisely
111 # one split. The test is to check whether that split
112 # happens soon enough that the client doesn't exceed
113 # 2x the split_size (the "immediate" split mode should
114 # kick in at 1.5x the split size).
115
116 self.assertEqual(self.get_splits(), 0)
117 self.mount_a.create_n_files("splitdir/file", split_size * 4)
118 self.wait_until_equal(
119 self.get_splits,
120 1,
121 reject_fn=lambda s: s > 1,
122 timeout=30
123 )
124
125 def test_deep_split(self):
126 """
127 That when the directory grows many times larger than split size,
128 the fragments get split again.
129 """
130
131 split_size = 100
132 merge_size = 1 # i.e. don't merge frag unless its empty
133 split_bits = 1
134
135 branch_factor = 2**split_bits
136
137 # Arbitrary: how many levels shall we try fragmenting before
138 # ending the test?
139 max_depth = 5
140
141 self._configure(
142 mds_bal_split_size=split_size,
143 mds_bal_merge_size=merge_size,
144 mds_bal_split_bits=split_bits
145 )
146
147 # Each iteration we will create another level of fragments. The
148 # placement of dentries into fragments is by hashes (i.e. pseudo
149 # random), so we rely on statistics to get the behaviour that
150 # by writing about 1.5x as many dentries as the split_size times
151 # the number of frags, we will get them all to exceed their
152 # split size and trigger a split.
153 depth = 0
154 files_written = 0
155 splits_expected = 0
156 while depth < max_depth:
157 log.info("Writing files for depth {0}".format(depth))
158 target_files = branch_factor**depth * int(split_size * 1.5)
159 create_files = target_files - files_written
160
161 self.ceph_cluster.mon_manager.raw_cluster_cmd("log",
162 "{0} Writing {1} files (depth={2})".format(
163 self.__class__.__name__, create_files, depth
164 ))
165 self.mount_a.create_n_files("splitdir/file_{0}".format(depth),
166 create_files)
167 self.ceph_cluster.mon_manager.raw_cluster_cmd("log",
168 "{0} Done".format(self.__class__.__name__))
169
170 files_written += create_files
171 log.info("Now have {0} files".format(files_written))
172
173 splits_expected += branch_factor**depth
174 log.info("Waiting to see {0} splits".format(splits_expected))
175 try:
176 self.wait_until_equal(
177 self.get_splits,
178 splits_expected,
179 timeout=30,
180 reject_fn=lambda x: x > splits_expected
181 )
182
183 frags = self.get_dir_ino("/splitdir")['dirfrags']
184 self.assertEqual(len(frags), branch_factor**(depth+1))
185 self.assertEqual(
186 sum([len(f['dentries']) for f in frags]),
187 target_files
188 )
189 except:
190 # On failures, log what fragmentation we actually ended
191 # up with. This block is just for logging, at the end
192 # we raise the exception again.
193 frags = self.get_dir_ino("/splitdir")['dirfrags']
194 log.info("depth={0} splits_expected={1} files_written={2}".format(
195 depth, splits_expected, files_written
196 ))
197 log.info("Dirfrags:")
198 for f in frags:
199 log.info("{0}: {1}".format(
200 f['dirfrag'], len(f['dentries'])
201 ))
202 raise
203
204 depth += 1
205
206 # Remember the inode number because we will be checking for
207 # objects later.
208 dir_inode_no = self.mount_a.path_to_ino("splitdir")
209
210 self.mount_a.run_shell(["rm", "-rf", "splitdir/"])
211 self.mount_a.umount_wait()
212
213 self.fs.mds_asok(['flush', 'journal'])
214
215 # Wait for all strays to purge
216 self.wait_until_equal(
217 lambda: self.fs.mds_asok(['perf', 'dump', 'mds_cache']
218 )['mds_cache']['num_strays'],
219 0,
220 timeout=1200
221 )
222 # Check that the metadata pool objects for all the myriad
223 # child fragments are gone
224 metadata_objs = self.fs.rados(["ls"])
225 frag_objs = []
226 for o in metadata_objs:
227 if o.startswith("{0:x}.".format(dir_inode_no)):
228 frag_objs.append(o)
229 self.assertListEqual(frag_objs, [])