mirror of
https://github.com/ceph/ceph
synced 2025-01-03 01:22:53 +00:00
Merge pull request #42549 from ajarr/wip-add-volume-rename
mgr/volumes: Add `fs volume rename` command Reviewed-by: Venky Shankar <vshankar@redhat.com>
This commit is contained in:
commit
b7af2a94a4
@ -44,6 +44,13 @@
|
||||
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.
|
||||
|
||||
* fs: A FS volume can be renamed using the `fs volume rename` command. Any cephx
|
||||
credentials authorized for the old volume name will need to be reauthorized to
|
||||
the new volume 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.
|
||||
|
||||
* MDS upgrades no longer require stopping all standby MDS daemons before
|
||||
upgrading the sole active MDS for a file system.
|
||||
|
||||
|
@ -79,6 +79,24 @@ List volumes using::
|
||||
|
||||
$ ceph fs volume ls
|
||||
|
||||
Rename a volume using::
|
||||
|
||||
$ ceph fs volume rename <vol_name> <new_vol_name> [--yes-i-really-mean-it]
|
||||
|
||||
Renaming a volume can be an expensive operation. It does the following:
|
||||
|
||||
- renames the orchestrator managed MDS service to match the <new_vol_name>.
|
||||
This involves launching a MDS service with <new_vol_name> and bringing down
|
||||
the MDS service with <vol_name>.
|
||||
- renames the file system matching <vol_name> to <new_vol_name>
|
||||
- changes the application tags on the data and metadata pools of the file system
|
||||
to <new_vol_name>
|
||||
- renames the metadata and data pools of the file system.
|
||||
|
||||
The CephX IDs authorized to <vol_name> need to be reauthorized to <new_vol_name>. Any
|
||||
on-going operations of the clients using these IDs may be disrupted. Mirroring is
|
||||
expected to be disabled on the volume.
|
||||
|
||||
FS Subvolume groups
|
||||
-------------------
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
.qa/distros/supported/centos_latest.yaml
|
@ -0,0 +1 @@
|
||||
.qa/distros/single-container-host.yaml
|
0
qa/suites/fs/cephadm/renamevolume/%
Normal file
0
qa/suites/fs/cephadm/renamevolume/%
Normal file
1
qa/suites/fs/cephadm/renamevolume/.qa
Symbolic link
1
qa/suites/fs/cephadm/renamevolume/.qa
Symbolic link
@ -0,0 +1 @@
|
||||
../.qa
|
38
qa/suites/fs/cephadm/renamevolume/0-start.yaml
Normal file
38
qa/suites/fs/cephadm/renamevolume/0-start.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
roles:
|
||||
- - host.a
|
||||
- osd.0
|
||||
- osd.1
|
||||
- osd.2
|
||||
- osd.3
|
||||
- client.0
|
||||
- - host.b
|
||||
- osd.4
|
||||
- osd.5
|
||||
- osd.6
|
||||
- osd.7
|
||||
- client.1
|
||||
openstack:
|
||||
- volumes: # attached to each instance
|
||||
count: 4
|
||||
size: 10 # GB
|
||||
overrides:
|
||||
ceph:
|
||||
conf:
|
||||
osd:
|
||||
osd shutdown pgref assert: true
|
||||
tasks:
|
||||
- install:
|
||||
- cephadm:
|
||||
roleless: true
|
||||
- cephadm.shell:
|
||||
host.a:
|
||||
- ceph orch status
|
||||
- ceph orch ps
|
||||
- ceph orch ls
|
||||
- ceph orch host ls
|
||||
- ceph orch device ls
|
||||
- cephadm.shell:
|
||||
host.a:
|
||||
- ceph fs volume create foo
|
||||
- fs.ready:
|
||||
timeout: 300
|
11
qa/suites/fs/cephadm/renamevolume/1-rename.yaml
Normal file
11
qa/suites/fs/cephadm/renamevolume/1-rename.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
tasks:
|
||||
- cephadm.shell:
|
||||
host.a:
|
||||
- ceph fs volume rename foo bar --yes-i-really-mean-it
|
||||
- fs.ready:
|
||||
timeout: 300
|
||||
- cephadm.shell:
|
||||
host.a:
|
||||
- |
|
||||
set -ex
|
||||
ceph orch ls mds --format=json | jq ".[] | .service_name" | grep "mds.bar"
|
1
qa/suites/fs/cephadm/renamevolume/distro/.qa
Symbolic link
1
qa/suites/fs/cephadm/renamevolume/distro/.qa
Symbolic link
@ -0,0 +1 @@
|
||||
../.qa
|
@ -0,0 +1 @@
|
||||
.qa/distros/single-container-host.yaml
|
1
qa/suites/fs/cephadm/renamevolume/overrides/.qa
Symbolic link
1
qa/suites/fs/cephadm/renamevolume/overrides/.qa
Symbolic link
@ -0,0 +1 @@
|
||||
../.qa
|
@ -0,0 +1 @@
|
||||
.qa/cephfs/overrides/whitelist_health.yaml
|
@ -513,6 +513,89 @@ class TestVolumes(TestVolumesHelper):
|
||||
self.assertNotIn(pool["name"], pools,
|
||||
"pool {0} exists after volume removal".format(pool["name"]))
|
||||
|
||||
def test_volume_rename(self):
|
||||
"""
|
||||
That volume, its file system and pools, can be renamed.
|
||||
"""
|
||||
for m in self.mounts:
|
||||
m.umount_wait()
|
||||
oldvolname = self.volname
|
||||
newvolname = self._generate_random_volume_name()
|
||||
new_data_pool, new_metadata_pool = f"cephfs.{newvolname}.data", f"cephfs.{newvolname}.meta"
|
||||
self._fs_cmd("volume", "rename", oldvolname, newvolname,
|
||||
"--yes-i-really-mean-it")
|
||||
volumels = json.loads(self._fs_cmd('volume', 'ls'))
|
||||
volnames = [volume['name'] for volume in volumels]
|
||||
# volume name changed
|
||||
self.assertIn(newvolname, volnames)
|
||||
self.assertNotIn(oldvolname, volnames)
|
||||
# pool names changed
|
||||
self.fs.get_pool_names(refresh=True)
|
||||
self.assertEqual(new_metadata_pool, self.fs.get_metadata_pool_name())
|
||||
self.assertEqual(new_data_pool, self.fs.get_data_pool_name())
|
||||
|
||||
def test_volume_rename_idempotency(self):
|
||||
"""
|
||||
That volume rename is idempotent.
|
||||
"""
|
||||
for m in self.mounts:
|
||||
m.umount_wait()
|
||||
oldvolname = self.volname
|
||||
newvolname = self._generate_random_volume_name()
|
||||
new_data_pool, new_metadata_pool = f"cephfs.{newvolname}.data", f"cephfs.{newvolname}.meta"
|
||||
self._fs_cmd("volume", "rename", oldvolname, newvolname,
|
||||
"--yes-i-really-mean-it")
|
||||
self._fs_cmd("volume", "rename", oldvolname, newvolname,
|
||||
"--yes-i-really-mean-it")
|
||||
volumels = json.loads(self._fs_cmd('volume', 'ls'))
|
||||
volnames = [volume['name'] for volume in volumels]
|
||||
self.assertIn(newvolname, volnames)
|
||||
self.assertNotIn(oldvolname, volnames)
|
||||
self.fs.get_pool_names(refresh=True)
|
||||
self.assertEqual(new_metadata_pool, self.fs.get_metadata_pool_name())
|
||||
self.assertEqual(new_data_pool, self.fs.get_data_pool_name())
|
||||
|
||||
def test_volume_rename_fails_without_confirmation_flag(self):
|
||||
"""
|
||||
That renaming volume fails without --yes-i-really-mean-it flag.
|
||||
"""
|
||||
newvolname = self._generate_random_volume_name()
|
||||
try:
|
||||
self._fs_cmd("volume", "rename", self.volname, newvolname)
|
||||
except CommandFailedError as ce:
|
||||
self.assertEqual(ce.exitstatus, errno.EPERM,
|
||||
"invalid error code on renaming a FS volume without the "
|
||||
"'--yes-i-really-mean-it' flag")
|
||||
else:
|
||||
self.fail("expected renaming of FS volume to fail without the "
|
||||
"'--yes-i-really-mean-it' flag")
|
||||
|
||||
def test_volume_rename_for_more_than_one_data_pool(self):
|
||||
"""
|
||||
That renaming a volume with more than one data pool does not change
|
||||
the name of the data pools.
|
||||
"""
|
||||
for m in self.mounts:
|
||||
m.umount_wait()
|
||||
self.fs.add_data_pool('another-data-pool')
|
||||
oldvolname = self.volname
|
||||
newvolname = self._generate_random_volume_name()
|
||||
self.fs.get_pool_names(refresh=True)
|
||||
orig_data_pool_names = list(self.fs.data_pools.values())
|
||||
new_metadata_pool = f"cephfs.{newvolname}.meta"
|
||||
self._fs_cmd("volume", "rename", self.volname, newvolname,
|
||||
"--yes-i-really-mean-it")
|
||||
volumels = json.loads(self._fs_cmd('volume', 'ls'))
|
||||
volnames = [volume['name'] for volume in volumels]
|
||||
# volume name changed
|
||||
self.assertIn(newvolname, volnames)
|
||||
self.assertNotIn(oldvolname, volnames)
|
||||
self.fs.get_pool_names(refresh=True)
|
||||
# metadata pool name changed
|
||||
self.assertEqual(new_metadata_pool, self.fs.get_metadata_pool_name())
|
||||
# data pool names unchanged
|
||||
self.assertCountEqual(orig_data_pool_names, list(self.fs.data_pools.values()))
|
||||
|
||||
|
||||
class TestSubvolumeGroups(TestVolumesHelper):
|
||||
"""Tests for FS subvolume group operations."""
|
||||
|
@ -21,6 +21,11 @@ def remove_pool(mgr, pool_name):
|
||||
'yes_i_really_really_mean_it': True}
|
||||
return mgr.mon_command(command)
|
||||
|
||||
def rename_pool(mgr, pool_name, new_pool_name):
|
||||
command = {'prefix': 'osd pool rename', 'srcpool': pool_name,
|
||||
'destpool': new_pool_name}
|
||||
return mgr.mon_command(command)
|
||||
|
||||
def create_filesystem(mgr, fs_name, metadata_pool, data_pool):
|
||||
command = {'prefix': 'fs new', 'fs_name': fs_name, 'metadata': metadata_pool,
|
||||
'data': data_pool}
|
||||
@ -35,6 +40,11 @@ def remove_filesystem(mgr, fs_name):
|
||||
command = {'prefix': 'fs rm', 'fs_name': fs_name, 'yes_i_really_mean_it': True}
|
||||
return mgr.mon_command(command)
|
||||
|
||||
def rename_filesystem(mgr, fs_name, new_fs_name):
|
||||
command = {'prefix': 'fs rename', 'fs_name': fs_name, 'new_fs_name': new_fs_name,
|
||||
'yes_i_really_mean_it': True}
|
||||
return mgr.mon_command(command)
|
||||
|
||||
def create_mds(mgr, fs_name, placement):
|
||||
spec = ServiceSpec(service_type='mds',
|
||||
service_id=fs_name,
|
||||
|
@ -2,7 +2,7 @@ import errno
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
@ -10,8 +10,8 @@ import orchestrator
|
||||
|
||||
from .lock import GlobalLock
|
||||
from ..exception import VolumeException
|
||||
from ..fs_util import create_pool, remove_pool, create_filesystem, \
|
||||
remove_filesystem, create_mds, volume_exists
|
||||
from ..fs_util import create_pool, remove_pool, rename_pool, create_filesystem, \
|
||||
remove_filesystem, rename_filesystem, create_mds, volume_exists
|
||||
from mgr_util import open_filesystem, CephfsConnectionException
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -117,6 +117,102 @@ def delete_volume(mgr, volname, metadata_pool, data_pools):
|
||||
result_str = "metadata pool: {0} data pool: {1} removed".format(metadata_pool, str(data_pools))
|
||||
return r, result_str, ""
|
||||
|
||||
def rename_volume(mgr, volname: str, newvolname: str) -> Tuple[int, str, str]:
|
||||
"""
|
||||
rename volume (orch MDS service, file system, pools)
|
||||
"""
|
||||
# To allow volume rename to be idempotent, check whether orch managed MDS
|
||||
# service is already renamed. If so, skip renaming MDS service.
|
||||
completion = None
|
||||
rename_mds_service = True
|
||||
try:
|
||||
completion = mgr.describe_service(
|
||||
service_type='mds', service_name=f"mds.{newvolname}", refresh=True)
|
||||
orchestrator.raise_if_exception(completion)
|
||||
except (ImportError, orchestrator.OrchestratorError):
|
||||
log.warning("Failed to fetch orch service mds.%s", newvolname)
|
||||
except Exception as e:
|
||||
# Don't let detailed orchestrator exceptions (python backtraces)
|
||||
# bubble out to the user
|
||||
log.exception("Failed to fetch orch service mds.%s", newvolname)
|
||||
return -errno.EINVAL, "", str(e)
|
||||
if completion and completion.result:
|
||||
rename_mds_service = False
|
||||
|
||||
# Launch new MDS service matching newvolname
|
||||
completion = None
|
||||
remove_mds_service = False
|
||||
if rename_mds_service:
|
||||
try:
|
||||
completion = mgr.describe_service(
|
||||
service_type='mds', service_name=f"mds.{volname}", refresh=True)
|
||||
orchestrator.raise_if_exception(completion)
|
||||
except (ImportError, orchestrator.OrchestratorError):
|
||||
log.warning("Failed to fetch orch service mds.%s", volname)
|
||||
except Exception as e:
|
||||
# Don't let detailed orchestrator exceptions (python backtraces)
|
||||
# bubble out to the user
|
||||
log.exception("Failed to fetch orch service mds.%s", volname)
|
||||
return -errno.EINVAL, "", str(e)
|
||||
if completion and completion.result:
|
||||
svc = completion.result[0]
|
||||
placement = svc.spec.placement.pretty_str()
|
||||
create_mds(mgr, newvolname, placement)
|
||||
remove_mds_service = True
|
||||
|
||||
# rename_filesytem is idempotent
|
||||
r, outb, outs = rename_filesystem(mgr, volname, newvolname)
|
||||
if r != 0:
|
||||
errmsg = f"Failed to rename file system '{volname}' to '{newvolname}'"
|
||||
log.error("Failed to rename file system '%s' to '%s'", volname, newvolname)
|
||||
outs = f'{errmsg}; {outs}'
|
||||
return r, outb, outs
|
||||
|
||||
# Rename file system's metadata and data pools
|
||||
metadata_pool, data_pools = get_pool_names(mgr, newvolname)
|
||||
|
||||
new_metadata_pool, new_data_pool = gen_pool_names(newvolname)
|
||||
if metadata_pool != new_metadata_pool:
|
||||
r, outb, outs = rename_pool(mgr, metadata_pool, new_metadata_pool)
|
||||
if r != 0:
|
||||
errmsg = f"Failed to rename metadata pool '{metadata_pool}' to '{new_metadata_pool}'"
|
||||
log.error("Failed to rename metadata pool '%s' to '%s'", metadata_pool, new_metadata_pool)
|
||||
outs = f'{errmsg}; {outs}'
|
||||
return r, outb, outs
|
||||
|
||||
data_pool_rename_failed = False
|
||||
# If file system has more than one data pool, then skip renaming
|
||||
# the data pools, and proceed to remove the old MDS service.
|
||||
if len(data_pools) > 1:
|
||||
data_pool_rename_failed = True
|
||||
else:
|
||||
data_pool = data_pools[0]
|
||||
if data_pool != new_data_pool:
|
||||
r, outb, outs = rename_pool(mgr, data_pool, new_data_pool)
|
||||
if r != 0:
|
||||
errmsg = f"Failed to rename data pool '{data_pool}' to '{new_data_pool}'"
|
||||
log.error("Failed to rename data pool '%s' to '%s'", data_pool, new_data_pool)
|
||||
outs = f'{errmsg}; {outs}'
|
||||
return r, outb, outs
|
||||
|
||||
# Tear down old MDS service
|
||||
if remove_mds_service:
|
||||
try:
|
||||
completion = mgr.remove_service('mds.' + volname)
|
||||
orchestrator.raise_if_exception(completion)
|
||||
except (ImportError, orchestrator.OrchestratorError):
|
||||
log.warning("Failed to tear down orch service mds.%s", volname)
|
||||
except Exception as e:
|
||||
# Don't let detailed orchestrator exceptions (python backtraces)
|
||||
# bubble out to the user
|
||||
log.exception("Failed to tear down orch service mds.%s", volname)
|
||||
return -errno.EINVAL, "", str(e)
|
||||
|
||||
outb = f"FS volume '{volname}' renamed to '{newvolname}'"
|
||||
if data_pool_rename_failed:
|
||||
outb += ". But failed to rename data pools as more than one data pool was found."
|
||||
|
||||
return r, outb, ""
|
||||
|
||||
def list_volumes(mgr):
|
||||
"""
|
||||
|
@ -10,7 +10,7 @@ from mgr_util import CephfsClient
|
||||
from .fs_util import listdir
|
||||
|
||||
from .operations.volume import create_volume, \
|
||||
delete_volume, list_volumes, open_volume, get_pool_names
|
||||
delete_volume, rename_volume, list_volumes, open_volume, get_pool_names
|
||||
from .operations.group import open_group, create_group, remove_group, open_group_unique
|
||||
from .operations.subvolume import open_subvol, create_subvol, remove_subvol, \
|
||||
create_clone
|
||||
@ -131,6 +131,21 @@ class VolumeClient(CephfsClient["Module"]):
|
||||
volumes = list_volumes(self.mgr)
|
||||
return 0, json.dumps(volumes, indent=4, sort_keys=True), ""
|
||||
|
||||
def rename_fs_volume(self, volname, newvolname, sure):
|
||||
if self.is_stopping():
|
||||
return -errno.ESHUTDOWN, "", "shutdown in progress"
|
||||
|
||||
if not sure:
|
||||
return (
|
||||
-errno.EPERM, "",
|
||||
"WARNING: This will rename the filesystem and possibly its "
|
||||
"pools. It 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 rename_volume(self.mgr, volname, newvolname)
|
||||
|
||||
### subvolume operations
|
||||
|
||||
def _create_subvolume(self, fs_handle, volname, group, subvolname, **kwargs):
|
||||
|
@ -60,6 +60,14 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
|
||||
'desc': "Delete a FS volume by passing --yes-i-really-mean-it flag",
|
||||
'perm': 'rw'
|
||||
},
|
||||
{
|
||||
'cmd': 'fs volume rename '
|
||||
f'name=vol_name,type=CephString,goodchars={goodchars} '
|
||||
f'name=new_vol_name,type=CephString,goodchars={goodchars} '
|
||||
'name=yes_i_really_mean_it,type=CephBool,req=false ',
|
||||
'desc': "Rename a CephFS volume by passing --yes-i-really-mean-it flag",
|
||||
'perm': 'rw'
|
||||
},
|
||||
{
|
||||
'cmd': 'fs subvolumegroup ls '
|
||||
'name=vol_name,type=CephString ',
|
||||
@ -416,6 +424,12 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
|
||||
def _cmd_fs_volume_ls(self, inbuf, cmd):
|
||||
return self.vc.list_fs_volumes()
|
||||
|
||||
@mgr_cmd_wrap
|
||||
def _cmd_fs_volume_rename(self, inbuf, cmd):
|
||||
return self.vc.rename_fs_volume(cmd['vol_name'],
|
||||
cmd['new_vol_name'],
|
||||
cmd.get('yes_i_really_mean_it', False))
|
||||
|
||||
@mgr_cmd_wrap
|
||||
def _cmd_fs_subvolumegroup_create(self, inbuf, cmd):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user