Skip to content

Commit db43ab3

Browse files
authored
Merge pull request ceph#61945 from kotreshhr/snapshot-referent-inodes
mds: snapshot referent inodes feature Reviewed-by: Venky Shankar <[email protected]>
2 parents 7680fe4 + ce3181e commit db43ab3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2799
-388
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
tasks:
2+
- cephfs_test_runner:
3+
modules:
4+
- tasks.cephfs.test_referent

qa/tasks/cephfs/cephfs_test_case.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class CephFSTestCase(CephTestCase):
7171
CLIENTS_REQUIRED = 1
7272
MDSS_REQUIRED = 1
7373
REQUIRE_ONE_CLIENT_REMOTE = False
74+
ALLOW_REFERENT_INODES = False
7475

7576
# Whether to create the default filesystem during setUp
7677
REQUIRE_FILESYSTEM = True
@@ -190,6 +191,10 @@ def setUp(self):
190191
for i in range(0, self.CLIENTS_REQUIRED):
191192
self.mounts[i].mount_wait()
192193

194+
# enable referent inodes
195+
if self.ALLOW_REFERENT_INODES:
196+
self.fs.set_allow_referent_inodes(True)
197+
193198
if self.REQUIRE_BACKUP_FILESYSTEM:
194199
if not self.REQUIRE_FILESYSTEM:
195200
self.skipTest("backup filesystem requires a primary filesystem as well")

qa/tasks/cephfs/filesystem.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,9 @@ def set_allow_standby_replay(self, yes):
665665
def set_allow_new_snaps(self, yes):
666666
self.set_var("allow_new_snaps", yes, '--yes-i-really-mean-it')
667667

668+
def set_allow_referent_inodes(self, yes):
669+
self.set_var("allow_referent_inodes", yes)
670+
668671
def set_bal_rank_mask(self, bal_rank_mask):
669672
self.set_var("bal_rank_mask", bal_rank_mask)
670673

@@ -1319,9 +1322,15 @@ def wait_for_daemons(self, timeout=None, skip_max_mds_check=False, status=None):
13191322

13201323
status = self.status()
13211324

1322-
def dencoder(self, obj_type, obj_blob):
1323-
args = [os.path.join(self._prefix, "ceph-dencoder"), 'type', obj_type, 'import', '-', 'decode', 'dump_json']
1325+
def dencoder(self, obj_type, obj_blob, skip=0, stray_okay=False):
1326+
args = [os.path.join(self._prefix, "ceph-dencoder"), 'type', obj_type]
1327+
if stray_okay:
1328+
args.extend(["stray_okay"])
1329+
if skip != 0 :
1330+
args.extend(["skip", str(skip)])
1331+
args.extend(['import', '-', 'decode', 'dump_json'])
13241332
p = self.mon_manager.controller.run(args=args, stdin=BytesIO(obj_blob), stdout=BytesIO())
1333+
13251334
return p.stdout.getvalue()
13261335

13271336
def rados(self, *args, **kwargs):
@@ -1538,6 +1547,43 @@ def _write_data_xattr(self, ino_no, xattr_name, data, pool=None):
15381547
args = ["setxattr", obj_name, xattr_name, data]
15391548
self.rados(args, pool=pool)
15401549

