]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mon/FSCommands: add command to rename a file system 41385/head
authorRamana Raja <rraja@redhat.com>
Tue, 20 Apr 2021 21:36:07 +0000 (17:36 -0400)
committerRamana Raja <rraja@redhat.com>
Thu, 17 Jun 2021 17:59:08 +0000 (13:59 -0400)
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 <rraja@redhat.com>
PendingReleaseNotes
doc/cephfs/administration.rst
qa/tasks/cephfs/test_admin.py
src/mds/MDSMap.h
src/mon/FSCommands.cc
src/mon/MonCommands.h

index 156bf1d9bea4d8e6953413b88e97d6a364ffeda4..4858af5883a3d63a8c829bbc6d7265ac81f61bfc 100644 (file)
 
 * 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.
index 9cb8fe9c9eaf7048647b40d189e125ebe9e6ab4c..7806eb8d6133186461a9f907c4f59c99ef89ca8c 100644 (file)
@@ -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 <file system name> <new file system name> [--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
 --------
index c6036380d87a0cfacb4facec5f8bdc96decd21b6..0714acb5f1c7f32f01e8b42343716cf5f34f96e6 100644 (file)
@@ -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.
index 31ae3c48cb4fbd76308c7a4153bb9060c4570259..f6ed07577c602b758a81455c1707d98cf67719d3 100644 (file)
@@ -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);
index 396ac3118b4ea59462dc0e187acdb848a8b91658..20e1796a470087ffbdf6bcf08fc4f1810bc07c18 100644 (file)
@@ -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<FsNewHandler>(paxos));
   handlers.push_back(std::make_shared<RemoveFilesystemHandler>());
   handlers.push_back(std::make_shared<ResetFilesystemHandler>());
+  handlers.push_back(std::make_shared<RenameFilesystemHandler>(paxos));
 
   handlers.push_back(std::make_shared<SetDefaultHandler>());
   handlers.push_back(std::make_shared<AliasHandler<SetDefaultHandler> >(
@@ -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;
       }
index 045565ecda33e0754d043f1baa205098e5980f30..705d0492ed573b039bd5953b1f03784f310448d5 100644 (file)
@@ -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