HP 3PAR driver handles shares servers

HP 3PAR driver can use driver_handle_share_servers=True
mode to automatically setup the FSIP using the IP
address, subnet and VLAN tag obtained from the share
network.

The driver_handles_share_servers=False is still supported
with a pre-configured IP address.

Implements Blueprint: hp3par-share-server-support

Change-Id: Iae63a22923a5d7622a8810629071f5ea9823b0ed
This commit is contained in:
Mark Sturdevant 2015-06-30 17:16:55 -07:00
parent 18fb649e4c
commit 7537951be6
5 changed files with 527 additions and 19 deletions

View File

@ -31,6 +31,7 @@ from manila.i18n import _LI
from manila.share import driver from manila.share import driver
from manila.share.drivers.hp import hp_3par_mediator from manila.share.drivers.hp import hp_3par_mediator
from manila.share import share_types from manila.share import share_types
from manila import utils
HP3PAR_OPTS = [ HP3PAR_OPTS = [
cfg.StrOpt('hp3par_api_url', cfg.StrOpt('hp3par_api_url',
@ -80,13 +81,19 @@ LOG = log.getLogger(__name__)
class HP3ParShareDriver(driver.ShareDriver): class HP3ParShareDriver(driver.ShareDriver):
"""HP 3PAR driver for Manila. """HP 3PAR driver for Manila.
Supports NFS and CIFS protocols on arrays with File Persona. Supports NFS and CIFS protocols on arrays with File Persona.
"""
VERSION = "1.0.01" Version history:
1.0.00 - Begin Liberty development (post-Kilo)
1.0.01 - Report thin/dedup/hp_flash_cache capabilities
1.0.02 - Add share server/share network support
"""
VERSION = "1.0.02"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(HP3ParShareDriver, self).__init__(False, *args, **kwargs) super(HP3ParShareDriver, self).__init__((True, False), *args, **kwargs)
self.configuration = kwargs.get('configuration', None) self.configuration = kwargs.get('configuration', None)
self.configuration.append_config_values(HP3PAR_OPTS) self.configuration.append_config_values(HP3PAR_OPTS)
@ -103,11 +110,13 @@ class HP3ParShareDriver(driver.ShareDriver):
{'driver_name': self.__class__.__name__, {'driver_name': self.__class__.__name__,
'version': self.VERSION}) 'version': self.VERSION})
self.share_ip_address = self.configuration.hp3par_share_ip_address if not self.driver_handles_share_servers:
if not self.share_ip_address: self.share_ip_address = self.configuration.hp3par_share_ip_address
raise exception.HP3ParInvalid( if not self.share_ip_address:
_("Unsupported configuration. " raise exception.HP3ParInvalid(
"hp3par_share_ip_address is not set.")) _("Unsupported configuration. "
"hp3par_share_ip_address must be set when "
"driver_handles_share_servers is False."))
mediator = hp_3par_mediator.HP3ParMediator( mediator = hp_3par_mediator.HP3ParMediator(
hp3par_username=self.configuration.hp3par_username, hp3par_username=self.configuration.hp3par_username,
@ -163,8 +172,60 @@ class HP3ParShareDriver(driver.ShareDriver):
return sha1.hexdigest() return sha1.hexdigest()
def get_network_allocations_number(self):
return 1
@staticmethod
def _validate_network_type(network_type):
if network_type not in ('flat', 'vlan', None):
reason = _('Invalid network type. %s is not supported by the '
'3PAR driver.')
raise exception.NetworkBadConfigurationException(
reason=reason % network_type)
def _setup_server(self, network_info, metadata=None):
LOG.debug("begin _setup_server with %s", network_info)
self._validate_network_type(network_info['network_type'])
ip = network_info['network_allocations'][0]['ip_address']
subnet = utils.cidr_to_netmask(network_info['cidr'])
vlantag = network_info['segmentation_id']
self._hp3par.create_fsip(ip, subnet, vlantag, self.fpg, self.vfs)
return {
'share_server_name': network_info['server_id'],
'share_server_id': network_info['server_id'],
'ip': ip,
'subnet': subnet,
'vlantag': vlantag if vlantag else 0,
'fpg': self.fpg,
'vfs': self.vfs,
}
def _teardown_server(self, server_details, security_services=None):
LOG.debug("begin _teardown_server with %s", server_details)
self._hp3par.remove_fsip(server_details.get('ip'),
server_details.get('fpg'),
server_details.get('vfs'))
def _get_share_ip(self, share_server):
return share_server['backend_details'].get('ip') if share_server else (
self.share_ip_address)
@staticmethod @staticmethod
def _build_export_location(protocol, ip, path): def _build_export_location(protocol, ip, path):
if not ip:
message = _('Failed to build export location due to missing IP.')
raise exception.InvalidInput(message)
if not path:
message = _('Failed to build export location due to missing path.')
raise exception.InvalidInput(message)
if protocol == 'NFS': if protocol == 'NFS':
location = ':'.join((ip, path)) location = ':'.join((ip, path))
elif protocol == 'CIFS': elif protocol == 'CIFS':
@ -173,6 +234,7 @@ class HP3ParShareDriver(driver.ShareDriver):
message = _('Invalid protocol. Expected NFS or CIFS. ' message = _('Invalid protocol. Expected NFS or CIFS. '
'Got %s.') % protocol 'Got %s.') % protocol
raise exception.InvalidInput(message) raise exception.InvalidInput(message)
return location return location
@staticmethod @staticmethod
@ -194,7 +256,7 @@ class HP3ParShareDriver(driver.ShareDriver):
def create_share(self, context, share, share_server=None): def create_share(self, context, share, share_server=None):
"""Is called to create share.""" """Is called to create share."""
ip = self.share_ip_address ip = self._get_share_ip(share_server)
protocol = share['share_proto'] protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share) extra_specs = share_types.get_extra_specs_from_share(share)
@ -215,7 +277,7 @@ class HP3ParShareDriver(driver.ShareDriver):
share_server=None): share_server=None):
"""Is called to create share from snapshot.""" """Is called to create share from snapshot."""
ip = self.share_ip_address ip = self._get_share_ip(share_server)
protocol = share['share_proto'] protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share) extra_specs = share_types.get_extra_specs_from_share(share)

