Implement Ensure Shares in the CephFS driver
This allows switching between NFS protocol helpers that the driver currently supports and avoids unnecessary ensuring of driver resources on restarts unless a small set of config opts are toggled. Use of either NFS protocol helper in the driver will trigger access rules to be reapplied when ensuring shares. Implements: bp use-cephadm-nfs-ganesha Partial-Bug: #2035137 Change-Id: I93c4e8616a1cfb5ab713b420aff69464969f28d5 Signed-off-by: Goutham Pacha Ravi <gouthampravi@gmail.com>
This commit is contained in:
parent
389a2ea1e5
commit
ee8af90109
@ -138,6 +138,13 @@ cephfs_opts = [
|
|||||||
cfg.StrOpt('cephfs_filesystem_name',
|
cfg.StrOpt('cephfs_filesystem_name',
|
||||||
help="The name of the filesystem to use, if there are "
|
help="The name of the filesystem to use, if there are "
|
||||||
"multiple filesystems in the cluster."),
|
"multiple filesystems in the cluster."),
|
||||||
|
cfg.StrOpt('cephfs_ensure_all_shares_salt',
|
||||||
|
default="manila_cephfs_reef_bobcat",
|
||||||
|
help="Provide a unique string value to make the driver "
|
||||||
|
"ensure all of the shares it has created during "
|
||||||
|
"startup. Ensuring would re-export shares and this "
|
||||||
|
"action isn't always required, unless something has "
|
||||||
|
"been administratively modified on CephFS.")
|
||||||
]
|
]
|
||||||
|
|
||||||
cephfsnfs_opts = [
|
cephfsnfs_opts = [
|
||||||
@ -540,15 +547,31 @@ class CephFSDriver(driver.ExecuteMixin, driver.GaneshaMixin,
|
|||||||
context, share, access_rules, add_rules, delete_rules,
|
context, share, access_rules, add_rules, delete_rules,
|
||||||
share_server=share_server)
|
share_server=share_server)
|
||||||
|
|
||||||
def ensure_share(self, context, share, share_server=None):
|
def get_backend_info(self, context):
|
||||||
try:
|
return self.protocol_helper.get_backend_info(context)
|
||||||
export_location = self._get_export_locations(share)
|
|
||||||
except exception.ShareBackendException as e:
|
|
||||||
if 'does not exist' in str(e).lower():
|
|
||||||
raise exception.ShareResourceNotFound(share_id=share['id'])
|
|
||||||
raise
|
|
||||||
|
|
||||||
return export_location
|
def ensure_shares(self, context, shares):
|
||||||
|
share_updates = {}
|
||||||
|
for share in shares:
|
||||||
|
share_updates[share['id']] = {
|
||||||
|
'reapply_access_rules':
|
||||||
|
self.protocol_helper.reapply_rules_while_ensuring_shares,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
share_updates[share['id']].update({
|
||||||
|
'export_locations': self._get_export_locations(share),
|
||||||
|
})
|
||||||
|
except exception.ShareBackendException as e:
|
||||||
|
if 'does not exist' in str(e).lower():
|
||||||
|
msg = ("Share instance %(si)s belonging to share "
|
||||||
|
"%(share)s cannot be found on the backend.")
|
||||||
|
msg_payload = {'si': share['id'],
|
||||||
|
'share': share['share_id']}
|
||||||
|
LOG.exception(msg, msg_payload)
|
||||||
|
share_updates[share['id']] = {
|
||||||
|
'status': constants.STATUS_ERROR,
|
||||||
|
}
|
||||||
|
return share_updates
|
||||||
|
|
||||||
def extend_share(self, share, new_size, share_server=None):
|
def extend_share(self, share, new_size, share_server=None):
|
||||||
# resize FS subvolume/share
|
# resize FS subvolume/share
|
||||||
@ -778,6 +801,7 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
|
|||||||
supported_access_types = (CEPHX_ACCESS_TYPE, )
|
supported_access_types = (CEPHX_ACCESS_TYPE, )
|
||||||
supported_access_levels = (constants.ACCESS_LEVEL_RW,
|
supported_access_levels = (constants.ACCESS_LEVEL_RW,
|
||||||
constants.ACCESS_LEVEL_RO)
|
constants.ACCESS_LEVEL_RO)
|
||||||
|
reapply_rules_while_ensuring_shares = False
|
||||||
|
|
||||||
def __init__(self, execute, config, **kwargs):
|
def __init__(self, execute, config, **kwargs):
|
||||||
self.rados_client = kwargs.pop('rados_client')
|
self.rados_client = kwargs.pop('rados_client')
|
||||||
@ -803,6 +827,12 @@ class NativeProtocolHelper(ganesha.NASHelperBase):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_backend_info(self, context):
|
||||||
|
return {
|
||||||
|
"cephfs_ensure_all_shares_salt":
|
||||||
|
self.configuration.cephfs_ensure_all_shares_salt,
|
||||||
|
}
|
||||||
|
|
||||||
def get_export_locations(self, share, subvolume_path):
|
def get_export_locations(self, share, subvolume_path):
|
||||||
# To mount this you need to know the mon IPs and the path to the volume
|
# To mount this you need to know the mon IPs and the path to the volume
|
||||||
mon_addrs = self.get_mon_addrs()
|
mon_addrs = self.get_mon_addrs()
|
||||||
@ -1055,6 +1085,7 @@ class NFSProtocolHelper(NFSProtocolHelperMixin, ganesha.GaneshaNASHelper2):
|
|||||||
|
|
||||||
shared_data = {}
|
shared_data = {}
|
||||||
supported_protocols = ('NFS',)
|
supported_protocols = ('NFS',)
|
||||||
|
reapply_rules_while_ensuring_shares = True
|
||||||
|
|
||||||
def __init__(self, execute, config_object, **kwargs):
|
def __init__(self, execute, config_object, **kwargs):
|
||||||
if config_object.cephfs_ganesha_server_is_remote:
|
if config_object.cephfs_ganesha_server_is_remote:
|
||||||
@ -1151,12 +1182,22 @@ class NFSProtocolHelper(NFSProtocolHelperMixin, ganesha.GaneshaNASHelper2):
|
|||||||
|
|
||||||
return export_ips
|
return export_ips
|
||||||
|
|
||||||
|
def get_backend_info(self, context):
|
||||||
|
backend_info = {
|
||||||
|
"cephfs_ganesha_export_ips": self.config.cephfs_ganesha_export_ips,
|
||||||
|
"cephfs_ganesha_server_ip": self.config.cephfs_ganesha_server_ip,
|
||||||
|
"cephfs_ensure_all_shares_salt":
|
||||||
|
self.configuration.cephfs_ensure_all_shares_salt,
|
||||||
|
}
|
||||||
|
return backend_info
|
||||||
|
|
||||||
|
|
||||||
class NFSClusterProtocolHelper(NFSProtocolHelperMixin, ganesha.NASHelperBase):
|
class NFSClusterProtocolHelper(NFSProtocolHelperMixin, ganesha.NASHelperBase):
|
||||||
|
|
||||||
supported_access_types = ('ip', )
|
supported_access_types = ('ip', )
|
||||||
supported_access_levels = (constants.ACCESS_LEVEL_RW,
|
supported_access_levels = (constants.ACCESS_LEVEL_RW,
|
||||||
constants.ACCESS_LEVEL_RO)
|
constants.ACCESS_LEVEL_RO)
|
||||||
|
reapply_rules_while_ensuring_shares = True
|
||||||
|
|
||||||
def __init__(self, execute, config_object, **kwargs):
|
def __init__(self, execute, config_object, **kwargs):
|
||||||
self.rados_client = kwargs.pop('rados_client')
|
self.rados_client = kwargs.pop('rados_client')
|
||||||
@ -1308,3 +1349,15 @@ class NFSClusterProtocolHelper(NFSProtocolHelperMixin, ganesha.NASHelperBase):
|
|||||||
self._deny_access(share)
|
self._deny_access(share)
|
||||||
|
|
||||||
return rule_state_map
|
return rule_state_map
|
||||||
|
|
||||||
|
def get_backend_info(self, context):
|
||||||
|
backend_info = {
|
||||||
|
"cephfs_ganesha_export_ips":
|
||||||
|
self.configuration.cephfs_ganesha_export_ips,
|
||||||
|
"cephfs_ganesha_server_ip":
|
||||||
|
self.configuration.cephfs_ganesha_server_ip,
|
||||||
|
"cephfs_nfs_cluster_id": self.nfs_clusterid,
|
||||||
|
"cephfs_ensure_all_shares_salt":
|
||||||
|
self.configuration.cephfs_ensure_all_shares_salt,
|
||||||
|
}
|
||||||
|
return backend_info
|
||||||
|
@ -53,6 +53,12 @@ class NASHelperBase(metaclass=abc.ABCMeta):
|
|||||||
delete_rules, share_server=None):
|
delete_rules, share_server=None):
|
||||||
"""Update access rules of share."""
|
"""Update access rules of share."""
|
||||||
|
|
||||||
|
def get_backend_info(self, context):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def ensure_shares(self, context, shares):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class GaneshaNASHelper(NASHelperBase):
|
class GaneshaNASHelper(NASHelperBase):
|
||||||
"""Perform share access changes using Ganesha version < 2.4."""
|
"""Perform share access changes using Ganesha version < 2.4."""
|
||||||
|
@ -235,38 +235,53 @@ class CephFSDriverTestCase(test.TestCase):
|
|||||||
self._context, self._share, access_rules, add_rules, delete_rules,
|
self._context, self._share, access_rules, add_rules, delete_rules,
|
||||||
share_server=None)
|
share_server=None)
|
||||||
|
|
||||||
def test_ensure_share(self):
|
def test_ensure_shares(self):
|
||||||
expected_exports = {
|
self._driver.protocol_helper.reapply_rules_while_ensuring_shares = True
|
||||||
'path': '1.2.3.4,5.6.7.8:/foo/bar',
|
shares = [
|
||||||
'is_admin_only': False,
|
fake_share.fake_share(share_id='123', share_proto='NFS'),
|
||||||
'metadata': {},
|
fake_share.fake_share(share_id='456', share_proto='NFS'),
|
||||||
|
fake_share.fake_share(share_id='789', share_proto='NFS')
|
||||||
|
]
|
||||||
|
export_locations = [
|
||||||
|
{
|
||||||
|
'path': '1.2.3.4,5.6.7.8:/foo/bar',
|
||||||
|
'is_admin_only': False,
|
||||||
|
'metadata': {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'path': '1.2.3.4,5.6.7.8:/foo/quz',
|
||||||
|
'is_admin_only': False,
|
||||||
|
'metadata': {},
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
expected_updates = {
|
||||||
|
shares[0]['id']: {
|
||||||
|
'status': constants.STATUS_ERROR,
|
||||||
|
'reapply_access_rules': True,
|
||||||
|
},
|
||||||
|
shares[1]['id']: {
|
||||||
|
'export_locations': export_locations[0],
|
||||||
|
'reapply_access_rules': True,
|
||||||
|
},
|
||||||
|
shares[2]['id']: {
|
||||||
|
'export_locations': export_locations[1],
|
||||||
|
'reapply_access_rules': True,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
err_message = (f"Error ENOENT: subvolume {self._share['id']} does "
|
||||||
|
f"not exist")
|
||||||
|
expected_exception = exception.ShareBackendException(err_message)
|
||||||
self.mock_object(
|
self.mock_object(
|
||||||
self._driver, '_get_export_locations',
|
self._driver, '_get_export_locations',
|
||||||
mock.Mock(return_value=expected_exports))
|
mock.Mock(side_effect=[expected_exception] + export_locations))
|
||||||
|
|
||||||
returned_exports = self._driver.ensure_share(self._context,
|
actual_updates = self._driver.ensure_shares(self._context, shares)
|
||||||
self._share)
|
|
||||||
|
|
||||||
self._driver._get_export_locations.assert_called_once_with(self._share)
|
self.assertEqual(3, self._driver._get_export_locations.call_count)
|
||||||
|
self._driver._get_export_locations.assert_has_calls([
|
||||||
self.assertEqual(expected_exports, returned_exports)
|
mock.call(shares[0]), mock.call(shares[1]), mock.call(shares[2])])
|
||||||
|
self.assertEqual(expected_updates, actual_updates)
|
||||||
def test_ensure_share_subvolume_not_found(self):
|
|
||||||
message = f"Error ENOENT: subvolume {self._share['id']} does not exist"
|
|
||||||
expected_exception = exception.ShareBackendException(message)
|
|
||||||
|
|
||||||
self.mock_object(
|
|
||||||
self._driver, '_get_export_locations',
|
|
||||||
mock.Mock(side_effect=expected_exception))
|
|
||||||
|
|
||||||
self.assertRaises(
|
|
||||||
exception.ShareResourceNotFound,
|
|
||||||
self._driver.ensure_share,
|
|
||||||
self._context,
|
|
||||||
self._share)
|
|
||||||
|
|
||||||
self._driver._get_export_locations.assert_called_once_with(self._share)
|
|
||||||
|
|
||||||
def test_delete_share(self):
|
def test_delete_share(self):
|
||||||
clone_status_prefix = "fs clone status"
|
clone_status_prefix = "fs clone status"
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
The CephFS backend driver now supports a bulk share recovery mechanism
|
||||||
|
(``ensure_shares``). At startup time, a combination of driver configuration
|
||||||
|
options will determine if the driver must re-evaluate export paths of
|
||||||
|
previously created shares. If these configuration options do not change,
|
||||||
|
service startup will skip through this recovery stage.
|
||||||
|
- |
|
||||||
|
The CephFS backend driver will also reapply access rules when performing
|
||||||
|
a recovery of pre-existing shares.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
A new configuration option called ``cephfs_ensure_all_shares_salt`` has
|
||||||
|
been introduced to assist cloud administrtrators that would like the CephFS
|
||||||
|
driver to reconcile export paths of existing shares during service
|
||||||
|
startup.
|
Loading…
Reference in New Issue
Block a user