Implement update_access() in generic driver + LVM

Add update_access() method to generic and LVM drivers,
to NFSHelper and CIFSHelper.

Remove allow_access() and deny_access() from helpers.

Partially implements bp new-share-access-driver-interface

Co-Authored-By: Rodrigo Barbieri <rodrigo.barbieri@fit-tecnologia.org.br>

Change-Id: Ie34c92f8b9dc95d6af5cb150427503bf66543ab6
This commit is contained in:
Julia Varlamova 2015-12-11 03:25:18 -05:00 committed by Rodrigo Barbieri
parent 21cc6fea34
commit fbfd65b338
7 changed files with 483 additions and 569 deletions

View File

@ -652,7 +652,7 @@ class ShareDriver(object):
Drivers should support 2 different cases in this method:
1. Recovery after error - 'access_rules' contains all access_rules,
'add_rules' and 'delete_rules' are None. Driver should clear any
'add_rules' and 'delete_rules' shall be None. Driver should clear any
existent access rules and apply all access rules for given share.
This recovery is made at driver start up.

View File

@ -143,7 +143,7 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
service_instance.ServiceInstanceManager(
driver_config=self.configuration))
def _ssh_exec(self, server, command):
def _ssh_exec(self, server, command, check_exit_code=True):
connection = self.ssh_connections.get(server['instance_id'])
ssh_conn_timeout = self.configuration.ssh_conn_timeout
if not connection:
@ -163,7 +163,8 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
ssh_pool.remove(ssh)
ssh = ssh_pool.create()
self.ssh_connections[server['instance_id']] = (ssh_pool, ssh)
return processutils.ssh_execute(ssh, ' '.join(command))
return processutils.ssh_execute(ssh, ' '.join(command),
check_exit_code=check_exit_code)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
@ -821,23 +822,34 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver):
share_server['backend_details'], share['name'], recreate=True)
@ensure_server
def allow_access(self, context, share, access, share_server=None):
"""Allow access to the share."""
def update_access(self, context, share, access_rules, add_rules=None,
delete_rules=None, share_server=None):
"""Update access rules for given share.
# NOTE(vponomaryov): use direct verification for case some additional
# level is added.
access_level = access['access_level']
if access_level not in (const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO):
raise exception.InvalidShareAccessLevel(level=access_level)
self._get_helper(share).allow_access(
share_server['backend_details'], share['name'],
access['access_type'], access['access_level'], access['access_to'])
This driver has two different behaviors according to parameters:
1. Recovery after error - 'access_rules' contains all access_rules,
'add_rules' and 'delete_rules' shall be None. Previously existing
access rules are cleared and then added back according
to 'access_rules'.
@ensure_server
def deny_access(self, context, share, access, share_server=None):
"""Deny access to the share."""
self._get_helper(share).deny_access(
share_server['backend_details'], share['name'], access)
2. Adding/Deleting of several access rules - 'access_rules' contains
all access_rules, 'add_rules' and 'delete_rules' contain rules which
should be added/deleted. Rules in 'access_rules' are ignored and
only rules from 'add_rules' and 'delete_rules' are applied.
:param context: Current context
:param share: Share model with share data.
:param access_rules: All access rules for given share
:param add_rules: None or List of access rules which should be added
access_rules already contains these rules.
:param delete_rules: None or List of access rules which should be
removed. access_rules doesn't contain these rules.
:param share_server: None or Share server model
"""
self._get_helper(share).update_access(share_server['backend_details'],
share['name'], access_rules,
add_rules=add_rules,
delete_rules=delete_rules)
def _get_helper(self, share):
helper = self._helpers.get(share['share_proto'])

View File

