+import errno
import json
import uuid
from io import StringIO
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
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
return filepaths, filedata, mounts, keyring
+
class TestAdminCommandIdempotency(CephFSTestCase):
"""
Tests for administration command idempotency.
}
};
+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:
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> >(
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;
}
* 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",
"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",
"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