1550+
def read_meta_inode(self, dir_ino, file_name, pool=None):
1551+
"""
1552+
Get decoded in-memory inode from the metadata pool
1553+
"""
1554+
if pool is None:
1555+
pool = self.get_metadata_pool_name()
1556+
1557+
dirfrag_obj_name = "{0:x}.00000000".format(dir_ino)
1558+
args=["getomapval", dirfrag_obj_name, file_name+"_head", "-"]
1559+
try:
1560+
proc = self.rados(args, pool=pool, stdout=BytesIO())
1561+
except CommandFailedError as e:
1562+
log.error(e.__str__())
1563+
raise ObjectNotFound(dirfrag_obj_name)
1564+
1565+
obj_blob = proc.stdout.getvalue()
1566+
return json.loads(self.dencoder("inode_t<std::allocator>", obj_blob, 25, True).strip())
1567+
1568+
def read_remote_inode(self, ino_no, pool=None):
1569+
"""
1570+
Read the remote_inode xattr from the data pool, return a dict in the
1571+
format given by inodeno_t::dump, which is something like:
1572+
1573+
::
1574+
1575+
rados -p cephfs_data getxattr 100000001f8.00000000 remote_inode > out.bin
1576+
ceph-dencoder type inodeno_t import out.bin decode dump_json
1577+
1578+
{
1579+
"val": 1099511627778
1580+
}
1581+
1582+
:param pool: name of pool to read backtrace from. If omitted, FS must have only
1583+
one data pool and that will be used.
1584+
"""
1585+
return self._read_data_xattr(ino_no, "remote_inode", "inodeno_t", pool)
1586+
15411587
def read_symlink(self, ino_no, pool=None):
15421588
return self._read_data_xattr(ino_no, "symlink", "string_wrapper", pool)
15431589

@@ -1601,6 +1647,15 @@ def _enumerate_data_objects(self, ino, size):
16011647

16021648
return want_objects, exist_objects
16031649

