[NetApp] Add manage/unmanage of share servers

This patch implements the functionality of managing and
unmanaging of share servers to the NetApp driver,
allowing for shares and snapshots to be managed
and unmanaged in DHSS=True driver mode.

Implements: bp netapp-driver-manage-unmanage-with-share-servers
Change-Id: I6051cf038dcf9f175e0610fff0adf360230dc23c
Depends-On: I452c2a99b186f53d737cb7fbd7eabfcfd9b249d6
This commit is contained in:
Rodrigo Barbieri 2019-01-07 15:03:04 -02:00 committed by Rodrigo Barbieri
parent d877b61c5e
commit 5556bdb727
12 changed files with 249 additions and 35 deletions

View File

@ -1757,6 +1757,15 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
}
self.send_request('volume-rename', api_args)
@na_utils.trace
def rename_vserver(self, vserver_name, new_vserver_name):
"""Rename a vserver."""
api_args = {
'vserver-name': vserver_name,
'new-name': new_vserver_name,
}
self.send_request('vserver-rename', api_args)
@na_utils.trace
def modify_volume(self, aggregate_name, volume_name,
thin_provisioned=False, snapshot_policy=None,

View File

@ -83,6 +83,22 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def unmanage_snapshot(self, snapshot):
raise NotImplementedError
def manage_existing_with_server(
self, share, driver_options, share_server=None):
return self.library.manage_existing(
share, driver_options, share_server=share_server)
def unmanage_with_server(self, share, share_server=None):
self.library.unmanage(share, share_server=share_server)
def manage_existing_snapshot_with_server(
self, snapshot, driver_options, share_server=None):
return self.library.manage_existing_snapshot(
snapshot, driver_options, share_server=share_server)
def unmanage_snapshot_with_server(self, snapshot, share_server=None):
self.library.unmanage_snapshot(snapshot, share_server=share_server)
def update_access(self, context, share, access_rules, add_rules,
delete_rules, **kwargs):
self.library.update_access(context, share, access_rules, add_rules,
@ -240,3 +256,15 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def ensure_shares(self, context, shares):
return self.library.ensure_shares(context, shares)
def get_share_server_network_info(
self, context, share_server, identifier, driver_options):
return self.library.get_share_server_network_info(
context, share_server, identifier, driver_options)
def manage_server(self, context, share_server, identifier, driver_options):
return self.library.manage_server(
context, share_server, identifier, driver_options)
def unmanage_server(self, server_details, security_services=None):
return self.library.unmanage_server(server_details, security_services)

View File

@ -83,6 +83,20 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def unmanage_snapshot(self, snapshot):
self.library.unmanage_snapshot(snapshot)
def manage_existing_with_server(
self, share, driver_options, share_server=None):
raise NotImplementedError
def unmanage_with_server(self, share, share_server=None):
raise NotImplementedError
def manage_existing_snapshot_with_server(
self, snapshot, driver_options, share_server=None):
raise NotImplementedError
def unmanage_snapshot_with_server(self, snapshot, share_server=None):
raise NotImplementedError
def update_access(self, context, share, access_rules, add_rules,
delete_rules, **kwargs):
self.library.update_access(context, share, access_rules, add_rules,
@ -256,3 +270,13 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def ensure_shares(self, context, shares):
return self.library.ensure_shares(context, shares)
def get_share_server_network_info(
self, context, share_server, identifier, driver_options):
raise NotImplementedError
def manage_server(self, context, share_server, identifier, driver_options):
raise NotImplementedError
def unmanage_server(self, server_details, security_services=None):
raise NotImplementedError

View File

@ -997,15 +997,15 @@ class NetAppCmodeFileStorageLibrary(object):
raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name)
@na_utils.trace
def manage_existing(self, share, driver_options):
vserver, vserver_client = self._get_vserver(share_server=None)
def manage_existing(self, share, driver_options, share_server=None):
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_size = self._manage_container(share, vserver, vserver_client)
export_locations = self._create_export(share, None, vserver,
export_locations = self._create_export(share, share_server, vserver,
vserver_client)
return {'size': share_size, 'export_locations': export_locations}
@na_utils.trace
def unmanage(self, share):
def unmanage(self, share, share_server=None):
pass
@na_utils.trace
@ -1110,9 +1110,10 @@ class NetAppCmodeFileStorageLibrary(object):
raise exception.ManageInvalidShare(reason=msg % msg_args)
@na_utils.trace
def manage_existing_snapshot(self, snapshot, driver_options):
def manage_existing_snapshot(
self, snapshot, driver_options, share_server=None):
"""Brings an existing snapshot under Manila management."""
vserver, vserver_client = self._get_vserver(share_server=None)
vserver, vserver_client = self._get_vserver(share_server=share_server)
share_name = self._get_backend_share_name(snapshot['share_id'])
existing_snapshot_name = snapshot.get('provider_location')
new_snapshot_name = self._get_backend_snapshot_name(snapshot['id'])
@ -1159,7 +1160,7 @@ class NetAppCmodeFileStorageLibrary(object):
return {'size': size, 'provider_location': new_snapshot_name}
@na_utils.trace
def unmanage_snapshot(self, snapshot):
def unmanage_snapshot(self, snapshot, share_server=None):
"""Removes the specified snapshot from Manila management."""
@na_utils.trace

View File

@ -67,12 +67,9 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
check_for_setup_error())
@na_utils.trace
def _get_vserver(self, share_server=None):
if not share_server:
msg = _('Share server not provided')
raise exception.InvalidInput(reason=msg)
def _get_vserver(self, share_server=None, vserver_name=None):
if share_server:
backend_details = share_server.get('backend_details')
vserver = backend_details.get(
'vserver_name') if backend_details else None
@ -81,6 +78,11 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
msg = _('Vserver name is absent in backend details. Please '
'check whether Vserver was created properly.')
raise exception.VserverNotSpecified(msg)
elif vserver_name:
vserver = vserver_name
else:
msg = _('Share server not provided')
raise exception.InvalidInput(reason=msg)
if not self._client.vserver_exists(vserver):
raise exception.VserverNotFound(vserver=vserver)
@ -397,3 +399,37 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
if options['ipv6-enabled']:
versions.append(6)
return versions
def manage_server(self, context, share_server, identifier, driver_options):
"""Manages a vserver by renaming it and returning backend_details."""
new_vserver_name = self._get_vserver_name(share_server['id'])
old_vserver_name = self._get_correct_vserver_old_name(identifier)
if new_vserver_name != old_vserver_name:
self._client.rename_vserver(old_vserver_name, new_vserver_name)
backend_details = {'vserver_name': new_vserver_name}
return new_vserver_name, backend_details
def unmanage_server(self, server_details, security_services=None):
pass
def get_share_server_network_info(
self, context, share_server, identifier, driver_options):
"""Returns a list of IPs for each vserver network interface."""
vserver_name = self._get_correct_vserver_old_name(identifier)
vserver, vserver_client = self._get_vserver(vserver_name=vserver_name)
interfaces = vserver_client.get_network_interfaces()
allocations = []
for lif in interfaces:
allocations.append(lif['address'])
return allocations
def _get_correct_vserver_old_name(self, identifier):
# In case vserver_name includes the template, we check and add it here
if not self._client.vserver_exists(identifier):
return self._get_vserver_name(identifier)
return identifier

View File

@ -146,7 +146,10 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
def _get_export_location(share):
"""Returns IP address and export location of an NFS share."""
export_location = share['export_location'] or ':'
return export_location.rsplit(':', 1)
result = export_location.rsplit(':', 1)
if len(result) != 2:
return ['', '']
return result
@staticmethod
def _get_temp_export_policy_name():

View File

@ -3067,6 +3067,20 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with(
'volume-rename', volume_rename_api_args)
def test_rename_vserver(self):
vserver_api_args = {
'vserver-name': fake.VSERVER_NAME,
'new-name': fake.VSERVER_NAME_2,
}
self.mock_object(self.client, 'send_request')
self.client.rename_vserver(fake.VSERVER_NAME, fake.VSERVER_NAME_2)
self.client.send_request.assert_called_once_with(
'vserver-rename', vserver_api_args
)
def test_modify_volume_no_optional_args(self):
self.mock_object(self.client, 'send_request')

View File

@ -1534,13 +1534,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertFalse(vserver_client.split_volume_clone.called)
self.assertFalse(vserver_client.soft_delete_snapshot.called)
def test_manage_existing(self):
@ddt.data(None, fake.VSERVER1)
def test_manage_existing(self, fake_vserver):
vserver_client = mock.Mock()
self.mock_object(self.library,
'_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
vserver_client)))
mock__get_vserver = self.mock_object(
self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1, vserver_client)))
mock_manage_container = self.mock_object(
self.library,
'_manage_container',
@ -1550,24 +1550,29 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'_create_export',
mock.Mock(return_value=fake.NFS_EXPORTS))
result = self.library.manage_existing(fake.SHARE, {})
result = self.library.manage_existing(fake.SHARE, {},
share_server=fake_vserver)
expected = {
'size': fake.SHARE_SIZE,
'export_locations': fake.NFS_EXPORTS
}
mock__get_vserver.assert_called_once_with(share_server=fake_vserver)
mock_manage_container.assert_called_once_with(fake.SHARE,
fake.VSERVER1,
vserver_client)
mock_create_export.assert_called_once_with(fake.SHARE,
None,
fake_vserver,
fake.VSERVER1,
vserver_client)
self.assertDictEqual(expected, result)
def test_unmanage(self):
@ddt.data(None, fake.VSERVER1)
def test_unmanage(self, fake_vserver):
result = self.library.unmanage(fake.SHARE)
result = self.library.unmanage(fake.SHARE, share_server=fake_vserver)
self.assertIsNone(result)
@ -1782,7 +1787,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.FLEXVOL_TO_MANAGE,
vserver_client)
def test_manage_existing_snapshot(self):
@ddt.data(None, fake.VSERVER1)
def test_manage_existing_snapshot(self, fake_vserver):
vserver_client = mock.Mock()
mock_get_vserver = self.mock_object(
@ -1791,13 +1797,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client.get_volume.return_value = fake.FLEXVOL_TO_MANAGE
vserver_client.volume_has_snapmirror_relationships.return_value = False
result = self.library.manage_existing_snapshot(
fake.SNAPSHOT_TO_MANAGE, {})
fake.SNAPSHOT_TO_MANAGE, {}, share_server=fake_vserver)
share_name = self.library._get_backend_share_name(
fake.SNAPSHOT['share_id'])
new_snapshot_name = self.library._get_backend_snapshot_name(
fake.SNAPSHOT['id'])
mock_get_vserver.assert_called_once_with(share_server=None)
mock_get_vserver.assert_called_once_with(share_server=fake_vserver)
(vserver_client.volume_has_snapmirror_relationships.
assert_called_once_with(fake.FLEXVOL_TO_MANAGE))
vserver_client.rename_snapshot.assert_called_once_with(
@ -1870,9 +1876,10 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library.manage_existing_snapshot,
fake.SNAPSHOT_TO_MANAGE, {})
def test_unmanage_snapshot(self):
@ddt.data(None, fake.VSERVER1)
def test_unmanage_snapshot(self, fake_vserver):
result = self.library.unmanage_snapshot(fake.SNAPSHOT)
result = self.library.unmanage_snapshot(fake.SNAPSHOT, fake_vserver)
self.assertIsNone(result)

