HP 3PAR uses scoped extra-specs to influence share creation options

Add support for share type extra-specs that can be used to influence
share creation options that are specific to 3PAR. These extra-specs
are scoped (use 'hp_3par:' prefix).

CIFS extra-specs include 'smb_abe' (access based enumeration),
'smb_cache' and 'smb_ca' (continuous availability). 'smb_ca' is
ignored until hp3parclient > 3.2.1.

The NFS extra-spec is 'nfs_options'. This value is a comma-separated
string as needed for the 3PAR createfshare -options value in the CLI.
The following values are not allowed because the driver uses
specific settings (for now): (no_)root_squash, (in)secure.
The following values are not allowed per HP 3PAR CLI support
requirements: no_subtree_check, fsid.
'rw' and 'ro' are not allowed because read-only access should be
controlled by Manila and not by extra-specs.
Additional value validation is left to the 3PAR CLI to handle.

Implements Blueprint: hp3par-extra-specs
Change-Id: Ia311cae718bf3998346b068aebb08a64924195de
This commit is contained in:
Mark Sturdevant 2015-07-14 11:21:28 -07:00
parent a778199f01
commit 8895ea2254
6 changed files with 367 additions and 71 deletions

View File

@ -114,6 +114,72 @@ and using the shares. This includes:
- Configuring the Manila host networking properly for IP forwarding
- Configuring the VFS networking properly for client subnets
Share Types
-----------
When creating a share, a share type can be specified to determine where and
how the share will be created. If a share type is not specified, the
`default_share_type` set in the Manila configuration file is used.
Manila requires that the share type includes the
`driver_handles_share_servers` extra-spec. This ensures that the share
will be created on a backend that supports the requested
driver_handles_share_servers (share networks) capability.
For the HP 3PAR driver, this must be set to False.
Another common Manila extra-spec used to determine where a share is created
is `share_backend_name`. When this extra-spec is defined in the share type,
the share will be created on a backend with a matching share_backend_name.
Scoped extra-specs are used to influence vendor-specific implementation
details. Scoped extra-specs use a prefix followed by a colon. For HP 3PAR
these extra-specs have a prefix of `hp_3par`.
The following HP 3PAR extra-specs are used when creating CIFS (SMB) shares:
- `hp3_par:smb_access_based_enum` = true or false
- `hp3_par:smb_continuous_avail` = true or false
- `hp3_par:smb_cache` = off, manual, optimized or auto
`smb_access_based_enum` (Access Based Enumeration) specifies if users can see
only the files and directories to which they have been allowed access on the
shares. The default is `false`.
`smb_continuous_avail` (Continuous Availability) specifies if SMB3 continuous
availability features should be enabled for this share. If not specified,
the default is `true`. This setting will be ignored with hp3parclient 3.2.1
or earlier.
`smb_cache` specifies client-side caching for offline files. Valid values are:
* `off`: The client must not cache any files from this share. The share is
configured to disallow caching.
* `manual`: The client must allow only manual caching for the files open from
this share.
* `optimized`: The client may cache every file that it opens from
this share. Also, the client may satisfy the file requests from its
local cache. The share is configured to allow automatic caching
of programs and documents.
* `auto`: The client may cache every file that it opens from this
share. The share is configured to allow automatic caching of
documents.
* If this is not specified, the default is `manual`.
The following HP 3PAR extra-specs are used when creating NFS shares:
- `hp3_par:nfs_options` = Comma separated list of NFS export options
The NFS export options have the following limitations:
* `ro` and `rw` are not allowed (Manila will determine the read-only option)
* `no_subtree_check` and `fsid` are not allowed per HP 3PAR CLI support
* `(in)secure` and `(no_)root_squash` are not allowed because the HP 3PAR
driver controls those settings
All other NFS options are forwarded to the HP 3PAR as part of share creation.
The HP 3PAR will do additional validation at share creation time. Refer to
HP 3PAR CLI help for more details.
The :mod:`manila.share.drivers.hp.hp_3par_driver` Module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -28,6 +28,7 @@ from manila.i18n import _
from manila.i18n import _LI
from manila.share import driver
from manila.share.drivers.hp import hp_3par_mediator
from manila.share import share_types
HP3PAR_OPTS = [
cfg.StrOpt('hp3par_api_url',
@ -169,10 +170,13 @@ class HP3ParShareDriver(driver.ShareDriver):
ip = self.share_ip_address
protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share)
path = self._hp3par.create_share(
share['project_id'],
share['id'],
protocol,
extra_specs,
self.fpg, self.vfs,
size=share['size']
)
@ -186,9 +190,12 @@ class HP3ParShareDriver(driver.ShareDriver):
ip = self.share_ip_address
protocol = share['share_proto']
extra_specs = share_types.get_extra_specs_from_share(share)
path = self._hp3par.create_share_from_snapshot(
share['id'],
protocol,
extra_specs,
snapshot['share']['project_id'],
snapshot['share']['id'],
snapshot['share']['share_proto'],

View File

@ -33,9 +33,13 @@ if hp3parclient:
LOG = log.getLogger(__name__)
MIN_CLIENT_VERSION = (3, 2, 1)
MIN_SMB_CA_VERSION = (3, 2, 2)
DENY = '-'
ALLOW = '+'
OPEN_STACK_MANILA_FSHARE = 'OpenStack Manila fshare'
CACHE = 'cache'
CONTINUOUS_AVAIL = 'continuous_avail'
ACCESS_BASED_ENUM = 'access_based_enum'
class HP3ParMediator(object):
@ -55,6 +59,7 @@ class HP3ParMediator(object):
self.ssh_conn_timeout = kwargs.get('ssh_conn_timeout')
self._client = None
self.client_version = None
@staticmethod
def no_client():
@ -68,11 +73,11 @@ class HP3ParMediator(object):
LOG.exception(msg)
raise exception.HP3ParInvalidClient(message=msg)
client_version = hp3parclient.version_tuple
if client_version < MIN_CLIENT_VERSION:
self.client_version = hp3parclient.version_tuple
if self.client_version < MIN_CLIENT_VERSION:
msg = (_('Invalid hp3parclient version found (%(found)s). '
'Version %(minimum)s or greater required.') %
{'found': '.'.join(map(six.text_type, client_version)),
{'found': '.'.join(map(six.text_type, self.client_version)),
'minimum': '.'.join(map(six.text_type,
MIN_CLIENT_VERSION))})
LOG.exception(msg)
@ -176,7 +181,68 @@ class HP3ParMediator(object):
else:
return 'osf-%s' % uid
def create_share(self, project_id, share_id, share_proto, fpg, vfs,
@staticmethod
def _get_nfs_options(extra_specs, readonly):
"""Validate the NFS extra_specs and return the options to use."""
nfs_options = extra_specs.get('hp_3par:nfs_options')
if nfs_options:
options = nfs_options.split(',')
else:
options = []
# rw, ro, and (no)root_squash (in)secure options are not allowed in
# extra_specs because they will be forcibly set below.
# no_subtree_check and fsid are not allowed per 3PAR support.
# Other strings will be allowed to be sent to the 3PAR which will do
# further validation.
options_not_allowed = ['ro', 'rw',
'no_root_squash', 'root_squash',
'secure', 'insecure',
'no_subtree_check', 'fsid']
invalid_options = [
option for option in options if option in options_not_allowed
]
if invalid_options:
raise exception.InvalidInput(_('Invalid hp3_par:nfs_options in '
'extra-specs. The following '
'options are not allowed: %s') %
invalid_options)
options.append('ro' if readonly else 'rw')
options.append('no_root_squash')
options.append('insecure')
return ','.join(options)
def _build_createfshare_kwargs(self, protocol, fpg, fstore, readonly,
sharedir, extra_specs):
createfshare_kwargs = dict(fpg=fpg,
fstore=fstore,
sharedir=sharedir,
comment=OPEN_STACK_MANILA_FSHARE)
if protocol == 'nfs':
createfshare_kwargs['clientip'] = '127.0.0.1'
options = self._get_nfs_options(extra_specs, readonly)
createfshare_kwargs['options'] = options
else:
createfshare_kwargs['allowip'] = '127.0.0.1'
if self.client_version < MIN_SMB_CA_VERSION:
smb_opts = (ACCESS_BASED_ENUM, CACHE)
else:
smb_opts = (ACCESS_BASED_ENUM, CONTINUOUS_AVAIL, CACHE)
for smb_opt in smb_opts:
opt_value = extra_specs.get('hp_3par:smb_%s' % smb_opt)
if opt_value:
createfshare_kwargs[smb_opt] = opt_value
return createfshare_kwargs
def create_share(self, project_id, share_id, share_proto, extra_specs,
fpg, vfs,
fstore=None, sharedir=None, readonly=False, size=None):
"""Create the share and return its path.
@ -187,6 +253,7 @@ class HP3ParMediator(object):
:param project_id: The tenant ID.
:param share_id: The share-id with or without osf- prefix.
:param share_proto: The protocol (to map to smb or nfs)
:param extra_specs: The share type extra-specs
:param fpg: The file provisioning group
:param vfs: The virtual file system
:param fstore: (optional) The file store. When provided, an existing
@ -200,12 +267,27 @@ class HP3ParMediator(object):
protocol = self.ensure_supported_protocol(share_proto)
share_name = self.ensure_prefix(share_id)
if not fstore:
if not (sharedir or self.hp3par_fstore_per_share):
sharedir = share_name
if fstore:
use_existing_fstore = True
else:
use_existing_fstore = False
if self.hp3par_fstore_per_share:
fstore = share_name
else:
fstore = self.ensure_prefix(project_id, protocol)
createfshare_kwargs = self._build_createfshare_kwargs(protocol,
fpg,
fstore,
readonly,
sharedir,
extra_specs)
if not use_existing_fstore:
try:
result = self._client.createfstore(
vfs, fstore, fpg=fpg,
@ -245,28 +327,13 @@ class HP3ParMediator(object):
LOG.exception(msg)
raise exception.ShareBackendException(msg)
if not (sharedir or self.hp3par_fstore_per_share):
sharedir = share_name
try:
if protocol == 'nfs':
if readonly:
options = 'ro,no_root_squash,insecure'
else:
options = 'rw,no_root_squash,insecure'
result = self._client.createfshare(
protocol, vfs, share_name,
fpg=fpg, fstore=fstore, sharedir=sharedir,
clientip='127.0.0.1',
options=options,
comment=OPEN_STACK_MANILA_FSHARE)
else:
result = self._client.createfshare(
protocol, vfs, share_name,
fpg=fpg, fstore=fstore, sharedir=sharedir,
allowip='127.0.0.1',
comment=OPEN_STACK_MANILA_FSHARE)
result = self._client.createfshare(protocol,
vfs,
share_name,
**createfshare_kwargs)
LOG.debug("createfshare result=%s", result)
except Exception as e:
@ -300,7 +367,7 @@ class HP3ParMediator(object):
else:
return result['members'][0]['shareName']
def create_share_from_snapshot(self, share_id, share_proto,
def create_share_from_snapshot(self, share_id, share_proto, extra_specs,
orig_project_id, orig_share_id, orig_proto,
snapshot_id, fpg, vfs):
@ -339,6 +406,7 @@ class HP3ParMediator(object):
orig_project_id,
share_name,
protocol,
extra_specs,
fpg,
vfs,
fstore=fstore,

View File

@ -24,6 +24,7 @@ SAN_PASSWORD = 'testpassword4san'
API_URL = 'https://1.2.3.4:8080/api/v1'
TIMEOUT = 60
PORT = 22
SHARE_TYPE_ID = 123456789
# Constants to use with Mock and expect in results
EXPECTED_IP_10203040 = '10.20.30.40'
@ -42,6 +43,11 @@ EXPECTED_FPG = 'FPG_1'
EXPECTED_FSTORE = EXPECTED_PROJECT_ID
EXPECTED_VFS = 'test_vfs'
EXPECTED_HP_DEBUG = True
EXPECTED_EXTRA_SPECS = {}
GET_FSQUOTA = {'message': None,
'total': 1,
'members': [{'hardBlock': '1024', 'softBlock': '1024'}]}
NFS_SHARE_INFO = {
'project_id': EXPECTED_PROJECT_ID,
@ -63,3 +69,9 @@ SNAPSHOT_INFO = {
'share_proto': NFS,
},
}
class FakeException(Exception):
pass
FAKE_EXCEPTION = FakeException("Fake exception for testing.")

View File

@ -136,9 +136,12 @@ class HP3ParDriverTestCase(test.TestCase):
self.driver.vfs = constants.EXPECTED_VFS
self.driver.fpg = constants.EXPECTED_FPG
self.driver.share_ip_address = self.conf.hp3par_share_ip_address
self.mock_object(hp3pardriver, 'share_types')
get_extra_specs = hp3pardriver.share_types.get_extra_specs_from_share
get_extra_specs.return_value = constants.EXPECTED_EXTRA_SPECS
def do_create_share(self, protocol, expected_project_id, expected_share_id,
expected_size):
def do_create_share(self, protocol, share_type_id, expected_project_id,
expected_share_id, expected_size):
"""Re-usable code for create share."""
context = None
share_server = None
@ -146,6 +149,7 @@ class HP3ParDriverTestCase(test.TestCase):
'project_id': expected_project_id,
'id': expected_share_id,
'share_proto': protocol,
'share_type_id': share_type_id,
'size': expected_size,
}
location = self.driver.create_share(context, share, share_server)
@ -153,6 +157,7 @@ class HP3ParDriverTestCase(test.TestCase):
def do_create_share_from_snapshot(self,
protocol,
share_type_id,
snapshot_id,
expected_share_id,
expected_size):
@ -162,6 +167,7 @@ class HP3ParDriverTestCase(test.TestCase):
share = {
'id': expected_share_id,
'share_proto': protocol,
'share_type_id': share_type_id,
'size': expected_size,
}
location = self.driver.create_share_from_snapshot(context,
@ -208,6 +214,7 @@ class HP3ParDriverTestCase(test.TestCase):
constants.EXPECTED_SHARE_NAME)
location = self.do_create_share(constants.CIFS,
constants.SHARE_TYPE_ID,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_2)
@ -217,6 +224,7 @@ class HP3ParDriverTestCase(test.TestCase):
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_2)]
@ -232,6 +240,7 @@ class HP3ParDriverTestCase(test.TestCase):
constants.EXPECTED_SHARE_PATH)
location = self.do_create_share(constants.NFS,
constants.SHARE_TYPE_ID,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_1)
@ -241,6 +250,7 @@ class HP3ParDriverTestCase(test.TestCase):
mock.call.create_share(constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)]
@ -258,20 +268,24 @@ class HP3ParDriverTestCase(test.TestCase):
location = self.do_create_share_from_snapshot(
constants.CIFS,
constants.SHARE_TYPE_ID,
constants.SNAPSHOT_INFO,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_2)
self.assertEqual(expected_location, location)
expected_calls = [
mock.call.create_share_from_snapshot(constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_FSTORE,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
mock.call.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_FSTORE,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS),
]
self.mock_mediator.assert_has_calls(expected_calls)
def test_driver_create_nfs_share_from_snapshot(self):
@ -285,20 +299,24 @@ class HP3ParDriverTestCase(test.TestCase):
location = self.do_create_share_from_snapshot(
constants.NFS,
constants.SHARE_TYPE_ID,
constants.SNAPSHOT_INFO,
constants.EXPECTED_SHARE_ID,
constants.EXPECTED_SIZE_1)
self.assertEqual(expected_location, location)
expected_calls = [
mock.call.create_share_from_snapshot(constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)]
mock.call.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_SNAP_ID,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS)
]
self.mock_mediator.assert_has_calls(expected_calls)

View File

@ -188,10 +188,7 @@ class HP3ParMediatorTestCase(test.TestCase):
def test_mediator_client_version_exception(self):
"""Test the getWsApiVersion exception handling."""
class FakeException(Exception):
pass
self.mock_client.getWsApiVersion.side_effect = FakeException()
self.mock_client.getWsApiVersion.side_effect = constants.FAKE_EXCEPTION
self.assertRaises(exception.ShareBackendException,
self.init_mediator)
@ -204,14 +201,35 @@ class HP3ParMediatorTestCase(test.TestCase):
self.mediator.do_setup)
def get_expected_calls_for_create_share(self,
client_version,
expected_fpg,
expected_vfsname,
expected_protocol,
extra_specs,
expected_project_id,
expected_share_id):
expected_sharedir = expected_share_id
createfshare_kwargs = dict(comment='OpenStack Manila fshare',
fpg=expected_fpg,
sharedir=expected_sharedir,
fstore=expected_project_id)
if expected_protocol == constants.NFS_LOWER:
createfshare_kwargs['clientip'] = '127.0.0.1'
# Options from extra-specs.
opt_string = extra_specs.get('hp_3par:nfs_options', [])
opt_list = opt_string.split(',')
# Options that the mediator adds.
nfs_options = ['rw', 'no_root_squash', 'insecure']
nfs_options += opt_list
expected_options = ','.join(nfs_options)
createfshare_kwargs['options'] = OptionMatcher(
self.assertListEqual, expected_options)
expected_calls = [
mock.call.createfstore(expected_vfsname, expected_project_id,
comment='OpenStack Manila fstore',
@ -226,16 +244,26 @@ class HP3ParMediatorTestCase(test.TestCase):
fstore=expected_project_id),
mock.call.createfshare(expected_protocol, expected_vfsname,
expected_share_id,
comment='OpenStack Manila fshare',
fpg=expected_fpg,
sharedir=expected_sharedir,
clientip='127.0.0.1',
options='rw,no_root_squash,insecure',
fstore=expected_project_id),
**createfshare_kwargs),
mock.call.getfshare(expected_protocol, expected_share_id,
fpg=expected_fpg, vfs=expected_vfsname,
fstore=expected_project_id)]
else:
createfshare_kwargs['allowip'] = '127.0.0.1'
if client_version < hp3parmediator.MIN_SMB_CA_VERSION:
smb_opts = (hp3parmediator.ACCESS_BASED_ENUM,
hp3parmediator.CACHE)
else:
smb_opts = (hp3parmediator.ACCESS_BASED_ENUM,
hp3parmediator.CONTINUOUS_AVAIL,
hp3parmediator.CACHE)
for smb_opt in smb_opts:
opt_value = extra_specs.get('hp_3par:smb_%s' % smb_opt)
if opt_value:
createfshare_kwargs[smb_opt] = opt_value
expected_calls = [
mock.call.createfstore(expected_vfsname, expected_project_id,
comment='OpenStack Manila fstore',
@ -250,17 +278,35 @@ class HP3ParMediatorTestCase(test.TestCase):
fstore=expected_project_id),
mock.call.createfshare(expected_protocol, expected_vfsname,
expected_share_id,
comment='OpenStack Manila fshare',
fpg=expected_fpg,
sharedir=expected_sharedir,
allowip='127.0.0.1',
fstore=expected_project_id),
**createfshare_kwargs),
mock.call.getfshare(expected_protocol, expected_share_id,
fpg=expected_fpg, vfs=expected_vfsname,
fstore=expected_project_id)]
return expected_calls
def test_mediator_create_cifs_share(self):
@staticmethod
def _build_smb_extra_specs(**kwargs):
extra_specs = {'driver_handles_share_servers': False}
for k, v in kwargs.items():
extra_specs['hp_3par:smb_%s' % k] = v
return extra_specs
@ddt.data(((3, 2, 1), None, None, None),
((3, 2, 1), 'true', None, None),
((3, 2, 1), None, 'false', None),
((3, 2, 1), None, 'false', None),
((3, 2, 1), None, None, 'optimized'),
((3, 2, 1), 'true', 'false', 'optimized'),
((3, 2, 2), None, None, None),
((3, 2, 2), 'true', None, None),
((3, 2, 2), None, 'false', None),
((3, 2, 2), None, 'false', None),
((3, 2, 2), None, None, 'optimized'),
((3, 2, 2), 'true', 'false', 'optimized'))
@ddt.unpack
def test_mediator_create_cifs_share(self, client_version, abe, ca, cache):
self.hp3parclient = sys.modules['hp3parclient']
self.hp3parclient.version_tuple = client_version
self.init_mediator()
self.mock_client.getfshare.return_value = {
@ -269,15 +315,16 @@ class HP3ParMediatorTestCase(test.TestCase):
'members': [{'shareName': constants.EXPECTED_SHARE_NAME}]
}
self.mock_client.getfsquota.return_value = {
'message': None,
'total': 1,
'members': [{'hardBlock': '1024', 'softBlock': '1024'}]
}
self.mock_client.getfsquota.return_value = constants.GET_FSQUOTA
extra_specs = self._build_smb_extra_specs(access_based_enum=abe,
continuous_avail=ca,
cache=cache)
location = self.mediator.create_share(constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.CIFS,
extra_specs,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
@ -285,15 +332,43 @@ class HP3ParMediatorTestCase(test.TestCase):
self.assertEqual(constants.EXPECTED_SHARE_NAME, location)
expected_calls = self.get_expected_calls_for_create_share(
client_version,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
constants.SMB_LOWER,
extra_specs,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID)
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_nfs_share(self):
@ddt.data('ro',
'rw',
'no_root_squash',
'root_squash',
'secure',
'insecure',
'hide,insecure,no_wdelay,ro,bogus,root_squash,test')
def test_mediator_create_nfs_share_bad_options(self, nfs_options):
self.init_mediator()
extra_specs = {'hp_3par:nfs_options': nfs_options}
self.assertRaises(exception.InvalidInput,
self.mediator.create_share,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS.lower(),
extra_specs,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
self.assertFalse(self.mock_client.createfshare.called)
@ddt.data('sync',
'no_wdelay,sec=sys,hide,sync')
def test_mediator_create_nfs_share(self, nfs_options):
self.init_mediator()
self.mock_client.getfshare.return_value = {
@ -302,15 +377,14 @@ class HP3ParMediatorTestCase(test.TestCase):
'members': [{'sharePath': constants.EXPECTED_SHARE_PATH}]
}
self.mock_client.getfsquota.return_value = {
'message': None,
'total': 1,
'members': [{'hardBlock': '1024', 'softBlock': '1024'}]
}
self.mock_client.getfsquota.return_value = constants.GET_FSQUOTA
extra_specs = {'hp_3par:nfs_options': nfs_options}
location = self.mediator.create_share(constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS.lower(),
extra_specs,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
@ -318,14 +392,49 @@ class HP3ParMediatorTestCase(test.TestCase):
self.assertEqual(constants.EXPECTED_SHARE_PATH, location)
expected_calls = self.get_expected_calls_for_create_share(
hp3parmediator.MIN_CLIENT_VERSION,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
constants.NFS.lower(),
extra_specs,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID)
self.mock_client.assert_has_calls(expected_calls)
def test_mediator_create_nfs_share_get_exception(self):
self.init_mediator()
self.mock_client.getfshare.side_effect = constants.FAKE_EXCEPTION
self.mock_client.getfsquota.return_value = constants.GET_FSQUOTA
self.assertRaises(exception.ShareBackendException,
self.mediator.create_share,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS.lower(),
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
@ddt.data(0, 2)
def test_mediator_create_nfs_share_get_fail(self, count):
self.init_mediator()
self.mock_client.getfshare.return_value = {'total': count}
self.mock_client.getfsquota.return_value = constants.GET_FSQUOTA
self.assertRaises(exception.ShareBackendException,
self.mediator.create_share,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS.lower(),
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_FPG,
constants.EXPECTED_VFS,
size=constants.EXPECTED_SIZE_1)
def test_mediator_create_cifs_share_from_snapshot(self):
self.init_mediator()
@ -339,6 +448,7 @@ class HP3ParMediatorTestCase(test.TestCase):
location = self.mediator.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.CIFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
@ -385,6 +495,7 @@ class HP3ParMediatorTestCase(test.TestCase):
location = self.mediator.create_share_from_snapshot(
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
@ -432,6 +543,7 @@ class HP3ParMediatorTestCase(test.TestCase):
self.mediator.create_share_from_snapshot,
constants.EXPECTED_SHARE_ID,
constants.NFS,
constants.EXPECTED_EXTRA_SPECS,
constants.EXPECTED_PROJECT_ID,
constants.EXPECTED_SHARE_ID,
constants.NFS,
@ -1189,3 +1301,16 @@ class HP3ParMediatorTestCase(test.TestCase):
fstore=constants.EXPECTED_PROJECT_ID)
self.assertEqual(expected_result, result)
class OptionMatcher(object):
"""Options string order can vary. Compare as lists."""
def __init__(self, assert_func, expected_string):
self.assert_func = assert_func
self.expected = expected_string.split(',')
def __eq__(self, actual_string):
actual = actual_string.split(',')
self.assert_func(sorted(self.expected), sorted(actual))
return True