@ -17,7 +17,6 @@ import os
import re
from oslo_log import log
from oslo_utils import excutils
from manila.common import constants as const
from manila import exception
@ -51,13 +50,29 @@ class NASHelperBase(object):
"""Configure server before allowing access."""
pass
def allow_access(self, server, share_name, access_type, access_level,
access_to):
"""Allow access to the host."""
raise NotImplementedError()
def update_access(self, server, share_name, access_rules, add_rules=None,
delete_rules=None):
"""Update access rules for given share.
def deny_access(self, server, share_name, access, force=False):
"""Deny access to the host."""
This driver has two different behaviors according to parameters:
1. Recovery after error - 'access_rules' contains all access_rules,
'add_rules' and 'delete_rules' shall be None. Previously existing
access rules are cleared and then added back according
to 'access_rules'.
2. Adding/Deleting of several access rules - 'access_rules' contains
all access_rules, 'add_rules' and 'delete_rules' contain rules which
should be added/deleted. Rules in 'access_rules' are ignored and
only rules from 'add_rules' and 'delete_rules' are applied.
:param server: None or Share server's backend details
:param share_name: Share's path according to id.
:param access_rules: All access rules for given share
:param add_rules: None or List of access rules which should be added
access_rules already contains these rules.
:param delete_rules: None or List of access rules which should be
removed. access_rules doesn't contain these rules.
"""
raise NotImplementedError()
@staticmethod
@ -80,6 +95,24 @@ class NASHelperBase(object):
def restore_access_after_maintenance(self, server, share_name):
"""Enables access to share after maintenance operations were done."""
@staticmethod
def validate_access_rules(access_rules, allowed_types, allowed_levels):
"""Validates access rules according to access_type and access_level.
:param access_rules: List of access rules to be validated.
:param allowed_types: tuple of allowed type values.
:param allowed_levels: tuple of allowed level values.
"""
for access in (access_rules or []):
access_type = access['access_type']
access_level = access['access_level']
if access_type not in allowed_types:
reason = _("Only %s access type allowed.") % (
', '.join(tuple(["'%s'" % x for x in allowed_types])))
raise exception.InvalidShareAccess(reason=reason)
if access_level not in allowed_levels:
raise exception.InvalidShareAccessLevel(level=access_level)
def _get_maintenance_file_path(self, share_name):
return os.path.join(self.configuration.share_mount_path,
"%s.maintenance" % share_name)
@ -88,7 +121,7 @@ class NASHelperBase(object):
def nfs_synchronized(f):
def wrapped_func(self, *args, **kwargs):
key = "nfs-%s" % args[0]["instance_id"]
key = "nfs-%s" % args[0]["public_address"]
@utils.synchronized(key)
def source_func(self, *args, **kwargs):
@ -104,9 +137,9 @@ class NFSHelper(NASHelperBase):
def create_export(self, server, share_name, recreate=False):
"""Create new export, delete old one if exists."""
return ':'.join([server['public_address'],
return ':'.join((server['public_address'],
os.path.join(
self.configuration.share_mount_path, share_name)])
self.configuration.share_mount_path, share_name)))
def init_helper(self, server):
try:
@ -122,36 +155,90 @@ class NFSHelper(NASHelperBase):
"""Remove export."""
@nfs_synchronized
def allow_access(self, server, share_name, access_type, access_level,
access_to):
"""Allow access to the host."""
def update_access(self, server, share_name, access_rules, add_rules=None,
delete_rules=None):
"""Update access rules for given share.
Please refer to base class for a more in-depth description.
"""
local_path = os.path.join(self.configuration.share_mount_path,
share_name)
if access_type != 'ip':
msg = _('only ip access type allowed')
raise exception.InvalidShareAccess(reason=msg)
# check if presents in export
out, err = self._ssh_exec(server, ['sudo', 'exportfs'])
out = re.search(
re.escape(local_path) + '[\s\n]*' + re.escape(access_to), out)
if out is not None:
raise exception.ShareAccessExists(access_type=access_type,
access=access_to)
self._ssh_exec(
server,
['sudo', 'exportfs', '-o', '%s,no_subtree_check' % access_level,
':'.join([access_to, local_path])])
self._sync_nfs_temp_and_perm_files(server)
# Recovery mode
if not (add_rules or delete_rules):
@nfs_synchronized
def deny_access(self, server, share_name, access, force=False):
"""Deny access to the host."""
local_path = os.path.join(self.configuration.share_mount_path,
share_name)
self._ssh_exec(server, ['sudo', 'exportfs', '-u',
':'.join([access['access_to'], local_path])])
self._sync_nfs_temp_and_perm_files(server)
self.validate_access_rules(
access_rules, ('ip',),
(const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
hosts = self._get_host_list(out, local_path)
for host in hosts:
self._ssh_exec(server, ['sudo', 'exportfs', '-u',
':'.join((host, local_path))])
self._sync_nfs_temp_and_perm_files(server)
for access in access_rules:
self._ssh_exec(
server,
['sudo', 'exportfs', '-o',
'%s,no_subtree_check' % access['access_level'],
':'.join((access['access_to'], local_path))])
self._sync_nfs_temp_and_perm_files(server)
# Adding/Deleting specific rules
else:
self.validate_access_rules(
add_rules, ('ip',),
(const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
for access in (delete_rules or []):
try:
self.validate_access_rules(
[access], ('ip',),
(const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
except (exception.InvalidShareAccess,
exception.InvalidShareAccessLevel):
LOG.warning(_LW(
"Unsupported access level %(level)s or access type "
"%(type)s, skipping removal of access rule to "
"%(to)s.") % {'level': access['access_level'],
'type': access['access_type'],
'to': access['access_to']})
continue
self._ssh_exec(server, ['sudo', 'exportfs', '-u',
':'.join((access['access_to'], local_path))])
if delete_rules:
self._sync_nfs_temp_and_perm_files(server)
for access in (add_rules or []):
access_to, access_type = (access['access_to'],
access['access_type'])
found_item = re.search(
re.escape(local_path) + '[\s\n]*' + re.escape(access_to),
out)
if found_item is not None:
LOG.warning(_LW("Access rule %(type)s:%(to)s already "
"exists for share %(name)s") % {
'to': access_to,
'type': access_type,
'name': share_name
})
else:
self._ssh_exec(
server,
['sudo', 'exportfs', '-o',
'%s,no_subtree_check' % access['access_level'],
':'.join((access['access_to'], local_path))])
if add_rules:
self._sync_nfs_temp_and_perm_files(server)
def _get_host_list(self, output, local_path):
entries = []
output = output.replace('\n\t\t', ' ')
lines = output.split('\n')
for line in lines:
items = line.split(' ')
if local_path == items[0]:
entries.append(items[1])
return entries
def _sync_nfs_temp_and_perm_files(self, server):
"""Sync changes of exports with permanent NFS config file.
@ -164,11 +251,17 @@ class NFSHelper(NASHelperBase):
]
self._ssh_exec(server, sync_cmd)
self._ssh_exec(server, ['sudo', 'exportfs', '-a'])
out, _ = self._ssh_exec(
server, ['sudo', 'service', 'nfs-kernel-server', 'status'],
check_exit_code=False)
if "not" in out:
self._ssh_exec(
server, ['sudo', 'service', 'nfs-kernel-server', 'restart'])
def get_exports_for_share(self, server, old_export_location):
self._verify_server_has_public_address(server)
path = old_export_location.split(':')[-1]
return [':'.join([server['public_address'], path])]
return [':'.join((server['public_address'], path))]
def get_share_path_by_export_location(self, server, export_location):
return export_location.split(':')[-1]
@ -234,21 +327,27 @@ class CIFSHelperIPAccess(NASHelperBase):
try:
self._ssh_exec(
server, ['sudo', 'net', 'conf', 'showshare', share_name, ])
except exception.ProcessExecutionError as parent_e:
except exception.ProcessExecutionError:
# Share does not exist, create it
try:
self._ssh_exec(server, create_cmd)
except Exception:
# If we get here, then it will be useful
# to log parent exception too.
with excutils.save_and_reraise_exception():
LOG.error(parent_e)
except Exception as child_e:
msg = _("Could not create CIFS export %s.") % share_name
LOG.exception(child_e)
LOG.error(msg)
raise exception.ManilaException(reason=msg)
else:
# Share exists
if recreate:
self._ssh_exec(
server, ['sudo', 'net', 'conf', 'delshare', share_name, ])
self._ssh_exec(server, create_cmd)
try:
self._ssh_exec(server, create_cmd)
except Exception as e:
msg = _("Could not create CIFS export %s.") % share_name
LOG.exception(e)
LOG.error(msg)
raise exception.ManilaException(reason=msg)
else:
msg = _('Share section %s already defined.') % share_name
raise exception.ShareBackendException(msg=msg)
@ -270,38 +369,23 @@ class CIFSHelperIPAccess(NASHelperBase):
self._ssh_exec(server, ['sudo', 'smbcontrol', 'all', 'close-share',
share_name])
def allow_access(self, server, share_name, access_type, access_level,
access_to):
"""Add access for share."""
if access_type != 'ip':
reason = _('Only ip access type allowed.')
raise exception.InvalidShareAccess(reason=reason)
if access_level != const.ACCESS_LEVEL_RW:
raise exception.InvalidShareAccessLevel(level=access_level)
def update_access(self, server, share_name, access_rules, add_rules=None,
delete_rules=None):
"""Update access rules for given share.
hosts = self._get_allow_hosts(server, share_name)
if access_to in hosts:
raise exception.ShareAccessExists(
access_type=access_type, access=access_to)
hosts.append(access_to)
Please refer to base class for a more in-depth description. For this
specific implementation, add_rules and delete_rules parameters are not
used.
"""
hosts = []
self.validate_access_rules(
access_rules, ('ip',), (const.ACCESS_LEVEL_RW,))
for access in access_rules:
hosts.append(access['access_to'])
self._set_allow_hosts(server, hosts, share_name)
def deny_access(self, server, share_name, access, force=False):
"""Remove access for share."""
access_to, access_level = access['access_to'], access['access_level']
if access_level != const.ACCESS_LEVEL_RW:
return
try:
hosts = self._get_allow_hosts(server, share_name)
if access_to in hosts:
# Access rule can be in error state, if so
# it can be absent in rules, hence - skip removal.
hosts.remove(access_to)
self._set_allow_hosts(server, hosts, share_name)
except exception.ProcessExecutionError:
if not force:
raise
def _get_allow_hosts(self, server, share_name):
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf', 'getparm',
share_name, '\"hosts allow\"'])
@ -378,63 +462,35 @@ class CIFSHelperUserAccess(CIFSHelperIPAccess):
'read only': 'no',
}
def allow_access(self, server, share_name, access_type, access_level,
access_to):
"""Add to allow hosts additional access rule."""
if access_type != 'user':
reason = _('Only user access type allowed.')
raise exception.InvalidShareAccess(reason=reason)
all_users = self._get_valid_users(server, share_name)
def update_access(self, server, share_name, access_rules, add_rules=None,
delete_rules=None):
"""Update access rules for given share.
if access_to in all_users:
raise exception.ShareAccessExists(
access_type=access_type, access=access_to)
Please refer to base class for a more in-depth description. For this
specific implementation, add_rules and delete_rules parameters are not
used.
"""
all_users_rw = []
all_users_ro = []
user_list = self._get_valid_users(server, share_name, access_level)
user_list.append(access_to)
self._set_valid_users(server, user_list, share_name, access_level)
self.validate_access_rules(
access_rules, ('user',),
(const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
def deny_access(self, server, share_name, access, force=False):
"""Remove from allow hosts permit rule."""
access_to, access_level = access['access_to'], access['access_level']
users = self._get_valid_users(server, share_name, access_level,
force=force)
if access_to in users:
users.remove(access_to)
self._set_valid_users(server, users, share_name, access_level)
def _get_valid_users(self, server, share_name, access_level=None,
force=True):
if not access_level:
all_users_list = []
for param in ['valid users', 'read list']:
out = ""
try:
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf',
'getparm', share_name,
param])
out = out.replace("\"", "")
except exception.ProcessExecutionError:
if not force:
raise
all_users_list += out.split()
return all_users_list
param = self._get_conf_param(access_level)
try:
(out, _) = self._ssh_exec(server, ['sudo', 'net', 'conf',
'getparm', share_name, param])
out = out.replace("\"", "")
return out.split()
except exception.ProcessExecutionError:
if not force:
raise
return []
for access in access_rules:
if access['access_level'] == const.ACCESS_LEVEL_RW:
all_users_rw.append(access['access_to'])
else:
all_users_ro.append(access['access_to'])
self._set_valid_users(
server, all_users_rw, share_name, const.ACCESS_LEVEL_RW)
self._set_valid_users(
server, all_users_ro, share_name, const.ACCESS_LEVEL_RO)
def _get_conf_param(self, access_level):
if access_level == const.ACCESS_LEVEL_RW:
return 'valid users'
if access_level == const.ACCESS_LEVEL_RO:
else:
return 'read list'
def _set_valid_users(self, server, users, share_name, access_level):

View File

@ -35,7 +35,6 @@ from manila.i18n import _LW
from manila.share import driver
from manila.share.drivers import generic
LOG = log.getLogger(__name__)
share_opts = [
@ -146,11 +145,12 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
'instance_id': self.backend_name,
}
def _ssh_exec_as_root(self, server, command):
def _ssh_exec_as_root(self, server, command, check_exit_code=True):
kwargs = {}
if 'sudo' in command:
kwargs['run_as_root'] = True
command.remove('sudo')
kwargs['check_exit_code'] = check_exit_code
return self._execute(*command, **kwargs)
def do_setup(self, context):
@ -265,17 +265,34 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
except exception.InvalidShare as exc:
LOG.warning(exc.message)
def allow_access(self, ctx, share, access, share_server=None):
"""Allow access to the share."""
self._get_helper(share).allow_access(self.share_server, share['name'],
access['access_type'],
access['access_level'],
access['access_to'])
def update_access(self, context, share, access_rules, add_rules=None,
delete_rules=None, share_server=None):
"""Update access rules for given share.
def deny_access(self, ctx, share, access, share_server=None):
"""Deny access to the share."""
self._get_helper(share).deny_access(self.share_server, share['name'],
access)
This driver has two different behaviors according to parameters:
1. Recovery after error - 'access_rules' contains all access_rules,
'add_rules' and 'delete_rules' shall be None. Previously existing
access rules are cleared and then added back according
to 'access_rules'.
2. Adding/Deleting of several access rules - 'access_rules' contains
all access_rules, 'add_rules' and 'delete_rules' contain rules which
should be added/deleted. Rules in 'access_rules' are ignored and
only rules from 'add_rules' and 'delete_rules' are applied.
:param context: Current context
:param share: Share model with share data.
:param access_rules: All access rules for given share
:param add_rules: None or List of access rules which should be added
access_rules already contains these rules.
:param delete_rules: None or List of access rules which should be
removed. access_rules doesn't contain these rules.
:param share_server: None or Share server model
"""
self._get_helper(share).update_access(self.share_server,
share['name'], access_rules,
add_rules=add_rules,
delete_rules=delete_rules)
def _get_helper(self, share):
if share['share_proto'].lower().startswith('nfs'):

View File

@ -146,6 +146,14 @@ def get_fake_collated_cg_snap_info():
return fake_collated_cg_snap_info
def get_fake_access_rule(access_to, access_level, access_type='ip'):
return {
'access_type': access_type,
'access_to': access_to,
'access_level': access_level,
}
@ddt.ddt
class GenericShareDriverTestCase(test.TestCase):
"""Tests GenericShareDriver."""
@ -1208,38 +1216,25 @@ class GenericShareDriverTestCase(test.TestCase):
self._context, self.share, share_server=self.server)
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
def test_allow_access(self, access_level):
access = {
'access_type': 'ip',
'access_to': 'fake_dest',
'access_level': access_level,
}
self._driver.allow_access(
self._context, self.share, access, share_server=self.server)
def test_update_access(self, access_level):
# fakes
access_rules = [get_fake_access_rule('1.1.1.1', access_level),
get_fake_access_rule('2.2.2.2', access_level)]
add_rules = [get_fake_access_rule('2.2.2.2', access_level), ]
delete_rules = [get_fake_access_rule('3.3.3.3', access_level), ]
# run
self._driver.update_access(self._context, self.share, access_rules,
add_rules=add_rules,
delete_rules=delete_rules,
share_server=self.server)
# asserts
self._driver._helpers[self.share['share_proto']].\
allow_access.assert_called_once_with(
update_access.assert_called_once_with(
self.server['backend_details'], self.share['name'],
access['access_type'], access['access_level'],
access['access_to'])
def test_allow_access_unsupported(self):
access = {
'access_type': 'ip',
'access_to': 'fake_dest',
'access_level': 'fakefoobar',
}
self.assertRaises(
exception.InvalidShareAccessLevel,
self._driver.allow_access,
self._context, self.share, access, share_server=self.server)
def test_deny_access(self):
access = 'fake_access'
self._driver.deny_access(
self._context, self.share, access, share_server=self.server)
self._driver._helpers[
self.share['share_proto']].deny_access.assert_called_once_with(
self.server['backend_details'], self.share['name'], access)
access_rules, add_rules=add_rules, delete_rules=delete_rules)
@ddt.data(fake_share.fake_share(),
fake_share.fake_share(share_proto='NFSBOGUS'),
@ -1306,7 +1301,8 @@ class GenericShareDriverTestCase(test.TestCase):
self.server['ip'], 22, ssh_conn_timeout, self.server['username'],
self.server['password'], self.server['pk_path'], max_size=1)
ssh_pool.create.assert_called_once_with()
processutils.ssh_execute.assert_called_once_with(ssh, 'fake command')
processutils.ssh_execute.assert_called_once_with(
ssh, 'fake command', check_exit_code=True)
ssh.get_transport().is_active.assert_called_once_with()
self.assertEqual(
self._driver.ssh_connections,
@ -1329,7 +1325,8 @@ class GenericShareDriverTestCase(test.TestCase):
result = self._driver._ssh_exec(self.server, cmd)
processutils.ssh_execute.assert_called_once_with(ssh, 'fake command')
processutils.ssh_execute.assert_called_once_with(
ssh, 'fake command', check_exit_code=True)
ssh.get_transport().is_active.assert_called_once_with()
self.assertEqual(
self._driver.ssh_connections,
@ -1354,7 +1351,8 @@ class GenericShareDriverTestCase(test.TestCase):
result = self._driver._ssh_exec(self.server, cmd)
processutils.ssh_execute.assert_called_once_with(ssh, 'fake command')
processutils.ssh_execute.assert_called_once_with(
ssh, 'fake command', check_exit_code=True)
ssh.get_transport().is_active.assert_called_once_with()
ssh_pool.create.assert_called_once_with()
ssh_pool.remove.assert_called_once_with(ssh)

View File

@ -26,6 +26,7 @@ from manila.share.drivers import helpers
from manila import test
from manila.tests import fake_compute
from manila.tests import fake_utils
from manila.tests.share.drivers import test_generic
CONF = cfg.CONF
@ -48,6 +49,37 @@ class NFSHelperTestCase(test.TestCase):
ip=ip, public_address=ip, instance_id='fake_instance_id')
self.share_name = 'fake_share_name'
def test_init_helper(self):
# mocks
self.mock_object(
self._helper, '_ssh_exec',
mock.Mock(side_effect=exception.ProcessExecutionError(
stderr='command not found')))
# run
self.assertRaises(exception.ManilaException,
self._helper.init_helper, self.server)
# asserts
self._helper._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'exportfs'])
def test_init_helper_log(self):
# mocks
self.mock_object(
self._helper, '_ssh_exec',
mock.Mock(side_effect=exception.ProcessExecutionError(
stderr='fake')))
# run
self._helper.init_helper(self.server)
# asserts
self._helper._ssh_exec.assert_called_once_with(
self.server, ['sudo', 'exportfs'])
def test_create_export(self):
ret = self._helper.create_export(self.server, self.share_name)
expected_location = ':'.join([self.server['public_address'],
@ -56,38 +88,88 @@ class NFSHelperTestCase(test.TestCase):
self.assertEqual(expected_location, ret)
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
def test_allow_access(self, data):
def test_update_access(self, access_level):
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
self._helper.allow_access(
self.server, self.share_name, 'ip', data, '10.0.0.2')
local_path = os.path.join(CONF.share_mount_path, self.share_name)
exec_result = ' '.join([local_path, '2.2.2.3'])
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(return_value=(exec_result, '')))
access_rules = [
test_generic.get_fake_access_rule('1.1.1.1', access_level),
test_generic.get_fake_access_rule('2.2.2.2', access_level),
test_generic.get_fake_access_rule('2.2.2.3', access_level)]
add_rules = [
test_generic.get_fake_access_rule('2.2.2.2', access_level),
test_generic.get_fake_access_rule('2.2.2.3', access_level)]
delete_rules = [
test_generic.get_fake_access_rule('3.3.3.3', access_level),
test_generic.get_fake_access_rule('4.4.4.4', access_level, 'user')]
self._helper.update_access(self.server, self.share_name, access_rules,
add_rules=add_rules,
delete_rules=delete_rules)
local_path = os.path.join(CONF.share_mount_path, self.share_name)
self._helper._ssh_exec.assert_has_calls([
mock.call(self.server, ['sudo', 'exportfs']),
mock.call(self.server, ['sudo', 'exportfs', '-u',
':'.join(['3.3.3.3', local_path])]),
mock.call(self.server, ['sudo', 'exportfs', '-o',
'%s,no_subtree_check' % access_level,
':'.join(['2.2.2.2', local_path])]),
])
self._helper._sync_nfs_temp_and_perm_files.assert_has_calls([
mock.call(self.server), mock.call(self.server)])
def test_update_access_invalid_type(self):
access_rules = [test_generic.get_fake_access_rule(
'2.2.2.2', const.ACCESS_LEVEL_RW, access_type='fake'), ]
self.assertRaises(
exception.InvalidShareAccess,
self._helper.update_access,
self.server,
self.share_name,
access_rules)
def test_update_access_invalid_level(self):
access_rules = [test_generic.get_fake_access_rule(
'2.2.2.2', 'fake_level', access_type='ip'), ]
self.assertRaises(
exception.InvalidShareAccessLevel,
self._helper.update_access,
self.server,
self.share_name,
access_rules)
def test_get_host_list(self):
fake_exportfs = ('/shares/share-1\n\t\t20.0.0.3\n'
'/shares/share-1\n\t\t20.0.0.6\n'
'/shares/share-2\n\t\t10.0.0.2\n'
'/shares/share-2\n\t\t10.0.0.5\n'
'/shares/share-3\n\t\t30.0.0.4\n'
'/shares/share-3\n\t\t30.0.0.7\n')
expected = ['20.0.0.3', '20.0.0.6']
result = self._helper._get_host_list(fake_exportfs, '/shares/share-1')
self.assertEqual(expected, result)
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
def test_update_access_recovery_mode(self, access_level):
access_rules = [test_generic.get_fake_access_rule(
'1.1.1.1', access_level), ]
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
self.mock_object(self._helper, '_get_host_list',
mock.Mock(return_value=['1.1.1.1']))
self._helper.update_access(self.server, self.share_name, access_rules)
local_path = os.path.join(CONF.share_mount_path, self.share_name)
self._ssh_exec.assert_has_calls([
mock.call(self.server, ['sudo', 'exportfs']),
mock.call(
self.server, ['sudo', 'exportfs', '-u',
':'.join([access_rules[0]['access_to'],
local_path])]),
mock.call(self.server, ['sudo', 'exportfs', '-o',
'%s,no_subtree_check' % data,
':'.join(['10.0.0.2', local_path])])
'%s,no_subtree_check' % access_level,
':'.join(['1.1.1.1', local_path])]),
])
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
self.server)
def test_allow_access_no_ip(self):
self.assertRaises(
exception.InvalidShareAccess,
self._helper.allow_access,
self.server, self.share_name,
'fake_type', 'fake_level', 'fake_rule')
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
def test_deny_access(self, data):
self.mock_object(self._helper, '_sync_nfs_temp_and_perm_files')
local_path = os.path.join(CONF.share_mount_path, self.share_name)
access = dict(
access_to='10.0.0.2', access_type='ip', access_level=data)
self._helper.deny_access(self.server, self.share_name, access)
export_string = ':'.join(['10.0.0.2', local_path])
expected_exec = ['sudo', 'exportfs', '-u', export_string]
self._ssh_exec.assert_called_once_with(self.server, expected_exec)
self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with(
self._helper._sync_nfs_temp_and_perm_files.assert_called_with(
self.server)
def test_sync_nfs_temp_and_perm_files(self):
@ -198,7 +280,7 @@ class CIFSHelperIPAccessTestCase(test.TestCase):
if 'showshare' in args[1]:
raise exception.ProcessExecutionError()
else:
return ('', '')
return '', ''
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(side_effect=fake_ssh_exec))
@ -222,8 +304,21 @@ class CIFSHelperIPAccessTestCase(test.TestCase):
share_path, 'writeable=y', 'guest_ok=y',
]
),
mock.call(self.server_details, mock.ANY),
])
def test_create_export_share_does_not_exist_exception(self):
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(
side_effect=[exception.ProcessExecutionError(),
Exception('')]
))
self.assertRaises(
exception.ManilaException, self._helper.create_export,
self.server_details, self.share_name)
def test_create_export_share_exist_recreate_true(self):
ret = self._helper.create_export(self.server_details, self.share_name,
recreate=True)
@ -249,6 +344,7 @@ class CIFSHelperIPAccessTestCase(test.TestCase):
share_path, 'writeable=y', 'guest_ok=y',
]
),
mock.call(self.server_details, mock.ANY),
])
def test_create_export_share_exist_recreate_false(self):
@ -298,122 +394,49 @@ class CIFSHelperIPAccessTestCase(test.TestCase):
),
])
def test_allow_access_ip_exist(self):
hosts = [self.access['access_to'], ]
self.mock_object(self._helper, '_get_allow_hosts',
mock.Mock(return_value=hosts))
self.mock_object(self._helper, '_set_allow_hosts')
self.assertRaises(
exception.ShareAccessExists,
self._helper.allow_access,
self.server_details,
self.share_name,
self.access['access_type'],
self.access['access_level'],
self.access['access_to'])
self._helper._get_allow_hosts.assert_called_once_with(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_has_calls([])
def test_allow_access_ip_does_not_exist(self):
hosts = []
self.mock_object(self._helper, '_get_allow_hosts',
mock.Mock(return_value=hosts))
self.mock_object(self._helper, '_set_allow_hosts')
self._helper.allow_access(
self.server_details, self.share_name,
self.access['access_type'], self.access['access_level'],
self.access['access_to'])
self._helper._get_allow_hosts.assert_called_once_with(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_called_once_with(
self.server_details, hosts, self.share_name)
def test_allow_access_wrong_type(self):
self.assertRaises(
exception.InvalidShareAccess,
self._helper.allow_access,
self.server_details,
self.share_name, 'fake', const.ACCESS_LEVEL_RW, '1.1.1.1')
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
def test_allow_access_wrong_access_level(self, data):
def test_update_access_wrong_access_level(self):
access_rules = [test_generic.get_fake_access_rule(
'2.2.2.2', const.ACCESS_LEVEL_RO), ]
self.assertRaises(
exception.InvalidShareAccessLevel,
self._helper.allow_access,
self._helper.update_access,
self.server_details,
self.share_name, 'ip', data, '1.1.1.1')
self.share_name,
access_rules)
@ddt.data(const.ACCESS_LEVEL_RO, 'fake')
def test_deny_access_unsupported_access_level(self, data):
access = dict(access_to='1.1.1.1', access_level=data)
self.mock_object(self._helper, '_get_allow_hosts')
self.mock_object(self._helper, '_set_allow_hosts')
self._helper.deny_access(self.server_details, self.share_name, access)
self.assertFalse(self._helper._get_allow_hosts.called)
self.assertFalse(self._helper._set_allow_hosts.called)
def test_deny_access_list_has_value(self):
hosts = [self.access['access_to'], ]
self.mock_object(self._helper, '_get_allow_hosts',
mock.Mock(return_value=hosts))
self.mock_object(self._helper, '_set_allow_hosts')
self._helper.deny_access(
self.server_details, self.share_name, self.access)
self._helper._get_allow_hosts.assert_called_once_with(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_called_once_with(
self.server_details, [], self.share_name)
def test_deny_access_list_does_not_have_value(self):
hosts = []
self.mock_object(self._helper, '_get_allow_hosts',
mock.Mock(return_value=hosts))
self.mock_object(self._helper, '_set_allow_hosts')
self._helper.deny_access(
self.server_details, self.share_name, self.access)
self._helper._get_allow_hosts.assert_called_once_with(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_has_calls([])
def test_deny_access_force(self):
self.mock_object(
self._helper,
'_get_allow_hosts',
mock.Mock(side_effect=exception.ProcessExecutionError()),
)
self.mock_object(self._helper, '_set_allow_hosts')
self._helper.deny_access(
self.server_details, self.share_name, self.access, force=True)
self._helper._get_allow_hosts.assert_called_once_with(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_has_calls([])
def test_deny_access_not_force(self):
def raise_process_execution_error(*args, **kwargs):
raise exception.ProcessExecutionError()
self.mock_object(self._helper, '_get_allow_hosts',
mock.Mock(side_effect=raise_process_execution_error))
self.mock_object(self._helper, '_set_allow_hosts')
def test_update_access_wrong_access_type(self):
access_rules = [test_generic.get_fake_access_rule(
'2.2.2.2', const.ACCESS_LEVEL_RW, access_type='fake'), ]
self.assertRaises(
exception.ProcessExecutionError,
self._helper.deny_access,
self.server_details, self.share_name, self.access)
self._helper._get_allow_hosts.assert_called_once_with(
exception.InvalidShareAccess,
self._helper.update_access,
self.server_details,
self.share_name,
access_rules)
def test_update_access(self):
access_rules = [test_generic.get_fake_access_rule(
'1.1.1.1', const.ACCESS_LEVEL_RW), ]
self._helper.update_access(self.server_details, self.share_name,
access_rules)
self._helper._ssh_exec.assert_called_once_with(
self.server_details, ['sudo', 'net', 'conf', 'setparm',
self.share_name, '"hosts allow"',
'"1.1.1.1"'])
def test_get_allow_hosts(self):
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(
return_value=('1.1.1.1 2.2.2.2 3.3.3.3', '')))
expected = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
result = self._helper._get_allow_hosts(
self.server_details, self.share_name)
self._helper._set_allow_hosts.assert_has_calls([])
self.assertEqual(expected, result)
cmd = ['sudo', 'net', 'conf', 'getparm', self.share_name,
'\"hosts allow\"']
self._helper._ssh_exec.assert_called_once_with(
self.server_details, cmd)
@ddt.data(
'', '1.2.3.4:/nfs/like/export', '/1.2.3.4/foo', '\\1.2.3.4\\foo',
@ -534,232 +557,36 @@ class CIFSHelperUserAccessTestCase(test.TestCase):
self._helper = helpers.CIFSHelperUserAccess(
self._execute, self._ssh_exec, self.fake_conf)
@ddt.data('ip', 'cert', 'fake')
def test_allow_access_wrong_type(self, wrong_access_type):
def test_update_access_exception_type(self):
access_rules = [test_generic.get_fake_access_rule(
'user1', const.ACCESS_LEVEL_RW, access_type='ip')]
self.assertRaises(exception.InvalidShareAccess,
self._helper.update_access, self.server_details,
self.share_name, access_rules, None, None)
def test_update_access(self):
access_list = [test_generic.get_fake_access_rule(
'user1', const.ACCESS_LEVEL_RW, access_type='user'),
test_generic.get_fake_access_rule(
'user2', const.ACCESS_LEVEL_RO, access_type='user')]
self._helper.update_access(self.server_details, self.share_name,
access_list, None, None)
self._helper._ssh_exec.assert_has_calls([
mock.call(self.server_details,
['sudo', 'net', 'conf', 'setparm', self.share_name,
'valid users', '"user1"']),
mock.call(self.server_details,
['sudo', 'net', 'conf', 'setparm', self.share_name,
'read list', '"user2"'])
])
def test_update_access_exception_level(self):
access_rules = [test_generic.get_fake_access_rule(
'user1', 'fake_level', access_type='user'), ]
self.assertRaises(
exception.InvalidShareAccess,
self._helper.allow_access,
exception.InvalidShareAccessLevel,
self._helper.update_access,
self.server_details,
self.share_name,
wrong_access_type,
const.ACCESS_LEVEL_RW,
'1.1.1.1')
@ddt.data(access_rw, access_ro)
def test_allow_access_ro_rule_does_not_exist(self, access):
users = ['user1', 'user2']
self.mock_object(self._helper, '_get_valid_users',
mock.Mock(return_value=users))
self.mock_object(self._helper, '_set_valid_users')
self._helper.allow_access(
self.server_details, self.share_name,
access['access_type'], access['access_level'],
access['access_to'])
self.assertEqual(
[mock.call(self.server_details, self.share_name),
mock.call(self.server_details, self.share_name,
access['access_level'])],
self._helper._get_valid_users.call_args_list)
self._helper._set_valid_users.assert_called_once_with(
self.server_details,
users,
self.share_name,
access['access_level'])
@ddt.data(access_rw, access_ro)
def test_allow_access_ro_rule_exists(self, access):
users = ['user1', 'user2', 'manila-user']
self.mock_object(self._helper, '_get_valid_users',
mock.Mock(return_value=users))
self.assertRaises(
exception.ShareAccessExists,
self._helper.allow_access,
self.server_details,
self.share_name,
access['access_type'],
access['access_level'],
access['access_to'])
@ddt.data(access_rw, access_ro)
def test_deny_access_list_has_value(self, access):
users = ['user1', 'user2', 'manila-user']
self.mock_object(self._helper, '_get_valid_users',
mock.Mock(return_value=users))
self.mock_object(self._helper, '_set_valid_users')
self._helper.deny_access(
self.server_details, self.share_name, access)
self._helper._get_valid_users.assert_called_once_with(
self.server_details,
self.share_name,
access['access_level'],
force=False)
self._helper._set_valid_users.assert_called_once_with(
self.server_details, ['user1', 'user2'], self.share_name,
access['access_level'])
@ddt.data(access_rw, access_ro)
def test_deny_access_list_does_not_have_value(self, access):
users = []
self.mock_object(self._helper, '_get_valid_users',
mock.Mock(return_value=users))
self.mock_object(self._helper, '_set_valid_users')
self._helper.deny_access(
self.server_details, self.share_name, access)
self._helper._get_valid_users.assert_called_once_with(
self.server_details,
self.share_name,
access['access_level'],
force=False)
self._helper._set_valid_users.assert_has_calls([])
@ddt.data(access_rw, access_ro)
def test_deny_access_force_access_exists(self, access):
users = ['user1', 'user2', 'manila-user']
self.mock_object(self._helper, '_get_valid_users',
mock.Mock(return_value=users))
self.mock_object(self._helper, '_set_valid_users')
self._helper.deny_access(
self.server_details, self.share_name, access, force=True)
self._helper._get_valid_users.assert_called_once_with(
self.server_details,
self.share_name,
access['access_level'],
force=True)
self._helper._set_valid_users.assert_called_once_with(
self.server_details, ['user1', 'user2'], self.share_name,
access['access_level'])
@ddt.data(access_rw, access_ro)
def test_deny_access_force_access_does_not_exist(self, access):
self.mock_object(
self._helper,
'_get_valid_users',
mock.Mock(return_value=[]),
)
self.mock_object(self._helper, '_set_valid_users')
self._helper.deny_access(
self.server_details, self.share_name, access, force=True)
self._helper._get_valid_users.assert_called_once_with(
self.server_details,
self.share_name,
access['access_level'],
force=True)
self._helper._set_valid_users.assert_has_calls([])
@ddt.data(access_rw, access_ro)
def test_deny_access_force_exc(self, access):
self.mock_object(
self._helper,
'_get_valid_users',
mock.Mock(side_effect=exception.ProcessExecutionError()),
)
self.mock_object(self._helper, '_set_valid_users')
self.assertRaises(exception.ProcessExecutionError,
self._helper.deny_access,
self.server_details,
self.share_name,
access,
force=True)
self._helper._get_valid_users.assert_called_once_with(
self.server_details,
self.share_name,
access['access_level'],
force=True)
def test_get_conf_param_rw(self):
result = self._helper._get_conf_param(const.ACCESS_LEVEL_RW)
self.assertEqual('valid users', result)
def test_get_conf_param_ro(self):
result = self._helper._get_conf_param(const.ACCESS_LEVEL_RO)
self.assertEqual('read list', result)
@ddt.data(False, True)
def test_get_valid_users(self, force):
users = ("\"manila-user\" \"user1\" \"user2\"", None)
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(return_value=users))
result = self._helper._get_valid_users(self.server_details,
self.share_name,
const.ACCESS_LEVEL_RW,
force=force)
self.assertEqual(['manila-user', 'user1', 'user2'], result)
self._helper._ssh_exec.assert_called_once_with(
self.server_details,
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
@ddt.data(False, True)
def test_get_valid_users_access_level_none(self, force):
def fake_ssh_exec(*args, **kwargs):
if 'valid users' in args[1]:
return ("\"user1\"", '')
else:
return ("\"user2\"", '')
self.mock_object(self._helper, '_ssh_exec',
mock.Mock(side_effect=fake_ssh_exec))
result = self._helper._get_valid_users(self.server_details,
self.share_name,
force=force)
self.assertEqual(['user1', 'user2'], result)
for param in ['read list', 'valid users']:
self._helper._ssh_exec.assert_any_call(
self.server_details,
['sudo', 'net', 'conf', 'getparm', self.share_name, param])
def test_get_valid_users_access_level_none_with_exc(self):
self.mock_object(
self._helper,
'_ssh_exec',
mock.Mock(side_effect=exception.ProcessExecutionError()))
self.assertRaises(exception.ProcessExecutionError,
self._helper._get_valid_users,
self.server_details,
self.share_name,
force=False)
self._helper._ssh_exec.assert_called_once_with(
self.server_details,
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
def test_get_valid_users_force_with_exc(self):
self.mock_object(
self._helper,
'_ssh_exec',
mock.Mock(side_effect=exception.ProcessExecutionError()))
result = self._helper._get_valid_users(self.server_details,
self.share_name,
const.ACCESS_LEVEL_RW)
self.assertEqual([], result)
self._helper._ssh_exec.assert_called_once_with(
self.server_details,
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
def test_get_valid_users_not_force_with_exc(self):
self.mock_object(
self._helper,
'_ssh_exec',
mock.Mock(side_effect=exception.ProcessExecutionError()))
self.assertRaises(exception.ProcessExecutionError,
self._helper._get_valid_users, self.server_details,
self.share_name, const.ACCESS_LEVEL_RW, force=False)
self._helper._ssh_exec.assert_called_once_with(
self.server_details,
['sudo', 'net', 'conf', 'getparm', self.share_name, 'valid users'])
def test_set_valid_users(self):
self.mock_object(self._helper, '_ssh_exec', mock.Mock())
self._helper._set_valid_users(self.server_details, ['user1', 'user2'],
self.share_name, const.ACCESS_LEVEL_RW)
self._helper._ssh_exec.assert_called_once_with(
self.server_details,
['sudo', 'net', 'conf', 'setparm', self.share_name,
'valid users', '"user1 user2"'])
access_rules)

View File

@ -16,9 +16,11 @@
import os
import ddt
import mock
from oslo_config import cfg
from manila.common import constants as const
from manila import context
from manila import exception
from manila.share import configuration
@ -26,6 +28,7 @@ from manila.share.drivers import lvm
from manila import test
from manila.tests.db import fakes as db_fakes
from manila.tests import fake_utils
from manila.tests.share.drivers import test_generic
CONF = cfg.CONF
@ -69,6 +72,7 @@ def fake_access(**kwargs):
return db_fakes.FakeModel(access)
@ddt.ddt
class LVMShareDriverTestCase(test.TestCase):
"""Tests LVMShareDriver."""
@ -358,23 +362,22 @@ class LVMShareDriverTestCase(test.TestCase):
self.server,
self.share['name'])
def test_allow_access(self):
mount_path = self._get_mount_path(self.share)
self._helper_nfs.allow_access(mount_path,
self.share['name'],
self.access['access_type'],
self.access['access_to'])
self._driver.allow_access(self._context, self.share, self.access,
self.share_server)
def test_deny_access(self):
mount_path = self._get_mount_path(self.share)
self._helper_nfs.deny_access(mount_path,
self.share['name'],
self.access['access_type'],
self.access['access_to'])
self._driver.deny_access(self._context, self.share, self.access,
self.share_server)
@ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
def test_update_access(self, access_level):
access_rules = [test_generic.get_fake_access_rule(
'1.1.1.1', access_level), ]
add_rules = [test_generic.get_fake_access_rule(
'2.2.2.2', access_level), ]
delete_rules = [test_generic.get_fake_access_rule(
'3.3.3.3', access_level), ]
self._driver.update_access(self._context, self.share, access_rules,
add_rules=add_rules,
delete_rules=delete_rules,
share_server=self.server)
(self._driver._helpers[self.share['share_proto']].
update_access.assert_called_once_with(
self.server, self.share['name'],
access_rules, add_rules=add_rules, delete_rules=delete_rules))
def test_mount_device(self):
mount_path = self._get_mount_path(self.share)
@ -448,14 +451,15 @@ class LVMShareDriverTestCase(test.TestCase):
command = ['fake_command']
self.mock_object(self._driver, '_execute')
self._driver._ssh_exec_as_root('fake_server', command)
self._driver._execute.assert_called_once_with('fake_command')
self._driver._execute.assert_called_once_with('fake_command',
check_exit_code=True)
def test_ssh_exec_as_root_with_sudo(self):
command = ['sudo', 'fake_command']
self.mock_object(self._driver, '_execute')
self._driver._ssh_exec_as_root('fake_server', command)
self._driver._execute.assert_called_once_with('fake_command',
run_as_root=True)
self._driver._execute.assert_called_once_with(
'fake_command', run_as_root=True, check_exit_code=True)
def test_extend_container(self):
self.mock_object(self._driver, '_try_execute')