cDOT driver should report all share export locations
Manila has the ability for shares to report multiple export locations. This is an important capability for drivers that manage storage backends that export shares on multiple network interfaces. This commit adds this capability to the cDOT driver. This commit also updates the cDOT driver to use backslashes in CIFS export paths. Change-Id: I772caaad2871a072bb5bc4a505678d9fbf0c579c Closes-Bug: #1429246
This commit is contained in:
parent
7b00db5f37
commit
725a9ced55
|
@ -488,9 +488,9 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||||
msg_args = {'vserver': vserver, 'proto': share['share_proto']}
|
msg_args = {'vserver': vserver, 'proto': share['share_proto']}
|
||||||
raise exception.NetAppException(msg % msg_args)
|
raise exception.NetAppException(msg % msg_args)
|
||||||
|
|
||||||
ip_address = interfaces[0]['address']
|
export_addresses = [interface['address'] for interface in interfaces]
|
||||||
export_location = helper.create_share(share_name, ip_address)
|
export_locations = helper.create_share(share_name, export_addresses)
|
||||||
return export_location
|
return export_locations
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _remove_export(self, share, vserver_client):
|
def _remove_export(self, share, vserver_client):
|
||||||
|
|
|
@ -29,7 +29,7 @@ class NetAppBaseHelper(object):
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def create_share(self, share, export_ip):
|
def create_share(self, share, export_addresses):
|
||||||
"""Creates NAS share."""
|
"""Creates NAS share."""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
NetApp CIFS protocol helper class.
|
NetApp CIFS protocol helper class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from manila import exception
|
from manila import exception
|
||||||
|
@ -31,11 +33,12 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||||
"""Netapp specific cluster-mode CIFS sharing driver."""
|
"""Netapp specific cluster-mode CIFS sharing driver."""
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_share(self, share_name, export_ip):
|
def create_share(self, share_name, export_addresses):
|
||||||
"""Creates CIFS share on Data ONTAP Vserver."""
|
"""Creates CIFS share on Data ONTAP Vserver."""
|
||||||
self._client.create_cifs_share(share_name)
|
self._client.create_cifs_share(share_name)
|
||||||
self._client.remove_cifs_share_access(share_name, 'Everyone')
|
self._client.remove_cifs_share_access(share_name, 'Everyone')
|
||||||
return "//%s/%s" % (export_ip, share_name)
|
return [r'\\%s\%s' % (export_address, share_name)
|
||||||
|
for export_address in export_addresses]
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def delete_share(self, share):
|
def delete_share(self, share):
|
||||||
|
@ -84,6 +87,10 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_export_location(share):
|
def _get_export_location(share):
|
||||||
"""Returns host ip and share name for a given CIFS share."""
|
"""Returns host ip and share name for a given CIFS share."""
|
||||||
export_location = share['export_location'] or '///'
|
export_location = share['export_location'] or '\\\\\\'
|
||||||
_x, _x, host_ip, share_name = export_location.split('/')
|
regex = r'^(?:\\\\|//)(?P<host_ip>.*)(?:\\|/)(?P<share_name>.*)$'
|
||||||
return host_ip, share_name
|
match = re.match(regex, export_location)
|
||||||
|
if match:
|
||||||
|
return match.group('host_ip'), match.group('share_name')
|
||||||
|
else:
|
||||||
|
return '', ''
|
||||||
|
|
|
@ -29,11 +29,11 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
|
||||||
"""Netapp specific cluster-mode NFS sharing driver."""
|
"""Netapp specific cluster-mode NFS sharing driver."""
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_share(self, share_name, export_ip):
|
def create_share(self, share_name, export_addresses):
|
||||||
"""Creates NFS share."""
|
"""Creates NFS share."""
|
||||||
export_path = self._client.get_volume_junction_path(share_name)
|
export_path = self._client.get_volume_junction_path(share_name)
|
||||||
export_location = ':'.join([export_ip, export_path])
|
return [':'.join([export_address, export_path])
|
||||||
return export_location
|
for export_address in export_addresses]
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def delete_share(self, share):
|
def delete_share(self, share):
|
||||||
|
|
|
@ -790,7 +790,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
def test_create_export(self):
|
def test_create_export(self):
|
||||||
|
|
||||||
protocol_helper = mock.Mock()
|
protocol_helper = mock.Mock()
|
||||||
protocol_helper.create_share.return_value = 'fake_export_location'
|
protocol_helper.create_share.return_value = fake.NFS_EXPORTS
|
||||||
self.mock_object(self.library,
|
self.mock_object(self.library,
|
||||||
'_get_helper',
|
'_get_helper',
|
||||||
mock.Mock(return_value=protocol_helper))
|
mock.Mock(return_value=protocol_helper))
|
||||||
|
@ -802,10 +802,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||||
vserver_client)
|
vserver_client)
|
||||||
|
|
||||||
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
|
share_name = self.library._get_valid_share_name(fake.SHARE['id'])
|
||||||
self.assertEqual('fake_export_location', result)
|
self.assertEqual(fake.NFS_EXPORTS, result)
|
||||||
protocol_helper.create_share.assert_called_once_with(
|
protocol_helper.create_share.assert_called_once_with(
|
||||||
share_name,
|
share_name, fake.LIF_ADDRESSES)
|
||||||
fake.LIFS[0]['address'])
|
|
||||||
|
|
||||||
def test_create_export_lifs_not_found(self):
|
def test_create_export_lifs_not_found(self):
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,7 @@ SNAPSHOT = {
|
||||||
}
|
}
|
||||||
|
|
||||||
LIF_NAMES = []
|
LIF_NAMES = []
|
||||||
LIF_ADDRESSES = ('10.10.10.10', '10.10.10.20')
|
LIF_ADDRESSES = ['10.10.10.10', '10.10.10.20']
|
||||||
LIFS = (
|
LIFS = (
|
||||||
{'address': LIF_ADDRESSES[0],
|
{'address': LIF_ADDRESSES[0],
|
||||||
'home-node': CLUSTER_NODES[0],
|
'home-node': CLUSTER_NODES[0],
|
||||||
|
@ -208,6 +208,9 @@ LIFS = (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NFS_EXPORTS = [':'.join([LIF_ADDRESSES[0], 'fake_export_path']),
|
||||||
|
':'.join([LIF_ADDRESSES[1], 'fake_export_path'])]
|
||||||
|
|
||||||
SHARE_ACCESS = {
|
SHARE_ACCESS = {
|
||||||
'access_type': 'user',
|
'access_type': 'user',
|
||||||
'access_to': [LIF_ADDRESSES[0]]
|
'access_to': [LIF_ADDRESSES[0]]
|
||||||
|
|
|
@ -15,18 +15,19 @@
|
||||||
|
|
||||||
SHARE_NAME = 'fake_share'
|
SHARE_NAME = 'fake_share'
|
||||||
SHARE_ID = '9dba208c-9aa7-11e4-89d3-123b93f75cba'
|
SHARE_ID = '9dba208c-9aa7-11e4-89d3-123b93f75cba'
|
||||||
SHARE_ADDRESS = '10.10.10.10'
|
SHARE_ADDRESS_1 = '10.10.10.10'
|
||||||
|
SHARE_ADDRESS_2 = '10.10.10.20'
|
||||||
CLIENT_ADDRESS_1 = '20.20.20.10'
|
CLIENT_ADDRESS_1 = '20.20.20.10'
|
||||||
CLIENT_ADDRESS_2 = '20.20.20.20'
|
CLIENT_ADDRESS_2 = '20.20.20.20'
|
||||||
|
|
||||||
CIFS_SHARE = {
|
CIFS_SHARE = {
|
||||||
'export_location': '//%s/%s' % (SHARE_ADDRESS, SHARE_NAME),
|
'export_location': r'\\%s\%s' % (SHARE_ADDRESS_1, SHARE_NAME),
|
||||||
'id': SHARE_ID
|
'id': SHARE_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
NFS_SHARE_PATH = '/%s' % SHARE_NAME
|
NFS_SHARE_PATH = '/%s' % SHARE_NAME
|
||||||
NFS_SHARE = {
|
NFS_SHARE = {
|
||||||
'export_location': '%s:%s' % (SHARE_ADDRESS, NFS_SHARE_PATH),
|
'export_location': '%s:%s' % (SHARE_ADDRESS_1, NFS_SHARE_PATH),
|
||||||
'id': SHARE_ID
|
'id': SHARE_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,29 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||||
|
|
||||||
def test_create_share(self):
|
def test_create_share(self):
|
||||||
|
|
||||||
result = self.helper.create_share(fake.SHARE_NAME, fake.SHARE_ADDRESS)
|
result = self.helper.create_share(fake.SHARE_NAME,
|
||||||
|
[fake.SHARE_ADDRESS_1])
|
||||||
|
|
||||||
|
expected = [r'\\%s\%s' % (fake.SHARE_ADDRESS_1, fake.SHARE_NAME)]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
self.mock_client.create_cifs_share.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
self.mock_client.remove_cifs_share_access.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME, 'Everyone')
|
||||||
|
|
||||||
|
def test_create_share_multiple(self):
|
||||||
|
|
||||||
|
result = self.helper.create_share(fake.SHARE_NAME,
|
||||||
|
[fake.SHARE_ADDRESS_1,
|
||||||
|
fake.SHARE_ADDRESS_2])
|
||||||
|
|
||||||
|
expected = [r'\\%s\%s' % (fake.SHARE_ADDRESS_1, fake.SHARE_NAME),
|
||||||
|
r'\\%s\%s' % (fake.SHARE_ADDRESS_2, fake.SHARE_NAME)]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
self.mock_client.create_cifs_share.assert_called_once_with(
|
self.mock_client.create_cifs_share.assert_called_once_with(
|
||||||
fake.SHARE_NAME)
|
fake.SHARE_NAME)
|
||||||
self.mock_client.remove_cifs_share_access.assert_called_once_with(
|
self.mock_client.remove_cifs_share_access.assert_called_once_with(
|
||||||
fake.SHARE_NAME, 'Everyone')
|
fake.SHARE_NAME, 'Everyone')
|
||||||
self.assertEqual('//%s/%s' % (fake.SHARE_ADDRESS, fake.SHARE_NAME),
|
|
||||||
result)
|
|
||||||
|
|
||||||
def test_delete_share(self):
|
def test_delete_share(self):
|
||||||
|
|
||||||
|
@ -147,7 +162,7 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||||
def test_get_target(self):
|
def test_get_target(self):
|
||||||
|
|
||||||
target = self.helper.get_target(fake.CIFS_SHARE)
|
target = self.helper.get_target(fake.CIFS_SHARE)
|
||||||
self.assertEqual(fake.SHARE_ADDRESS, target)
|
self.assertEqual(fake.SHARE_ADDRESS_1, target)
|
||||||
|
|
||||||
def test_get_target_missing_location(self):
|
def test_get_target_missing_location(self):
|
||||||
|
|
||||||
|
@ -157,7 +172,17 @@ class NetAppClusteredCIFSHelperTestCase(test.TestCase):
|
||||||
def test_get_export_location(self):
|
def test_get_export_location(self):
|
||||||
|
|
||||||
host_ip, share_name = self.helper._get_export_location(fake.CIFS_SHARE)
|
host_ip, share_name = self.helper._get_export_location(fake.CIFS_SHARE)
|
||||||
self.assertEqual(fake.SHARE_ADDRESS, host_ip)
|
self.assertEqual(fake.SHARE_ADDRESS_1, host_ip)
|
||||||
|
self.assertEqual(fake.SHARE_NAME, share_name)
|
||||||
|
|
||||||
|
def test_get_export_location_legacy_forward_slashes(self):
|
||||||
|
|
||||||
|
fake_share = fake.CIFS_SHARE.copy()
|
||||||
|
fake_share['export_location'] = fake_share['export_location'].replace(
|
||||||
|
'\\', '/')
|
||||||
|
|
||||||
|
host_ip, share_name = self.helper._get_export_location(fake_share)
|
||||||
|
self.assertEqual(fake.SHARE_ADDRESS_1, host_ip)
|
||||||
self.assertEqual(fake.SHARE_NAME, share_name)
|
self.assertEqual(fake.SHARE_NAME, share_name)
|
||||||
|
|
||||||
def test_get_export_location_missing_location(self):
|
def test_get_export_location_missing_location(self):
|
||||||
|
|
|
@ -41,10 +41,24 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
||||||
self.mock_client.get_volume_junction_path.return_value = (
|
self.mock_client.get_volume_junction_path.return_value = (
|
||||||
fake.NFS_SHARE_PATH)
|
fake.NFS_SHARE_PATH)
|
||||||
|
|
||||||
result = self.helper.create_share(fake.SHARE_NAME, fake.SHARE_ADDRESS)
|
result = self.helper.create_share(fake.SHARE_NAME,
|
||||||
|
[fake.SHARE_ADDRESS_1])
|
||||||
|
|
||||||
self.assertEqual(':'.join([fake.SHARE_ADDRESS, fake.NFS_SHARE_PATH]),
|
expected = [':'.join([fake.SHARE_ADDRESS_1, fake.NFS_SHARE_PATH])]
|
||||||
result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_create_share_multiple(self):
|
||||||
|
|
||||||
|
self.mock_client.get_volume_junction_path.return_value = (
|
||||||
|
fake.NFS_SHARE_PATH)
|
||||||
|
|
||||||
|
result = self.helper.create_share(fake.SHARE_NAME,
|
||||||
|
[fake.SHARE_ADDRESS_1,
|
||||||
|
fake.SHARE_ADDRESS_2])
|
||||||
|
|
||||||
|
expected = [':'.join([fake.SHARE_ADDRESS_1, fake.NFS_SHARE_PATH]),
|
||||||
|
':'.join([fake.SHARE_ADDRESS_2, fake.NFS_SHARE_PATH])]
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
def test_delete_share(self):
|
def test_delete_share(self):
|
||||||
|
|
||||||
|
@ -123,7 +137,7 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
||||||
def test_get_target(self):
|
def test_get_target(self):
|
||||||
|
|
||||||
target = self.helper.get_target(fake.NFS_SHARE)
|
target = self.helper.get_target(fake.NFS_SHARE)
|
||||||
self.assertEqual(fake.SHARE_ADDRESS, target)
|
self.assertEqual(fake.SHARE_ADDRESS_1, target)
|
||||||
|
|
||||||
def test_get_target_missing_location(self):
|
def test_get_target_missing_location(self):
|
||||||
|
|
||||||
|
@ -154,7 +168,7 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
|
||||||
|
|
||||||
host_ip, export_path = self.helper._get_export_location(
|
host_ip, export_path = self.helper._get_export_location(
|
||||||
fake.NFS_SHARE)
|
fake.NFS_SHARE)
|
||||||
self.assertEqual(fake.SHARE_ADDRESS, host_ip)
|
self.assertEqual(fake.SHARE_ADDRESS_1, host_ip)
|
||||||
self.assertEqual('/' + fake.SHARE_NAME, export_path)
|
self.assertEqual('/' + fake.SHARE_NAME, export_path)
|
||||||
|
|
||||||
def test_get_export_location_missing_location(self):
|
def test_get_export_location_missing_location(self):
|
||||||
|
|
Loading…
Reference in New Issue