From: Ramana Raja Date: Tue, 20 Apr 2021 21:36:07 +0000 (-0400) Subject: mon/FSCommands: add command to rename a file system X-Git-Tag: v17.1.0~1588^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=bd83fac8035bdf445a84420be2da5fd66a835c27;p=ceph.git mon/FSCommands: add command to rename a file system The fs_name of the relevant MDSMap is set to the new name. Also, the application tags of the data pools and the meta data pool of the file system is set to the new name. Fixes: https://tracker.ceph.com/issues/47276 Signed-off-by: Ramana Raja --- diff --git a/PendingReleaseNotes b/PendingReleaseNotes index 156bf1d9bea4..4858af5883a3 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -19,6 +19,12 @@ * The ``device_health_metrics`` pool has been renamed ``.mgr``. It is now used as a common store for all ``ceph-mgr`` modules. +* fs: A file system can be renamed using the `fs rename` command. Any cephx + credentials authorized for the old file system name will need to be + reauthorized to the new file system name. Since the operations of the clients + using these re-authorized IDs may be disrupted, this command requires the + "--yes-i-really-mean-it" flag. Also, mirroring is expected to be disabled + on the file system. * RGW: `radosgw-admin realm delete` is now renamed to `radosgw-admin realm rm`. This is consistent with the help message. diff --git a/doc/cephfs/administration.rst b/doc/cephfs/administration.rst index 9cb8fe9c9eaf..7806eb8d6133 100644 --- a/doc/cephfs/administration.rst +++ b/doc/cephfs/administration.rst @@ -82,6 +82,16 @@ file system. If any files have layouts for the removed data pool, the file data will become unavailable. The default data pool (when creating the file system) cannot be removed. +:: + + fs rename [--yes-i-really-mean-it] + +Rename a Ceph file system. This also changes the application tags on the data +pools and metadata pool of the file system to the new file system name. +The CephX IDs authorized to the old file system name need to be reauthorized +to the new name. Any on-going operations of the clients using these IDs may be +disrupted. Mirroring is expected to be disabled on the file system. + Settings -------- diff --git a/qa/tasks/cephfs/test_admin.py b/qa/tasks/cephfs/test_admin.py index c6036380d87a..0714acb5f1c7 100644 --- a/qa/tasks/cephfs/test_admin.py +++ b/qa/tasks/cephfs/test_admin.py @@ -1,3 +1,4 @@ +import errno import json import uuid from io import StringIO @@ -213,6 +214,170 @@ class TestFsNew(TestAdminCommands): pool_names[i], 'cephfs', keys[i], fs_name) +class TestRenameCommand(TestAdminCommands): + """ + Tests for rename command. + """ + + CLIENTS_REQUIRED = 1 + MDSS_REQUIRED = 2 + + def test_fs_rename(self): + """ + That the file system can be renamed, and the application metadata set on its pools are as expected. + """ + # Renaming the file system breaks this mount as the client uses + # file system specific authorization. The client cannot read + # or write even if the client's cephx ID caps are updated to access + # the new file system name without the client being unmounted and + # re-mounted. + self.mount_a.umount_wait(require_clean=True) + orig_fs_name = self.fs.name + new_fs_name = 'new_cephfs' + client_id = 'test_new_cephfs' + + self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it') + + # authorize a cephx ID access to the renamed file system. + # use the ID to write to the file system. + self.fs.name = new_fs_name + keyring = self.fs.authorize(client_id, ('/', 'rw')) + keyring_path = self.mount_a.client_remote.mktemp(data=keyring) + self.mount_a.remount(client_id=client_id, + client_keyring_path=keyring_path, + cephfs_mntpt='/', + cephfs_name=self.fs.name) + filedata, filename = 'some data on fs', 'file_on_fs' + filepath = os_path_join(self.mount_a.hostfs_mntpt, filename) + self.mount_a.write_file(filepath, filedata) + self.check_pool_application_metadata_key_value( + self.fs.get_data_pool_name(), 'cephfs', 'data', new_fs_name) + self.check_pool_application_metadata_key_value( + self.fs.get_metadata_pool_name(), 'cephfs', 'metadata', new_fs_name) + + # cleanup + self.mount_a.umount_wait() + self.run_cluster_cmd(f'auth rm client.{client_id}') + + def test_fs_rename_idempotency(self): + """ + That the file system rename operation is idempotent. + """ + # Renaming the file system breaks this mount as the client uses + # file system specific authorization. + self.mount_a.umount_wait(require_clean=True) + orig_fs_name = self.fs.name + new_fs_name = 'new_cephfs' + + self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it') + self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it') + + # original file system name does not appear in `fs ls` command + self.assertFalse(self.fs.exists()) + self.fs.name = new_fs_name + self.assertTrue(self.fs.exists()) + + def test_fs_rename_fs_new_fails_with_old_fsname_existing_pools(self): + """ + That after renaming a file system, creating a file system with + old name and existing FS pools fails. + """ + # Renaming the file system breaks this mount as the client uses + # file system specific authorization. + self.mount_a.umount_wait(require_clean=True) + orig_fs_name = self.fs.name + new_fs_name = 'new_cephfs' + data_pool = self.fs.get_data_pool_name() + metadata_pool = self.fs.get_metadata_pool_name() + self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it') + + try: + self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool}") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EINVAL, + "invalid error code on creating a new file system with old " + "name and existing pools.") + else: + self.fail("expected creating new file system with old name and " + "existing pools to fail.") + + try: + self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool} --force") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EEXIST, + "invalid error code on creating a new file system with old " + "name, existing pools and --force flag.") + else: + self.fail("expected creating new file system with old name, " + "existing pools, and --force flag to fail.") + + try: + self.run_cluster_cmd(f"fs new {orig_fs_name} {metadata_pool} {data_pool} " + "--allow-dangerous-metadata-overlay") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EINVAL, + "invalid error code on creating a new file system with old name, " + "existing pools and --allow-dangerous-metadata-overlay flag.") + else: + self.fail("expected creating new file system with old name, " + "existing pools, and --allow-dangerous-metadata-overlay flag to fail.") + + def test_fs_rename_fails_without_yes_i_really_mean_it_flag(self): + """ + That renaming a file system without '--yes-i-really-mean-it' flag fails. + """ + try: + self.run_cluster_cmd(f"fs rename {self.fs.name} new_fs") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EPERM, + "invalid error code on renaming a file system without the " + "'--yes-i-really-mean-it' flag") + else: + self.fail("expected renaming of file system without the " + "'--yes-i-really-mean-it' flag to fail ") + + def test_fs_rename_fails_for_non_existent_fs(self): + """ + That renaming a non-existent file system fails. + """ + try: + self.run_cluster_cmd("fs rename non_existent_fs new_fs --yes-i-really-mean-it") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.ENOENT, "invalid error code on renaming a non-existent fs") + else: + self.fail("expected renaming of a non-existent file system to fail") + + def test_fs_rename_fails_new_name_already_in_use(self): + """ + That renaming a file system fails if the new name refers to an existing file system. + """ + self.fs2 = self.mds_cluster.newfs(name='cephfs2', create=True) + + try: + self.run_cluster_cmd(f"fs rename {self.fs.name} {self.fs2.name} --yes-i-really-mean-it") + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EINVAL, + "invalid error code on renaming to a fs name that is already in use") + else: + self.fail("expected renaming to a new file system name that is already in use to fail.") + + def test_fs_rename_fails_with_mirroring_enabled(self): + """ + That renaming a file system fails if mirroring is enabled on it. + """ + orig_fs_name = self.fs.name + new_fs_name = 'new_cephfs' + + self.run_cluster_cmd(f'fs mirror enable {orig_fs_name}') + try: + self.run_cluster_cmd(f'fs rename {orig_fs_name} {new_fs_name} --yes-i-really-mean-it') + except CommandFailedError as ce: + self.assertEqual(ce.exitstatus, errno.EPERM, "invalid error code on renaming a mirrored file system") + else: + self.fail("expected renaming of a mirrored file system to fail") + self.run_cluster_cmd(f'fs mirror disable {orig_fs_name}') + + class TestRequiredClientFeatures(CephFSTestCase): CLIENTS_REQUIRED = 0 MDSS_REQUIRED = 1 @@ -284,6 +449,7 @@ class TestRequiredClientFeatures(CephFSTestCase): p = self.fs.required_client_features('rm', '1', stderr=StringIO()) self.assertIn("removed feature 'reserved' from required_client_features", p.stderr.getvalue()) + class TestConfigCommands(CephFSTestCase): """ Test that daemons and clients respond to the otherwise rarely-used @@ -597,6 +763,7 @@ class TestFsAuthorize(CapsHelper): return filepaths, filedata, mounts, keyring + class TestAdminCommandIdempotency(CephFSTestCase): """ Tests for administration command idempotency. diff --git a/src/mds/MDSMap.h b/src/mds/MDSMap.h index 31ae3c48cb4f..f6ed07577c60 100644 --- a/src/mds/MDSMap.h +++ b/src/mds/MDSMap.h @@ -205,6 +205,7 @@ public: void clear_flag(int f) { flags &= ~f; } std::string_view get_fs_name() const {return fs_name;} + void set_fs_name(std::string new_fs_name) { fs_name = std::move(new_fs_name); } void set_snaps_allowed() { set_flag(CEPH_MDSMAP_ALLOW_SNAPS); diff --git a/src/mon/FSCommands.cc b/src/mon/FSCommands.cc index 396ac3118b4e..20e1796a4700 100644 --- a/src/mon/FSCommands.cc +++ b/src/mon/FSCommands.cc @@ -972,6 +972,100 @@ class ResetFilesystemHandler : public FileSystemCommandHandler } }; +class RenameFilesystemHandler : public FileSystemCommandHandler +{ + public: + explicit RenameFilesystemHandler(Paxos *paxos) + : FileSystemCommandHandler("fs rename"), m_paxos(paxos) + { + } + + bool batched_propose() override { + return true; + } + + int handle( + Monitor *mon, + FSMap& fsmap, + MonOpRequestRef op, + const cmdmap_t& cmdmap, + std::stringstream &ss) override + { + ceph_assert(m_paxos->is_plugged()); + + string fs_name; + cmd_getval(cmdmap, "fs_name", fs_name); + auto fs = fsmap.get_filesystem(fs_name); + + string new_fs_name; + cmd_getval(cmdmap, "new_fs_name", new_fs_name); + auto new_fs = fsmap.get_filesystem(new_fs_name); + + if (fs == nullptr) { + if (new_fs) { + // make 'fs rename' idempotent + ss << "File system may already have been renamed. Desired file system '" + << new_fs_name << "' exists."; + return 0; + } else { + ss << "File system '" << fs_name << "' does not exist"; + return -ENOENT; + } + } + + if (new_fs) { + ss << "Desired file system name '" << new_fs_name << "' already in use"; + return -EINVAL; + } + + if (fs->mirror_info.mirrored) { + ss << "Mirroring is enabled on file system '"<< fs_name << "'. Disable mirroring on the " + "file system after ensuring it's OK to do so, and then retry to rename."; + return -EPERM; + } + + // Check for confirmation flag + bool sure = false; + cmd_getval(cmdmap, "yes_i_really_mean_it", sure); + if (!sure) { + ss << "this is a potentially disruptive operation, clients' cephx credentials need reauthorized " + "to access the file system and its pools with the new name. " + "Add --yes-i-really-mean-it if you are sure you wish to continue."; + return -EPERM; + } + + if (!mon->osdmon()->is_writeable()) { + // not allowed to write yet, so retry when we can + mon->osdmon()->wait_for_writeable(op, new PaxosService::C_RetryMessage(mon->mdsmon(), op)); + return -EAGAIN; + } + for (const auto p : fs->mds_map.get_data_pools()) { + mon->osdmon()->do_application_enable(p, + pg_pool_t::APPLICATION_NAME_CEPHFS, + "data", new_fs_name, true); + } + + mon->osdmon()->do_application_enable(fs->mds_map.get_metadata_pool(), + pg_pool_t::APPLICATION_NAME_CEPHFS, + "metadata", new_fs_name, true); + mon->osdmon()->propose_pending(); + + auto f = [new_fs_name](auto fs) { + fs->mds_map.set_fs_name(new_fs_name); + }; + fsmap.modify_filesystem(fs->fscid, std::move(f)); + + ss << "File system is renamed. cephx credentials authorized to " + "old file system name need to be reauthorized to new file " + "system name."; + + return 0; + } + +private: + Paxos *m_paxos; +}; + class RemoveDataPoolHandler : public FileSystemCommandHandler { public: @@ -1285,6 +1379,7 @@ FileSystemCommandHandler::load(Paxos *paxos) handlers.push_back(std::make_shared(paxos)); handlers.push_back(std::make_shared()); handlers.push_back(std::make_shared()); + handlers.push_back(std::make_shared(paxos)); handlers.push_back(std::make_shared()); handlers.push_back(std::make_shared >( @@ -1383,8 +1478,9 @@ int FileSystemCommandHandler::is_op_allowed( auto fs = fsmap_copy.get_filesystem(fs_name); if (fs == nullptr) { - /* let "fs rm" handle idempotent case where file system does not exist */ - if (!(get_prefix() == "fs rm" && fsmap.get_filesystem(fs_name) == nullptr)) { + auto prefix = get_prefix(); + /* let "fs rm" and "fs rename" handle idempotent cases where file systems do not exist */ + if (!(prefix == "fs rm" || prefix == "fs rename") && fsmap.get_filesystem(fs_name) == nullptr) { ss << "Filesystem not found: '" << fs_name << "'"; return -ENOENT; } diff --git a/src/mon/MonCommands.h b/src/mon/MonCommands.h index 045565ecda33..705d0492ed57 100644 --- a/src/mon/MonCommands.h +++ b/src/mon/MonCommands.h @@ -280,6 +280,7 @@ COMMAND("versions", * MDS commands (MDSMonitor.cc) */ +#define FS_NAME_GOODCHARS "[A-Za-z0-9-_.]" COMMAND_WITH_FLAG("mds stat", "show MDS status", "mds", "r", FLAG(HIDDEN)) COMMAND("fs dump " "name=epoch,type=CephInt,req=false,range=0", @@ -326,8 +327,8 @@ COMMAND("mds compat rm_incompat " "name=feature,type=CephInt,range=0", "remove incompatible feature", "mds", "rw") COMMAND("fs new " - "name=fs_name,type=CephString,goodchars=[A-Za-z0-9-_.] " - "name=metadata,type=CephString " + "name=fs_name,type=CephString,goodchars=" FS_NAME_GOODCHARS + " name=metadata,type=CephString " "name=data,type=CephString " "name=force,type=CephBool,req=false " "name=allow_dangerous_metadata_overlay,type=CephBool,req=false", @@ -412,6 +413,11 @@ COMMAND("fs mirror peer_remove " "name=fs_name,type=CephString " "name=uuid,type=CephString ", "remove a mirror peer for a ceph filesystem", "mds", "rw") +COMMAND("fs rename " + "name=fs_name,type=CephString " + "name=new_fs_name,type=CephString,goodchars=" FS_NAME_GOODCHARS + " name=yes_i_really_mean_it,type=CephBool,req=false", + "rename a ceph file system", "mds", "rw") /* * Monmap commands