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:
parent
a778199f01
commit
8895ea2254
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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'],
|
||||
|
@ -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,
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user