Early validate for CIFS without security service.

To create a share with protocol CIFS it is mandatory to add a security
service to the share network beforehand. If this is forgotten the share
ends up in error. Validate the security service association based on
share protocol from given driver. Raise invalid request exception if
needed association between share_network and security_service is
missing for given driver e.g. ONTAP.

DocImpact

Closes-Bug: #1900752
Change-Id: Ib7e9850e6439ee5d04f826d129afb1ab06950ce7
This commit is contained in:
kpdev 2021-02-22 08:29:43 +01:00
parent 8adba915a5
commit 18668d2465
14 changed files with 137 additions and 0 deletions

View File

@ -131,6 +131,12 @@ class Detail(object):
'024', '024',
_("No default share type has been made available. " _("No default share type has been made available. "
"You must specify a share type for creating shares.")) "You must specify a share type for creating shares."))
MISSING_SECURITY_SERVICE = (
'025',
_("Share Driver failed to create share because a security service "
"has not been added to the share network used. Please add a "
"security service to the share network."))
ALL = ( ALL = (
UNKNOWN_ERROR, UNKNOWN_ERROR,
NO_VALID_HOST, NO_VALID_HOST,
@ -156,6 +162,7 @@ class Detail(object):
UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE, UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE,
SECURITY_SERVICE_FAILED_AUTH, SECURITY_SERVICE_FAILED_AUTH,
NO_DEFAULT_SHARE_TYPE, NO_DEFAULT_SHARE_TYPE,
MISSING_SECURITY_SERVICE,
) )
# Exception and detail mappings # Exception and detail mappings

View File

@ -268,6 +268,7 @@ class ShareDriver(object):
# in-use share networks. This property will be saved in every new share # in-use share networks. This property will be saved in every new share
# server. # server.
self.security_service_update_support = False self.security_service_update_support = False
self.dhss_mandatory_security_service_association = {}
self.pools = [] self.pools = []
if self.configuration: if self.configuration:

View File

@ -87,6 +87,9 @@ class EMCShareDriver(driver.ShareDriver):
super(EMCShareDriver, self).__init__( super(EMCShareDriver, self).__init__(
self.plugin.driver_handles_share_servers, *args, **kwargs) self.plugin.driver_handles_share_servers, *args, **kwargs)
self.dhss_mandatory_security_service_association = (
self.plugin.dhss_mandatory_security_service_association)
if hasattr(self.plugin, 'ipv6_implemented'): if hasattr(self.plugin, 'ipv6_implemented'):
self.ipv6_implemented = self.plugin.ipv6_implemented self.ipv6_implemented = self.plugin.ipv6_implemented

View File

@ -27,6 +27,7 @@ class StorageConnection(object):
# NOTE(vponomaryov): redefine 'driver_handles_share_servers' within # NOTE(vponomaryov): redefine 'driver_handles_share_servers' within
# plugin. # plugin.
self.driver_handles_share_servers = None self.driver_handles_share_servers = None
self.dhss_mandatory_security_service_association = {}
@abc.abstractmethod @abc.abstractmethod
def create_share(self, context, share, share_server): def create_share(self, context, share, share_server):

View File

@ -55,6 +55,7 @@ class IsilonStorageConnection(base.StorageConnection):
self._isilon_api = None self._isilon_api = None
self._isilon_api_class = isilon_api.IsilonApi self._isilon_api_class = isilon_api.IsilonApi
self.driver_handles_share_servers = False self.driver_handles_share_servers = False
self.dhss_mandatory_security_service_association = {}
def _get_container_path(self, share): def _get_container_path(self, share):
"""Return path to a container.""" """Return path to a container."""

View File

@ -84,6 +84,10 @@ class PowerMaxStorageConnection(driver.StorageConnection):
self.driver_handles_share_servers = True self.driver_handles_share_servers = True
self.port_conf = None self.port_conf = None
self.ipv6_implemented = True self.ipv6_implemented = True
self.dhss_mandatory_security_service_association = {
'nfs': None,
'cifs': 'active_directory',
}
def create_share(self, context, share, share_server=None): def create_share(self, context, share, share_server=None):
"""Create a share and export it based on protocol used.""" """Create a share and export it based on protocol used."""

View File

@ -107,6 +107,10 @@ class UnityStorageConnection(driver.StorageConnection):
# props from super class. # props from super class.
self.driver_handles_share_servers = (True, False) self.driver_handles_share_servers = (True, False)
self.dhss_mandatory_security_service_association = {
'nfs': None,
'cifs': 'active_directory',
}
def connect(self, emc_share_driver, context): def connect(self, emc_share_driver, context):
"""Connect to Unity storage.""" """Connect to Unity storage."""

View File

@ -81,6 +81,10 @@ class VNXStorageConnection(driver.StorageConnection):
self.driver_handles_share_servers = True self.driver_handles_share_servers = True
self.port_conf = None self.port_conf = None
self.ipv6_implemented = True self.ipv6_implemented = True
self.dhss_mandatory_security_service_association = {
'nfs': None,
'cifs': 'active_directory',
}
def create_share(self, context, share, share_server=None): def create_share(self, context, share, share_server=None):
"""Create a share and export it based on protocol used.""" """Create a share and export it based on protocol used."""

View File

@ -38,6 +38,10 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
# NetApp driver supports updating security service for in use share # NetApp driver supports updating security service for in use share
# networks. # networks.
self.security_service_update_support = True self.security_service_update_support = True
self.dhss_mandatory_security_service_association = {
'nfs': None,
'cifs': ['active_directory', 'ldap', 'kerberos', ]
}
def do_setup(self, context): def do_setup(self, context):
self.library.do_setup(context) self.library.do_setup(context)

