Dell PowerScale: add support for ensure shares

Implements: blueprint dell-powerscale-ensure-shares
Change-Id: I957505bb4bce57f45c4caa44b1ac1eb7d7ef766c
Signed-off-by: Yian Zong <yian.zong@dell.com>
This commit is contained in:
Yian Zong
2025-07-25 07:05:34 +00:00
parent 28c30b5065
commit 11640f02d4
8 changed files with 203 additions and 2 deletions

View File

@@ -46,6 +46,8 @@ The following operations are supported:
- Create a share from a snapshot.
- Ensure shares.
Back end configuration
~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -293,3 +293,15 @@ class EMCShareDriver(driver.ShareDriver):
if hasattr(self.plugin, 'get_default_filter_function'):
return self.plugin.get_default_filter_function()
return None
def get_backend_info(self, context):
"""Get driver and array configuration parameters."""
if hasattr(self.plugin, 'get_backend_info'):
return self.plugin.get_backend_info(context)
raise NotImplementedError()
def ensure_shares(self, context, shares):
"""Invoked to ensure that shares are exported."""
if hasattr(self.plugin, 'ensure_shares'):
return self.plugin.ensure_shares(context, shares)
raise NotImplementedError()

View File

@@ -32,8 +32,9 @@ from manila.share.drivers.dell_emc.plugins.isilon import isilon_api
0.1.0 - Initial version
1.0.0 - Fix Http auth issue, SSL verification error and etc
1.0.1 - Add support for update share stats
1.0.2 - Add support for ensure shares
"""
VERSION = "1.0.1"
VERSION = "1.0.2"
CONF = cfg.CONF
@@ -265,6 +266,7 @@ class IsilonStorageConnection(base.StorageConnection):
def ensure_share(self, context, share, share_server):
"""Invoked to ensure that share is exported."""
raise NotImplementedError()
def extend_share(self, share, new_size, share_server=None):
"""Extends a share."""
@@ -504,6 +506,59 @@ class IsilonStorageConnection(base.StorageConnection):
rule_state_map.update({rule['access_id']: {'state': 'error'}})
return cifs_user_permissions
def get_backend_info(self, context):
"""Get driver and array configuration parameters.
:returns: A dictionary containing driver-specific info.
"""
LOG.debug("Retrieving PowerScale backend info.")
cluster_version = self._isilon_api.get_cluster_version()
return {'driver_version': VERSION,
'cluster_version': cluster_version,
'rest_server': self._server,
'rest_port': self._port}
def ensure_shares(self, context, shares):
"""Invoked to ensure that shares are exported.
:shares: A list of all shares for updates.
:returns: None or a dictionary of updates in the format.
"""
LOG.debug("Ensuring PowerScale shares.")
updates = {}
for share in shares:
if share['share_proto'] == 'NFS':
container_path = self._get_container_path(share)
share_id = self._isilon_api.lookup_nfs_export(container_path)
if share_id:
location = self._format_nfs_path(container_path)
updates[share['id']] = {
'export_locations': [location],
'status': 'available',
'reapply_access_rules': True,
}
else:
LOG.warning(f'NFS Share {share["name"]} is not found.')
elif share['share_proto'] == 'CIFS':
smb_share = self._isilon_api.lookup_smb_share(share['name'])
if smb_share:
location = self._format_smb_path(share['name'])
updates[share['id']] = {
'export_locations': [location],
'status': 'available',
'reapply_access_rules': True,
}
else:
LOG.warning(f'CIFS Share {share["name"]} is not found.')
if share['id'] not in updates:
updates[share['id']] = {
'export_locations': [],
'status': 'error',
'reapply_access_rules': False,
}
return updates
def _format_smb_path(self, share_name):
return '\\\\{0}\\{1}'.format(self._server, share_name)

View File

@@ -393,6 +393,15 @@ class IsilonApi(object):
spaces['used'] = stat['value']
return spaces
def get_cluster_version(self):
url = '{0}/platform/12/cluster/version'.format(self.host_url)
r = self.send_get_request(url)
if r.status_code != 200:
raise exception.ShareBackendException(
msg=_('Failed to get cluster version from PowerScale.')
)
return r.json()['nodes'][0]['release']
def request(self, method, url, headers=None, data=None, params=None):
if data is not None:
data = jsonutils.dumps(data)

View File

@@ -454,7 +454,9 @@ class IsilonTest(test.TestCase):
def test_ensure_share(self):
share = {"name": self.SHARE_NAME, "share_proto": 'CIFS'}
self.storage_connection.ensure_share(self.mock_context, share, None)
self.assertRaises(NotImplementedError,
self.storage_connection.ensure_share,
self.mock_context, share, None)
@mock.patch(
'manila.share.drivers.dell_emc.plugins.isilon.isilon.isilon_api.'
@@ -980,3 +982,89 @@ class IsilonTest(test.TestCase):
self.storage_connection._delete_directory(path)
self._mock_isilon_api.is_path_existent.assert_called_with(path)
self._mock_isilon_api.delete_path.assert_not_called()
def test_get_backend_info(self):
self._mock_isilon_api.get_cluster_version.return_value = '1.0'
result = self.storage_connection.get_backend_info(None)
expected_info = {
'driver_version': isilon.VERSION,
'cluster_version': '1.0',
'rest_server': self.ISILON_ADDR,
'rest_port': '8080',
}
self.assertEqual(expected_info, result)
def test_ensure_shares_nfs_share_exists(self):
share = {
'id': '123',
'share_proto': 'NFS',
'name': 'my_share',
}
container_path = '/ifs/my_share'
location = '10.0.0.1:/ifs/my_share'
self.storage_connection._get_container_path = mock.MagicMock(
return_value=container_path)
self._mock_isilon_api.lookup_nfs_export.return_value = '123'
result = self.storage_connection.ensure_shares(None, [share])
expected_result = {
'123': {
'export_locations': [location],
'status': 'available',
'reapply_access_rules': True,
}
}
self.assertEqual(result, expected_result)
def test_ensure_shares_cifs_share_exists(self):
share = {
'id': '123',
'share_proto': 'CIFS',
'name': 'my_share',
}
location = '\\\\10.0.0.1\\my_share'
self._mock_isilon_api.lookup_smb_share.return_value = share
result = self.storage_connection.ensure_shares(None, [share])
expected_result = {
'123': {
'export_locations': [location],
'status': 'available',
'reapply_access_rules': True,
}
}
self.assertEqual(result, expected_result)
def test_ensure_shares_nfs_share_does_not_exist(self):
share = {
'id': '123',
'share_proto': 'NFS',
'name': 'my_share',
}
self._mock_isilon_api.lookup_nfs_export.return_value = None
result = self.storage_connection.ensure_shares(None, [share])
expected_result = {
'123': {
'export_locations': [],
'status': 'error',
'reapply_access_rules': False,
}
}
self.assertEqual(result, expected_result)
def test_ensure_shares_cifs_share_does_not_exist(self):
share = {
'id': '123',
'share_proto': 'CIFS',
'name': 'my_share',
}
self._mock_isilon_api.lookup_smb_share.return_value = None
result = self.storage_connection.ensure_shares(None, [share])
expected_result = {
'123': {
'export_locations': [],
'status': 'error',
'reapply_access_rules': False,
}
}
self.assertEqual(result, expected_result)

View File

@@ -886,6 +886,29 @@ class IsilonApiTest(test.TestCase):
self.assertRaises(exception.ShareBackendException,
self.isilon_api.get_space_stats)
def test_get_cluster_version_success(self):
self.isilon_api.send_get_request = mock.MagicMock()
self.isilon_api.send_get_request.return_value.status_code = 200
self.isilon_api.send_get_request.return_value.json.return_value = {
'nodes': [{'release': '1.0'}]}
version = self.isilon_api.get_cluster_version()
self.assertEqual(version, '1.0')
self.isilon_api.send_get_request.assert_called_once_with(
'{0}/platform/12/cluster/version'.format(self.isilon_api.host_url)
)
def test_get_cluster_version_failure(self):
self.isilon_api.send_get_request = mock.MagicMock()
self.isilon_api.send_get_request.return_value.status_code = 404
self.assertRaises(exception.ShareBackendException,
self.isilon_api.get_cluster_version)
self.isilon_api.send_get_request.assert_called_once_with(
'{0}/platform/12/cluster/version'.format(self.isilon_api.host_url)
)
def test_modify_smb_share_access_with_host_acl_and_smb_permission(self):
self.isilon_api.send_put_request = mock.MagicMock()
share_name = 'my_share'

View File

@@ -78,6 +78,12 @@ class FakeConnection(base.StorageConnection):
def teardown_server(self, server_details, security_services=None):
"""Teardown share server."""
def get_backend_info(self, context):
"""Get driver and array configuration parameters."""
def ensure_shares(self, context, shares):
"""Invoked to ensure that shares are exported."""
class FakeConnection_powermax(FakeConnection):
def __init__(self, *args, **kwargs):
@@ -228,6 +234,8 @@ class EMCShareFrameworkTestCase(test.TestCase):
self.driver.delete_share(context, share, share_server)
self.driver.delete_snapshot(context, snapshot, share_server)
self.driver.ensure_share(context, share, share_server)
self.driver.get_backend_info(context)
self.driver.ensure_shares(context, [share])
access = mock.Mock()
self.driver.allow_access(context, share, access, share_server)
self.driver.deny_access(context, share, access, share_server)

View File

@@ -0,0 +1,4 @@
---
features:
- |
Dell PowerScale Driver: Added support for ensure shares.