Skip to content

Commit 571a3fc

Browse files
Merge pull request ceph#55320 from rishabh-d-dave/mdscaps-update-issues
cephfs,mon: fix bugs related to updating MDS caps Reviewed-by: Venky Shankar <[email protected]>
2 parents 4bf1cd0 + f6bb579 commit 571a3fc

File tree

5 files changed

+188
-23
lines changed

5 files changed

+188
-23
lines changed

qa/tasks/cephfs/caps_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def run_mds_cap_tests(self, perm, mntpt=None):
249249
Run test for read perm and, for write perm, run positive test if it
250250
is present and run negative test if not.
251251
"""
252-
if mntpt:
252+
if mntpt and mntpt != '/':
253253
# beacaue we want to value of mntpt from test_set.path along with
254254
# slash that precedes it.
255255
mntpt = '/' + mntpt if mntpt[0] != '/' else mntpt

qa/tasks/cephfs/test_admin.py

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,7 +1519,98 @@ def test_multiple_path_rw(self):
15191519

15201520
self._remount_and_run_tests(FS_AUTH_CAPS, keyring)
15211521

1522+
def test_when_MDS_caps_needs_update_but_others_dont(self):
1523+
'''
1524+
Test that the command "ceph fs authorize" successfully updates MDS
1525+
caps even when MON and OSD caps don't need an update.
1526+
1527+
Tests: https://tracker.ceph.com/issues/64182
1528+
1529+
In this test we run -
1530+
1531+
ceph fs authorize cephfs1 client.x / rw
1532+
ceph fs authorize cephfs2 client.x / rw
1533+
ceph fs authorize cephfs2 client.x /dir1 rw
1534+
1535+
The first command will create the keyring by adding the MDS cap for
1536+
root path & MON and OSD caps with name of the FS name (say "cephfs1").
1537+
Second command will update the all of client's caps -- MON, OSD and
1538+
MDS caps to add caps for 2nd CephFS. The third command doesn't need
1539+
to add MON and OSD caps since cap for "cephfs2" has been granted
1540+
already. Thus, third command only need to update the MDS cap, thus
1541+
testing the bug under consideration here.
1542+
'''
1543+
PERM = 'rw'
1544+
DIR = 'dir1'
1545+
1546+
self.fs2 = self.mds_cluster.newfs(name='cephfs2', create=True)
1547+
self.mount_b.remount(cephfs_name=self.fs2.name)
1548+
self.mount_b.run_shell(f'mkdir {DIR}')
1549+
self.captesters = (CapTester(self.mount_a, '/'),
1550+
CapTester(self.mount_b, '/'),
1551+
CapTester(self.mount_b, f'/{DIR}'))
1552+
1553+
FS_AUTH_CAPS = (('/', PERM), ('/', PERM), (DIR, PERM))
1554+
keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS[0])
1555+
keyring = self.fs2.authorize(self.client_id, FS_AUTH_CAPS[1])
1556+
1557+
# if the following block of code pass it implies that
1558+
# https://tracker.ceph.com/issues/64182 has been fixed
1559+
keyring = self.fs2.authorize(self.client_id, FS_AUTH_CAPS[2])
1560+
mdscaps = ('caps mds = "'
1561+
f'allow {PERM} fsname={self.fs.name}, '
1562+
f'allow {PERM} fsname={self.fs2.name}, '
1563+
f'allow {PERM} fsname={self.fs2.name} path={DIR}')
1564+
self.assertIn(mdscaps, keyring)
1565+
1566+
self._remount_and_run_tests_for_cap(
1567+
FS_AUTH_CAPS[0], self.captesters[0], self.fs.name, self.mount_a,
1568+
keyring)
1569+
self._remount_and_run_tests_for_cap(
1570+
FS_AUTH_CAPS[1], self.captesters[1], self.fs2.name, self.mount_b,
1571+
keyring)
1572+
self._remount_and_run_tests_for_cap(
1573+
FS_AUTH_CAPS[2], self.captesters[2], self.fs2.name, self.mount_b,
1574+
keyring)
1575+
1576+
def test_adding_multiple_caps(self):
1577+
'''
1578+
Test that the command "ceph fs authorize" is successful in updating
1579+
the entity's caps when multiple caps are passed to it in one go.
1580+
1581+
Tests: https://tracker.ceph.com/issues/64127
1582+
Tests: https://tracker.ceph.com/issues/64417
1583+
'''
1584+
DIR = 'dir1'
1585+
self.mount_a.run_shell(f'mkdir {DIR}')
1586+
self.captesters = (CapTester(self.mount_a, '/'),
1587+
CapTester(self.mount_a, f'/{DIR}'))
1588+
self.fs.authorize(self.client_id, ('/', 'rw'))
1589+
1590+
FS_AUTH_CAPS = ('/', 'rw', 'root_squash'), (f'/{DIR}', 'rw' )
1591+
# The fact that following line passes means
1592+
# https://tracker.ceph.com/issues/64127 has been fixed
1593+
keyring = self.fs.authorize(self.client_id, FS_AUTH_CAPS)
1594+
1595+
# The fact that following lines passes means
1596+
# https://tracker.ceph.com/issues/64417 has been fixed.
1597+
mdscaps = (f'allow rw fsname={self.fs.name} root_squash, '
1598+
f'allow rw fsname={self.fs.name} path={DIR}')
1599+
self.assertIn(mdscaps, keyring)
1600+
1601+
self._remount_and_run_tests(FS_AUTH_CAPS, keyring)
1602+
15221603
def _remount_and_run_tests(self, fs_auth_caps, keyring):
1604+
'''
1605+
This method is specifically designed to meet needs of most of the
1606+
test case in this class. Following are assumptions made here -
1607+
1608+
1. CephFS to be mounted is self.fs
1609+
2. Mount object to be used is self.mount_a
1610+
3. Keyring file will be created on the host specified in self.mount_a.
1611+
4. CephFS dir that will serve as root is PATH component of particular
1612+
cap from FS_AUTH_CAPS.
1613+
'''
15231614
for i, c in enumerate(fs_auth_caps):
15241615
self.assertIn(i, (0, 1))
15251616
PATH = c[0]
@@ -1529,18 +1620,30 @@ def _remount_and_run_tests(self, fs_auth_caps, keyring):
15291620
self.captesters[i].run_cap_tests(self.fs, self.client_id, PERM,
15301621
PATH)
15311622

1532-
def tearDown(self):
1533-
self.mount_a.umount_wait()
1534-
self.run_ceph_cmd(f'auth rm {self.client_name}')
1535-
1536-
super(type(self), self).tearDown()
1537-
15381623
def _remount(self, keyring, path='/'):
15391624
keyring_path = self.mount_a.client_remote.mktemp(data=keyring)
15401625
self.mount_a.remount(client_id=self.client_id,
15411626
client_keyring_path=keyring_path,
15421627
cephfs_mntpt=path)
15431628

1629+
def _remount_and_run_tests_for_cap(self, cap, captester, fsname, mount,
1630+
keyring):
1631+
PATH = cap[0]
1632+
PERM = cap[1]
1633+
1634+
cephfs_mntpt = os_path_join('/', PATH)
1635+
keyring_path = mount.client_remote.mktemp(data=keyring)
1636+
mount.remount(client_id=self.client_id, cephfs_mntpt=cephfs_mntpt,
1637+
cephfs_name=fsname, client_keyring_path=keyring_path)
1638+
1639+
captester.run_cap_tests(self.fs, self.client_id, PERM, PATH)
1640+
1641+
def tearDown(self):
1642+
self.mount_a.umount_wait()
1643+
self.run_ceph_cmd(f'auth rm {self.client_name}')
1644+
1645+
super(type(self), self).tearDown()
1646+
15441647

15451648
class TestFsAuthorizeUpdate(CephFSTestCase):
15461649
client_id = 'testuser'

src/mds/MDSAuthCaps.cc

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -382,25 +382,46 @@ bool MDSAuthCaps::parse(string_view str, ostream *err)
382382
}
383383
}
384384

385-
bool MDSAuthCaps::merge(MDSAuthCaps newcap)
385+
/* Check if the "cap grant" is already present in this cap object. If it is,
386+
* return false. If not, add it and return true.
387+
*
388+
* ng = new grant, new mds cap grant.
389+
*/
390+
bool MDSAuthCaps::merge_one_cap_grant(MDSCapGrant ng)
386391
{
387-
ceph_assert(newcap.grants.size() == 1);
388-
auto ng = newcap.grants[0];
389-
392+
// check if "ng" is already present in this cap object.
390393
for (auto& g : grants) {
391394
if (g.match.fs_name == ng.match.fs_name && g.match.path == ng.match.path) {
392-
if (g.spec.get_caps() == ng.spec.get_caps()) {
393-
// no update required. maintaining idempotency.
395+
if (g.spec.get_caps() == ng.spec.get_caps() &&
396+
g.match.root_squash == ng.match.root_squash) {
397+
// Since all components of MDS caps (fsname, path, perm/spec and
398+
// root_squash) matched, it means cap same as "ng" is present in MDS
399+
// cap grant list. No need to look further in MDS cap grant list.
400+
// No update is required. Maintain idempotency.
394401
return false;
395-
} else {
396-
// cap for given fs name is present, let's update it.
402+
}
403+
404+
// fsname and path match but perm/spec is different. update the cap
405+
// with new perm/spec.
406+
if (g.spec.get_caps() != ng.spec.get_caps()) {
397407
g.spec.set_caps(ng.spec.get_caps());
398-
return true;
399408
}
409+
410+
// fsname and path match but value of root_squash is different. update
411+
// its value.
412+
if (g.match.root_squash != ng.match.root_squash) {
413+
g.match.root_squash = ng.match.root_squash;
414+
}
415+
416+
// Since fsname and path matched and either perm/spec or root_squash
417+
// or both has been updated, cap from "ng" has been incorporated
418+
// into this cap grant list. Time to return.
419+
return true;
400420
}
401421
}
402422

403-
// cap for given fs name and/or path is absent, let's add a new cap for it.
423+
// Since a cap grant like "ng" is absent in this cap object's grant list,
424+
// add "ng" to the cap grant list.
404425
grants.push_back(MDSCapGrant(
405426
MDSCapSpec(ng.spec.get_caps()),
406427
MDSCapMatch(ng.match.fs_name, ng.match.path, ng.match.root_squash),
@@ -409,6 +430,29 @@ bool MDSAuthCaps::merge(MDSAuthCaps newcap)
409430
return true;
410431
}
411432

433+
/* User can pass one or MDS caps that it wishes to add to entity's keyring.
434+
* Merge all of these caps one by one. Return value indicates whether or not
435+
* AuthMonitor must update the entity's keyring.
436+
*
437+
* If all caps do not merge (that is, underlying helper method returns false
438+
* after attempting merge), no update is required. Return false so that
439+
* AuthMonitor doesn't run the update procedure for caps.
440+
*
441+
* If even one cap is merged (that is, underlying method returns true even
442+
* once), an update to the entity's keyring is required. Return true so that
443+
* AuthMonitor runs the update procedure.
444+
*/
445+
bool MDSAuthCaps::merge(MDSAuthCaps newcaps)
446+
{
447+
bool were_caps_merged = false;
448+
449+
for (auto& ng : newcaps.grants) {
450+
were_caps_merged |= merge_one_cap_grant(ng);
451+
}
452+
453+
return were_caps_merged;
454+
}
455+
412456
string MDSCapMatch::to_string()
413457
{
414458
string str = "";

src/mds/MDSAuthCaps.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ class MDSAuthCaps
259259

260260
void set_allow_all();
261261
bool parse(std::string_view str, std::ostream *err);
262-
bool merge(MDSAuthCaps newcap);
262+
bool merge_one_cap_grant(MDSCapGrant ng);
263+
bool merge(MDSAuthCaps newcaps);
263264

264265
bool allow_all() const;
265266
bool is_capable(std::string_view inode_path,

src/mon/AuthMonitor.cc

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,9 @@ AuthMonitor::caps_update AuthMonitor::_gen_wanted_caps(EntityAuth& e_auth,
18671867
map<string, string>& newcaps, ostream& out)
18681868
{
18691869
caps_update is_caps_update_reqd = CAPS_UPDATE_NOT_REQD;
1870+
caps_update is_caps_update_reqd_mon = CAPS_UPDATE_NOT_REQD;
1871+
caps_update is_caps_update_reqd_osd = CAPS_UPDATE_NOT_REQD;
1872+
caps_update is_caps_update_reqd_mds = CAPS_UPDATE_NOT_REQD;
18701873

18711874
if (e_auth.caps.empty()) {
18721875
return CAPS_UPDATE_REQD;
@@ -1888,15 +1891,29 @@ AuthMonitor::caps_update AuthMonitor::_gen_wanted_caps(EntityAuth& e_auth,
18881891
}
18891892

18901893
if (cap_entity == "mon") {
1891-
is_caps_update_reqd = _merge_caps<MonCap>(cap_entity, new_cap_str,
1894+
is_caps_update_reqd_mon = _merge_caps<MonCap>(cap_entity, new_cap_str,
18921895
cur_cap_str, newcaps, out);
18931896
} else if (cap_entity == "osd") {
1894-
is_caps_update_reqd = _merge_caps<OSDCap>(cap_entity, new_cap_str,
1897+
is_caps_update_reqd_osd = _merge_caps<OSDCap>(cap_entity, new_cap_str,
18951898
cur_cap_str, newcaps, out);
18961899
} else if (cap_entity == "mds") {
1897-
is_caps_update_reqd = _merge_caps<MDSAuthCaps>(cap_entity, new_cap_str,
1898-
cur_cap_str, newcaps, out);
1899-
}
1900+
is_caps_update_reqd_mds = _merge_caps<MDSAuthCaps>(cap_entity,
1901+
new_cap_str, cur_cap_str, newcaps, out);
1902+
}
1903+
}
1904+
1905+
// if any one of MON, OSD or MDS caps failed to parse, it is pointless
1906+
// to run the update procedure.
1907+
if (is_caps_update_reqd_mon == CAPS_PARSING_ERR ||
1908+
is_caps_update_reqd_osd == CAPS_PARSING_ERR ||
1909+
is_caps_update_reqd_mds == CAPS_PARSING_ERR) {
1910+
is_caps_update_reqd = CAPS_PARSING_ERR;
1911+
// even if any one of MON, OSD or MDS caps needs an update, the update
1912+
// procedure needs to be executed.
1913+
} else if (is_caps_update_reqd_mon == CAPS_UPDATE_REQD ||
1914+
is_caps_update_reqd_osd == CAPS_UPDATE_REQD ||
1915+
is_caps_update_reqd_mds == CAPS_UPDATE_REQD) {
1916+
is_caps_update_reqd = CAPS_UPDATE_REQD;
19001917
}
19011918

19021919
return is_caps_update_reqd;

0 commit comments

Comments
 (0)