View File

@ -110,6 +110,25 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertRaises(exception.InvalidInput,
self.library._get_vserver)
def test_get_vserver_no_share_server_with_vserver_name(self):
fake_vserver_client = 'fake_client'
mock_vserver_exists = self.mock_object(
self.library._client, 'vserver_exists',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_get_api_client',
mock.Mock(return_value=fake_vserver_client))
result_vserver, result_vserver_client = self.library._get_vserver(
share_server=None, vserver_name=fake.VSERVER1)
mock_vserver_exists.assert_called_once_with(
fake.VSERVER1
)
self.assertEqual(fake.VSERVER1, result_vserver)
self.assertEqual(fake_vserver_client, result_vserver_client)
def test_get_vserver_no_backend_details(self):
fake_share_server = copy.deepcopy(fake.SHARE_SERVER)
@ -186,6 +205,69 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
}
self.assertEqual(expected, result)
@ddt.data('fake', fake.IDENTIFIER)
def test_manage_server(self, fake_vserver_name):
self.mock_object(context,
'get_admin_context',
mock.Mock(return_value='fake_admin_context'))
mock_get_vserver_name = self.mock_object(
self.library, '_get_vserver_name',
mock.Mock(return_value=fake_vserver_name))
new_identifier, new_details = self.library.manage_server(
context, fake.SHARE_SERVER, fake.IDENTIFIER, {})
mock_get_vserver_name.assert_called_once_with(fake.SHARE_SERVER['id'])
self.assertEqual(fake_vserver_name, new_details['vserver_name'])
self.assertEqual(fake_vserver_name, new_identifier)
def test_get_share_server_network_info(self):
fake_vserver_client = mock.Mock()
self.mock_object(context,
'get_admin_context',
mock.Mock(return_value='fake_admin_context'))
mock_get_vserver = self.mock_object(
self.library, '_get_vserver',
mock.Mock(return_value=['fake', fake_vserver_client]))
net_interfaces = copy.deepcopy(c_fake.NETWORK_INTERFACES_MULTIPLE)
self.mock_object(fake_vserver_client,
'get_network_interfaces',
mock.Mock(return_value=net_interfaces))
result = self.library.get_share_server_network_info(context,
fake.SHARE_SERVER,
fake.IDENTIFIER,
{})
mock_get_vserver.assert_called_once_with(
vserver_name=fake.IDENTIFIER
)
reference_allocations = []
for lif in net_interfaces:
reference_allocations.append(lif['address'])
self.assertEqual(reference_allocations, result)
@ddt.data((True, fake.IDENTIFIER),
(False, fake.IDENTIFIER))
@ddt.unpack
def test__verify_share_server_name(self, vserver_exists, identifier):
mock_exists = self.mock_object(self.client, 'vserver_exists',
mock.Mock(return_value=vserver_exists))
expected_result = identifier
if not vserver_exists:
expected_result = self.library._get_vserver_name(identifier)
result = self.library._get_correct_vserver_old_name(identifier)
self.assertEqual(result, expected_result)
mock_exists.assert_called_once_with(identifier)
def test_handle_housekeeping_tasks(self):
self.mock_object(self.client, 'prune_deleted_nfs_export_policies')