View File

@ -35,6 +35,7 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
False, *args, **kwargs) False, *args, **kwargs)
self.library = lib_single_svm.NetAppCmodeSingleSVMFileStorageLibrary( self.library = lib_single_svm.NetAppCmodeSingleSVMFileStorageLibrary(
self.DRIVER_NAME, **kwargs) self.DRIVER_NAME, **kwargs)
self.dhss_mandatory_security_service_association = {}
def do_setup(self, context): def do_setup(self, context):
self.library.do_setup(context) self.library.do_setup(context)

View File

@ -2006,6 +2006,36 @@ class ShareManager(manager.SchedulerDependentManager):
else: else:
share_server = None share_server = None
if share_network_id and self.driver.driver_handles_share_servers:
proto = share_instance.get('share_proto').lower()
ret_types = (
self.driver.dhss_mandatory_security_service_association.get(
proto))
if ret_types:
share_network = self.db.share_network_get(context,
share_network_id)
share_network_ss = []
for security_service in share_network['security_services']:
share_network_ss.append(security_service['type'].lower())
for types in ret_types:
if types not in share_network_ss:
self.db.share_instance_update(
context, share_instance_id,
{'status': constants.STATUS_ERROR}
)
self.message_api.create(
context,
message_field.Action.CREATE,
share['project_id'],
resource_type=message_field.Resource.SHARE,
resource_id=share_id,
detail=(message_field.Detail
.MISSING_SECURITY_SERVICE))
raise exception.InvalidRequest(_(
"Share network security service association is "
"mandatory for protocol %s.") %
share_instance.get('share_proto'))
status = constants.STATUS_AVAILABLE status = constants.STATUS_AVAILABLE
try: try:
if snapshot_ref: if snapshot_ref:

View File

@ -26,6 +26,7 @@ from manila import test
class FakeConnection(base.StorageConnection): class FakeConnection(base.StorageConnection):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.ipv6_implemented = True self.ipv6_implemented = True
self.dhss_mandatory_security_service_association = {}
pass pass
@property @property

View File

@ -2255,6 +2255,72 @@ class ShareManagerTestCase(test.TestCase):
resource_id=shr['id'], resource_id=shr['id'],
detail=message_field.Detail.NO_SHARE_SERVER) detail=message_field.Detail.NO_SHARE_SERVER)
def test_create_share_instance_with_security_service_missing(self):
"""Test creation fails if security service association is missing."""
self.mock_object(self.share_manager, 'driver')
self.share_manager.driver.driver_handles_share_servers = True
self.share_manager.driver.\
dhss_mandatory_security_service_association = {
'fake_proto': ['fake_ss', 'fake_ss2', ]
}
ss_data = {
'name': 'fake_name',
'ou': 'fake_ou',
'domain': 'fake_domain',
'server': 'fake_server',
'dns_ip': 'fake_dns_ip',
'user': 'fake_user',
'type': 'fake_ss',
'password': 'fake_pass',
}
security_service = db_utils.create_security_service(**ss_data)
share_net = db_utils.create_share_network()
share_net_subnet = db_utils.create_share_network_subnet(
share_network_id=share_net['id'],
availability_zone_id=None,
)
db.share_network_add_security_service(context.get_admin_context(),
share_net['id'],
security_service['id'])
share = db_utils.create_share(
share_network_id=share_net['id'],
share_proto='fake_proto',
)
db_utils.create_share_server(
share_network_subnet_id=share_net_subnet['id'],
host=self.share_manager.host,
status=constants.STATUS_ERROR)
fake_server = {
'id': 'fake_srv_id',
'status': constants.STATUS_CREATING,
}
fake_metadata = {
'request_host': 'fake_host',
'share_type_id': 'fake_share_type_id',
}
self.mock_object(self.share_manager, '_build_server_metadata',
mock.Mock(return_value=fake_metadata))
self.mock_object(db, 'share_server_create',
mock.Mock(return_value=fake_server))
self.mock_object(self.share_manager, '_setup_server',
mock.Mock(return_value=fake_server))
self.assertRaises(
exception.InvalidRequest,
self.share_manager.create_share_instance,
self.context,
share.instance['id']
)
share = db.share_get(self.context, share['id'])
self.assertEqual(constants.STATUS_ERROR, share['status'])
self.share_manager.message_api.create.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
message_field.Action.CREATE,
str(share.project_id),
resource_type=message_field.Resource.SHARE,
resource_id=share['id'],
detail=message_field.Detail.MISSING_SECURITY_SERVICE)
@ddt.data( @ddt.data(
(True, 1, 3, 10, 0), (True, 1, 3, 10, 0),
(False, 1, 100, 5, 0), (False, 1, 100, 5, 0),
@ -2419,6 +2485,8 @@ class ShareManagerTestCase(test.TestCase):
share_srv share_srv
) )
self.share_manager.driver = driver_mock self.share_manager.driver = driver_mock
self.share_manager.driver.\
dhss_mandatory_security_service_association = {}
self.share_manager.create_share_instance(self.context, self.share_manager.create_share_instance(self.context,
share.instance['id']) share.instance['id'])
self.assertFalse(self.share_manager.driver.setup_network.called) self.assertFalse(self.share_manager.driver.setup_network.called)

View File

@ -0,0 +1,8 @@
---
fixes:
- |
For some drivers, to create a share with specific protocol it is mandatory
to add a security service to the share network beforehand. If this is
forgotten the share ends up in error. From now on, Manila won't allow
shares to be created when the specified protocol requires a specific
security service type that is not associated to the share network.