1650+
def list_data_objects (self):
1651+
"""
1652+
Get the list of existing data objects in the data pool
1653+
"""
1654+
existing_objects = self.rados(["ls"], pool=self.get_data_pool_name(), stdout=StringIO()).stdout.getvalue().split("\n")
1655+
1656+
return existing_objects
1657+
1658+
16041659
def data_objects_present(self, ino, size):
16051660
"""
16061661
Check that *all* the expected data objects for an inode are present in the data pool

qa/tasks/cephfs/test_backtrace.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class TestBacktrace(CephFSTestCase):
66
def test_backtrace(self):
77
"""
8-
That the 'parent' 'layout' and 'symlink' xattrs on the head objects of files
9-
are updated correctly.
8+
That the 'parent', 'layout', 'symlink' and 'remote_inode' xattrs on the
9+
head objects of files are updated correctly.
1010
"""
1111

1212
old_data_pool_name = self.fs.get_data_pool_name()
@@ -36,6 +36,33 @@ def test_backtrace(self):
3636
"s" : "./file1",
3737
})
3838

39+
# Disabling referent_inodes fs option should not create referent inode and
40+
# and therefore no 'remote_inode' xattr on hardlink creation
41+
self.fs.set_allow_referent_inodes(False);
42+
self.mount_a.run_shell(["mkdir", "hardlink_dir0"])
43+
self.mount_a.run_shell(["touch", "hardlink_dir0/file1"])
44+
self.mount_a.run_shell(["ln", "hardlink_dir0/file1", "hardlink_dir0/hl_file1"])
45+
self.fs.mds_asok(["flush", "journal"])
46+
47+
file1_ino = self.mount_a.path_to_ino("hardlink_dir0/file1", follow_symlinks=False)
48+
file1_inode_dump = self.fs.mds_asok(['dump', 'inode', hex(file1_ino)])
49+
self.assertListEqual(file1_inode_dump['referent_inodes'], [])
50+
51+
# Enabling referent_inodes fs option should store 'remote_inode' xattr
52+
# on hardlink creation
53+
self.fs.set_allow_referent_inodes(True);
54+
self.mount_a.run_shell(["mkdir", "hardlink_dir"])
55+
self.mount_a.run_shell(["touch", "hardlink_dir/file1"])
56+
self.mount_a.run_shell(["ln", "hardlink_dir/file1", "hardlink_dir/hl_file1"])
57+
self.fs.mds_asok(["flush", "journal"])
58+
59+
file1_ino = self.mount_a.path_to_ino("hardlink_dir/file1", follow_symlinks=False)
60+
file1_inode_dump = self.fs.mds_asok(['dump', 'inode', hex(file1_ino)])
61+
referent_ino = file1_inode_dump["referent_inodes"][0]
62+
63+
remote_inode_xattr = self.fs.read_remote_inode(referent_ino)
64+
self.assertEqual(remote_inode_xattr['val'], file1_ino)
65+
3966
# Create a file for subsequent checks
4067
self.mount_a.run_shell(["mkdir", "parent_a"])
4168
self.mount_a.run_shell(["touch", "parent_a/alpha"])

qa/tasks/cephfs/test_data_scan.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,44 @@ def validate(self):
164164
self.assert_equal(target, "symdir/onemegs")
165165
return self._errors
166166

167+
class HardlinkWorkload(Workload):
168+
"""
169+
Hardlink file, check that it gets recovered as hardlink
170+
with referent inode pointing to the remote inode
171+
"""
172+
def write(self):
173+
self._filesystem.set_allow_referent_inodes(True);
174+
self._mount.run_shell(["mkdir", "hardlink_dir"])
175+
self._mount.write_n_mb("hardlink_dir/file1", 1)
176+
self._mount.run_shell(["ln", "hardlink_dir/file1", "hardlink_dir/hl_file1"])
177+
self._mount.run_shell(["ln", "hardlink_dir/file1", "hardlink_dir/hl_file2"])
178+
self._filesystem.set_allow_referent_inodes(False);
179+
180+
def validate(self):
181+
self._mount.run_shell(["sudo", "ls", "hardlink_dir"], omit_sudo=False)
182+
st_file1 = self._mount.lstat("hardlink_dir/file1")
183+
st_hl_file1 = self._mount.lstat("hardlink_dir/hl_file1")
184+
st_hl_file2 = self._mount.lstat("hardlink_dir/hl_file2")
185+
# link count check
186+
self.assert_equal(3, st_file1['st_nlink'])
187+
self.assert_equal(3, st_hl_file1['st_nlink'])
188+
self.assert_equal(3, st_hl_file2['st_nlink'])
189+
# ino number check
190+
self.assert_equal(st_file1['st_ino'], st_hl_file1['st_ino'])
191+
self.assert_equal(st_file1['st_ino'], st_hl_file2['st_ino'])
192+
# reverse link - referent inode list
193+
file1_ino = self._mount.path_to_ino("hardlink_dir/file1", follow_symlinks=False)
194+
file1_inode_dump = self._filesystem.mds_asok(['dump', 'inode', hex(file1_ino)])
195+
self.assert_equal(len(file1_inode_dump['referent_inodes']), 2)
196+
# remote link
197+
referent1_ino = file1_inode_dump['referent_inodes'][0]
198+
referent1_inode_dump = self._filesystem.mds_asok(['dump', 'inode', hex(referent1_ino)])
199+
self.assert_equal(file1_ino, referent1_inode_dump['remote_ino'])
200+
referent2_ino = file1_inode_dump['referent_inodes'][1]
201+
referent2_inode_dump = self._filesystem.mds_asok(['dump', 'inode', hex(referent2_ino)])
202+
self.assert_equal(file1_ino, referent2_inode_dump['remote_ino'])
203+
return self._errors
204+
167205
class NestedDirWorkload(Workload):
168206
"""
169207
Nested directories, one is lost.
@@ -540,6 +578,9 @@ def test_rebuild_simple(self):
540578
def test_rebuild_symlink(self):
541579
self._rebuild_metadata(SymlinkWorkload(self.fs, self.mount_a))
542580

581+
def test_rebuild_hardlink(self):
582+
self._rebuild_metadata(HardlinkWorkload(self.fs, self.mount_a))
583+
543584
def test_rebuild_nested(self):
544585
self._rebuild_metadata(NestedDirWorkload(self.fs, self.mount_a))
545586

0 commit comments

Comments
 (0)