View File

@ -356,6 +356,7 @@ NETWORK_INFO = {
NETWORK_INFO_NETMASK = '255.255.255.0'
SHARE_SERVER = {
'id': 'fake_id',
'share_network_id': 'c5b3a865-56d0-4d88-abe5-879965e099c9',
'backend_details': {
'vserver_name': VSERVER1
@ -523,6 +524,8 @@ COLLATED_CGSNAPSHOT_INFO = [
},
]
IDENTIFIER = 'c5b3a865-56d0-4d88-dke5-853465e099c9'
LIF_NAMES = []
LIF_ADDRESSES = ['10.10.10.10', '10.10.10.20']
LIFS = (

View File

@ -163,10 +163,11 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
self.assertEqual(fake.SHARE_ADDRESS_1, host_ip)
self.assertEqual('/' + fake.SHARE_NAME, export_path)
def test_get_export_location_missing_location(self):
@ddt.data('', 'invalid')
def test_get_export_location_missing_location_invalid(self, export):
fake_share = fake.NFS_SHARE.copy()
fake_share['export_location'] = ''
fake_share['export_location'] = export
host_ip, export_path = self.helper._get_export_location(fake_share)

View File

@ -0,0 +1,6 @@
---
features:
- Added managing and unmanaging of share servers
functionality to the NetApp driver, allowing for
shares and snapshots to be managed and unmanaged in
driver mode ``driver_handles_share_servers`` set to True.