View File

@ -53,8 +53,16 @@ SMB_EXTRA_SPECS_MAP = {
class HP3ParMediator(object): class HP3ParMediator(object):
"""3PAR client-facing code for the 3PAR driver.
VERSION = "1.0.01" Version history:
1.0.00 - Begin Liberty development (post-Kilo)
1.0.01 - Report thin/dedup/hp_flash_cache capabilities
1.0.02 - Add share server/share network support
"""
VERSION = "1.0.02"
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -828,3 +836,88 @@ class HP3ParMediator(object):
self._change_access(DENY, project_id, share_id, share_proto, self._change_access(DENY, project_id, share_id, share_proto,
access_type, access_to, fpg, vfs) access_type, access_to, fpg, vfs)
def fsip_exists(self, fsip):
"""Try to get FSIP. Return True if it exists."""
vfs = fsip['vfs']
fpg = fsip['fspool']
try:
result = self._client.getfsip(vfs, fpg=fpg)
LOG.debug("getfsip result: %s", result)
except Exception as e:
LOG.exception(e)
msg = (_('Failed to get FSIPs for FPG/VFS %(fspool)s/%(vfs)s.') %
fsip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
for member in result['members']:
if all(item in member.items() for item in fsip.items()):
return True
return False
def create_fsip(self, ip, subnet, vlantag, fpg, vfs):
vlantag_str = six.text_type(vlantag) if vlantag else '0'
# Try to create it. It's OK if it already exists.
try:
result = self._client.createfsip(ip,
subnet,
vfs,
fpg=fpg,
vlantag=vlantag_str)
LOG.debug("createfsip result: %s", result)
except Exception as e:
LOG.exception(e)
msg = (_('Failed to create FSIP for %s') % ip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# Verify that it really exists.
fsip = {
'fspool': fpg,
'vfs': vfs,
'address': ip,
'prefixLen': subnet,
'vlanTag': vlantag_str,
}
if not self.fsip_exists(fsip):
msg = (_('Failed to get FSIP after creating it for '
'FPG/VFS/IP/subnet/VLAN '
'%(fspool)s/%(vfs)s/'
'%(address)s/%(prefixLen)s/%(vlanTag)s.') % fsip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
def remove_fsip(self, ip, fpg, vfs):
if not (vfs and ip):
# If there is no VFS and/or IP, then there is no FSIP to remove.
return
try:
result = self._client.removefsip(vfs, ip, fpg=fpg)
LOG.debug("removefsip result: %s", result)
except Exception as e:
LOG.exception(e)
msg = (_('Failed to remove FSIP %s') % ip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)
# Verify that it really no longer exists.
fsip = {
'fspool': fpg,
'vfs': vfs,
'address': ip,
}
if self.fsip_exists(fsip):
msg = (_('Failed to remove FSIP for FPG/VFS/IP '
'%(fspool)s/%(vfs)s/%(address)s.') % fsip)
LOG.exception(msg)
raise exception.ShareBackendException(msg=msg)

View File

@ -25,11 +25,16 @@ API_URL = 'https://1.2.3.4:8080/api/v1'
TIMEOUT = 60 TIMEOUT = 60
PORT = 22 PORT = 22
SHARE_TYPE_ID = 123456789 SHARE_TYPE_ID = 123456789
CIDR_PREFIX = '24'
# Constants to use with Mock and expect in results # Constants to use with Mock and expect in results
EXPECTED_IP_10203040 = '10.20.30.40' EXPECTED_IP_10203040 = '10.20.30.40'
EXPECTED_IP_1234 = '1.2.3.4' EXPECTED_IP_1234 = '1.2.3.4'
EXPECTED_IP_127 = '127.0.0.1' EXPECTED_IP_127 = '127.0.0.1'
EXPECTED_SUBNET = '255.255.255.0' # based on CIDR_PREFIX above
EXPECTED_VLAN_TYPE = 'vlan'
EXPECTED_VLAN_TAG = '101'
EXPECTED_SERVER_ID = '1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e'
EXPECTED_PROJECT_ID = 'osf-nfs-project-id' EXPECTED_PROJECT_ID = 'osf-nfs-project-id'
EXPECTED_SHARE_ID = 'osf-share-id' EXPECTED_SHARE_ID = 'osf-share-id'
EXPECTED_SHARE_NAME = 'share-name' EXPECTED_SHARE_NAME = 'share-name'
@ -50,6 +55,22 @@ GET_FSQUOTA = {'message': None,
'total': 1, 'total': 1,
'members': [{'hardBlock': '1024', 'softBlock': '1024'}]} 'members': [{'hardBlock': '1024', 'softBlock': '1024'}]}
EXPECTED_FSIP = {
'fspool': EXPECTED_FPG,
'vfs': EXPECTED_VFS,
'address': EXPECTED_IP_1234,
'prefixLen': EXPECTED_SUBNET,
'vlanTag': EXPECTED_VLAN_TAG,
}
OTHER_FSIP = {
'fspool': EXPECTED_FPG,
'vfs': EXPECTED_VFS,
'address': '9.9.9.9',
'prefixLen': EXPECTED_SUBNET,
'vlanTag': EXPECTED_VLAN_TAG,
}
NFS_SHARE_INFO = { NFS_SHARE_INFO = {
'project_id': EXPECTED_PROJECT_ID, 'project_id': EXPECTED_PROJECT_ID,
'id': EXPECTED_SHARE_ID, 'id': EXPECTED_SHARE_ID,

View File

@ -34,7 +34,7 @@ class HP3ParDriverTestCase(test.TestCase):
# Create a mock configuration with attributes and a safe_get() # Create a mock configuration with attributes and a safe_get()
self.conf = mock.Mock() self.conf = mock.Mock()
self.conf.driver_handles_share_servers = False self.conf.driver_handles_share_servers = True
self.conf.hp3par_debug = constants.EXPECTED_HP_DEBUG self.conf.hp3par_debug = constants.EXPECTED_HP_DEBUG
self.conf.hp3par_username = constants.USERNAME self.conf.hp3par_username = constants.USERNAME
self.conf.hp3par_password = constants.PASSWORD self.conf.hp3par_password = constants.PASSWORD
@ -45,7 +45,7 @@ class HP3ParDriverTestCase(test.TestCase):
self.conf.hp3par_fpg = constants.EXPECTED_FPG self.conf.hp3par_fpg = constants.EXPECTED_FPG
self.conf.hp3par_san_ssh_port = constants.PORT self.conf.hp3par_san_ssh_port = constants.PORT
self.conf.ssh_conn_timeout = constants.TIMEOUT self.conf.ssh_conn_timeout = constants.TIMEOUT
self.conf.hp3par_share_ip_address = constants.EXPECTED_IP_10203040 self.conf.hp3par_share_ip_address = None
self.conf.hp3par_fstore_per_share = False self.conf.hp3par_fstore_per_share = False
self.conf.network_config_group = 'test_network_config_group' self.conf.network_config_group = 'test_network_config_group'
@ -89,6 +89,21 @@ class HP3ParDriverTestCase(test.TestCase):
self.assertEqual(constants.EXPECTED_VFS, self.driver.vfs) self.assertEqual(constants.EXPECTED_VFS, self.driver.vfs)
def test_driver_setup_no_dhss_success(self):
"""Driver do_setup without any errors with dhss=False."""
self.conf.driver_handles_share_servers = False
self.conf.hp3par_share_ip_address = constants.EXPECTED_IP_10203040
self.test_driver_setup_success()
def test_driver_setup_no_ss_no_ip(self):
"""Configured IP address is required for dhss=False."""
self.conf.driver_handles_share_servers = False
self.assertRaises(exception.HP3ParInvalid,
self.driver.do_setup, None)
def test_driver_with_setup_error(self): def test_driver_with_setup_error(self):
"""Driver do_setup when the mediator setup fails.""" """Driver do_setup when the mediator setup fails."""
@ -145,7 +160,6 @@ class HP3ParDriverTestCase(test.TestCase):
self.driver._hp3par = self.mock_mediator self.driver._hp3par = self.mock_mediator
self.driver.vfs = constants.EXPECTED_VFS self.driver.vfs = constants.EXPECTED_VFS
self.driver.fpg = constants.EXPECTED_FPG self.driver.fpg = constants.EXPECTED_FPG
self.driver.share_ip_address = self.conf.hp3par_share_ip_address
self.mock_object(hp3pardriver, 'share_types') self.mock_object(hp3pardriver, 'share_types')
get_extra_specs = hp3pardriver.share_types.get_extra_specs_from_share get_extra_specs = hp3pardriver.share_types.get_extra_specs_from_share
get_extra_specs.return_value = constants.EXPECTED_EXTRA_SPECS get_extra_specs.return_value = constants.EXPECTED_EXTRA_SPECS
@ -154,7 +168,8 @@ class HP3ParDriverTestCase(test.TestCase):
expected_share_id, expected_size): expected_share_id, expected_size):
"""Re-usable code for create share.""" """Re-usable code for create share."""
context = None context = None
share_server = None share_server = {
'backend_details': {'ip': constants.EXPECTED_IP_10203040}}
share = { share = {
'display_name': constants.EXPECTED_SHARE_NAME, 'display_name': constants.EXPECTED_SHARE_NAME,
'host': constants.EXPECTED_HOST, 'host': constants.EXPECTED_HOST,
@ -175,7 +190,11 @@ class HP3ParDriverTestCase(test.TestCase):
expected_size): expected_size):
"""Re-usable code for create share from snapshot.""" """Re-usable code for create share from snapshot."""
context = None context = None
share_server = None share_server = {
'backend_details': {
'ip': constants.EXPECTED_IP_10203040,
},
}
share = { share = {
'display_name': constants.EXPECTED_SHARE_NAME, 'display_name': constants.EXPECTED_SHARE_NAME,
'host': constants.EXPECTED_HOST, 'host': constants.EXPECTED_HOST,
@ -436,6 +455,37 @@ class HP3ParDriverTestCase(test.TestCase):
] ]
self.mock_mediator.assert_has_calls(expected_calls) self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_get_share_stats_not_ready(self):
"""Protect against stats update before driver is ready."""
self.mock_object(hp3pardriver, 'LOG')
expected_result = {
'driver_handles_share_servers': True,
'QoS_support': False,
'driver_version': self.driver.VERSION,
'free_capacity_gb': 0,
'max_over_subscription_ratio': None,
'reserved_percentage': 0,
'provisioned_capacity_gb': 0,
'share_backend_name': 'HP_3PAR',
'snapshot_support': True,
'storage_protocol': 'NFS_CIFS',
'thin_provisioning': True,
'total_capacity_gb': 0,
'vendor_name': 'HP',
'pools': None,
}
result = self.driver.get_share_stats(refresh=True)
self.assertEqual(expected_result, result)
expected_calls = [
mock.call.info('Skipping capacity and capabilities update. '
'Setup has not completed.')
]
hp3pardriver.LOG.assert_has_calls(expected_calls)
def test_driver_get_share_stats_no_refresh(self): def test_driver_get_share_stats_no_refresh(self):
"""Driver does not call mediator when refresh=False.""" """Driver does not call mediator when refresh=False."""
@ -464,8 +514,8 @@ class HP3ParDriverTestCase(test.TestCase):
} }
expected_result = { expected_result = {
'driver_handles_share_servers': True,
'QoS_support': False, 'QoS_support': False,
'driver_handles_share_servers': False,
'driver_version': expected_version, 'driver_version': expected_version,
'free_capacity_gb': expected_free, 'free_capacity_gb': expected_free,
'max_over_subscription_ratio': None, 'max_over_subscription_ratio': None,
@ -500,7 +550,7 @@ class HP3ParDriverTestCase(test.TestCase):
expected_result = { expected_result = {
'QoS_support': False, 'QoS_support': False,
'driver_handles_share_servers': False, 'driver_handles_share_servers': True,
'driver_version': expected_version, 'driver_version': expected_version,
'free_capacity_gb': 0, 'free_capacity_gb': 0,
'max_over_subscription_ratio': None, 'max_over_subscription_ratio': None,
@ -549,3 +599,84 @@ class HP3ParDriverTestCase(test.TestCase):
# Don't test with same regex as the code uses. # Don't test with same regex as the code uses.
for c in "'\".,;": for c in "'\".,;":
self.assertNotIn(c, comment) self.assertNotIn(c, comment)
def test_get_network_allocations_number(self):
self.assertEqual(1, self.driver.get_network_allocations_number())
def test_build_export_location_bad_protocol(self):
self.assertRaises(exception.InvalidInput,
self.driver._build_export_location,
"BOGUS",
constants.EXPECTED_IP_1234,
constants.EXPECTED_SHARE_PATH)
def test_build_export_location_bad_ip(self):
self.assertRaises(exception.InvalidInput,
self.driver._build_export_location,
constants.NFS,
None,
None)
def test_build_export_location_bad_path(self):
self.assertRaises(exception.InvalidInput,
self.driver._build_export_location,
constants.NFS,
constants.EXPECTED_IP_1234,
None)
def test_setup_server(self):
"""Setup server by creating a new FSIP."""
self.init_driver()
network_info = {
'network_allocations': [
{'ip_address': constants.EXPECTED_IP_1234}],
'cidr': '/'.join((constants.EXPECTED_IP_1234,
constants.CIDR_PREFIX)),
'network_type': constants.EXPECTED_VLAN_TYPE,
'segmentation_id': constants.EXPECTED_VLAN_TAG,
'server_id': constants.EXPECTED_SERVER_ID,
}
expected_result = {
'share_server_name': constants.EXPECTED_SERVER_ID,
'share_server_id': constants.EXPECTED_SERVER_ID,
'ip': constants.EXPECTED_IP_1234,
'subnet': constants.EXPECTED_SUBNET,
'vlantag': constants.EXPECTED_VLAN_TAG,
'fpg': constants.EXPECTED_FPG,
'vfs': constants.EXPECTED_VFS,
}
result = self.driver._setup_server(network_info)
expected_calls = [
mock.call.create_fsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VLAN_TAG,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)
self.assertEqual(expected_result, result)
def test_teardown_server(self):
self.init_driver()
server_details = {
'ip': constants.EXPECTED_IP_1234,
'fpg': constants.EXPECTED_FPG,
'vfs': constants.EXPECTED_VFS,
}
self.driver._teardown_server(server_details)
expected_calls = [
mock.call.remove_fsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)

View File

@ -1432,6 +1432,207 @@ class HP3ParMediatorTestCase(test.TestCase):
self.assertEqual(expected_result, result) self.assertEqual(expected_result, result)
def test_fsip_exists(self):
self.init_mediator()
# Make the result member a superset of the fsip items.
fsip_plus = constants.EXPECTED_FSIP.copy()
fsip_plus.update({'k': 'v', 'k2': 'v2'})
self.mock_client.getfsip.return_value = {
'total': 3,
'members': [{'bogus1': 1}, fsip_plus, {'bogus2': '2'}]
}
self.assertTrue(self.mediator.fsip_exists(constants.EXPECTED_FSIP))
self.mock_client.getfsip.assert_called_once_with(
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG)
def test_fsip_does_not_exist(self):
self.init_mediator()
self.mock_client.getfsip.return_value = {
'total': 3,
'members': [{'bogus1': 1}, constants.OTHER_FSIP, {'bogus2': '2'}]
}
self.assertFalse(self.mediator.fsip_exists(constants.EXPECTED_FSIP))
self.mock_client.getfsip.assert_called_once_with(
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG)
def test_fsip_exists_exception(self):
self.init_mediator()
class FakeException(Exception):
pass
self.mock_client.getfsip.side_effect = FakeException()
self.assertRaises(exception.ShareBackendException,
self.mediator.fsip_exists,
constants.EXPECTED_FSIP)
self.mock_client.getfsip.assert_called_once_with(
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG)
def test_create_fsip_success(self):
self.init_mediator()
# Make the result member a superset of the fsip items.
fsip_plus = constants.EXPECTED_FSIP.copy()
fsip_plus.update({'k': 'v', 'k2': 'v2'})
self.mock_client.getfsip.return_value = {
'total': 3,
'members': [{'bogus1': 1}, fsip_plus, {'bogus2': '2'}]
}
self.mediator.create_fsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VLAN_TAG,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
self.mock_client.getfsip.assert_called_once_with(
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG)
expected_calls = [
mock.call.createfsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
vlantag=constants.EXPECTED_VLAN_TAG),
mock.call.getfsip(constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG),
]
self.mock_client.assert_has_calls(expected_calls)
def test_create_fsip_exception(self):
self.init_mediator()
class FakeException(Exception):
pass
self.mock_client.createfsip.side_effect = FakeException()
self.assertRaises(exception.ShareBackendException,
self.mediator.create_fsip,
constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VLAN_TAG,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
self.mock_client.createfsip.assert_called_once_with(
constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
vlantag=constants.EXPECTED_VLAN_TAG)
def test_create_fsip_get_none(self):
self.init_mediator()
self.mock_client.getfsip.return_value = {'members': []}
self.assertRaises(exception.ShareBackendException,
self.mediator.create_fsip,
constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VLAN_TAG,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.createfsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_SUBNET,
constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG,
vlantag=constants.EXPECTED_VLAN_TAG),
mock.call.getfsip(constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG),
]
self.mock_client.assert_has_calls(expected_calls)
def test_remove_fsip_success(self):
self.init_mediator()
self.mock_client.getfsip.return_value = {
'members': [constants.OTHER_FSIP]
}
self.mediator.remove_fsip(constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.removefsip(constants.EXPECTED_VFS,
constants.EXPECTED_IP_1234,
fpg=constants.EXPECTED_FPG),
mock.call.getfsip(constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG),
]
self.mock_client.assert_has_calls(expected_calls)
@ddt.data(('ip', None),
('ip', ''),
(None, 'vfs'),
('', 'vfs'),
(None, None),
('', ''))
@ddt.unpack
def test_remove_fsip_without_ip_or_vfs(self, ip, vfs):
self.init_mediator()
self.mediator.remove_fsip(ip, constants.EXPECTED_FPG, vfs)
self.assertFalse(self.mock_client.removefsip.called)
def test_remove_fsip_not_gone(self):
self.init_mediator()
self.mock_client.getfsip.return_value = {
'members': [constants.EXPECTED_FSIP]
}
self.assertRaises(exception.ShareBackendException,
self.mediator.remove_fsip,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
expected_calls = [
mock.call.removefsip(constants.EXPECTED_VFS,
constants.EXPECTED_IP_1234,
fpg=constants.EXPECTED_FPG),
mock.call.getfsip(constants.EXPECTED_VFS,
fpg=constants.EXPECTED_FPG),
]
self.mock_client.assert_has_calls(expected_calls)
def test_remove_fsip_exception(self):
self.init_mediator()
class FakeException(Exception):
pass
self.mock_client.removefsip.side_effect = FakeException()
self.assertRaises(exception.ShareBackendException,
self.mediator.remove_fsip,
constants.EXPECTED_IP_1234,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
self.mock_client.removefsip.assert_called_once_with(
constants.EXPECTED_VFS,
constants.EXPECTED_IP_1234,
fpg=constants.EXPECTED_FPG)
class OptionMatcher(object): class OptionMatcher(object):
"""Options string order can vary. Compare as lists.""" """Options string order can vary. Compare as lists."""