Merge "Add "update_access" interface support for VNX."

This commit is contained in:
Jenkins 2016-12-09 13:15:06 +00:00 committed by Gerrit Code Review
commit 4936ffceac
7 changed files with 427 additions and 8 deletions
manila
share/drivers/dell_emc/plugins
tests/share/drivers/dell_emc/plugins/vnx

@ -61,6 +61,11 @@ class StorageConnection(object):
def deny_access(self, context, share, access, share_server):
"""Deny access to the share."""
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update access rules for given share."""
raise NotImplementedError()
def raise_connect_error(self):
"""Check for setup error."""
pass

@ -370,6 +370,61 @@ class VNXStorageConnection(driver.StorageConnection):
self._get_context('NFSShare').allow_share_access(
share['id'], host_ip, vdm_name, access_level)
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
# deleting rules
for rule in delete_rules:
self.deny_access(context, share, rule, share_server)
# adding rules
for rule in add_rules:
self.allow_access(context, share, rule, share_server)
# recovery mode
if not (add_rules or delete_rules):
white_list = []
for rule in access_rules:
self.allow_access(context, share, rule, share_server)
white_list.append(rule['access_to'])
self.clear_access(share, share_server, white_list)
def clear_access(self, share, share_server, white_list):
share_proto = share['share_proto'].upper()
share_name = share['id']
if share_proto == 'CIFS':
self._cifs_clear_access(share_name, share_server, white_list)
elif share_proto == 'NFS':
self._nfs_clear_access(share_name, share_server, white_list)
@vnx_utils.log_enter_exit
def _cifs_clear_access(self, share_name, share_server, white_list):
"""Clear access for CIFS share except hosts in the white list."""
vdm_name = self._get_share_server_name(share_server)
# Check if CIFS server exists.
server_name = vdm_name
status, server = self._get_context('CIFSServer').get(server_name,
vdm_name)
if status != constants.STATUS_OK:
message = (_("CIFS server %(server_name)s has issue. "
"Detail: %(status)s") %
{'server_name': server_name, 'status': status})
raise exception.EMCVnxXMLAPIError(err=message)
self._get_context('CIFSShare').clear_share_access(
share_name=share_name,
mover_name=vdm_name,
domain=server['domain'],
white_list_users=white_list)
@vnx_utils.log_enter_exit
def _nfs_clear_access(self, share_name, share_server, white_list):
"""Clear access for NFS share except hosts in the white list."""
self._get_context('NFSShare').clear_share_access(
share_name=share_name,
mover_name=self._get_share_server_name(share_server),
white_list_hosts=white_list)
def deny_access(self, context, share, access, share_server=None):
"""Deny access to a share."""
share_proto = share['share_proto']

@ -1730,6 +1730,47 @@ class CIFSShare(StorageObject):
LOG.error(message)
raise exception.EMCVnxXMLAPIError(err=message)
def get_share_access(self, mover_name, share_name):
get_str = 'sharesd %s dump' % share_name
get_access = [
'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
'-v', "%s" % get_str,
]
try:
out, err = self._execute_cmd(get_access, check_exit_code=True)
except processutils.ProcessExecutionError:
msg = _('Failed to get access list of CIFS share %s.') % share_name
LOG.exception(msg)
raise exception.EMCVnxXMLAPIError(err=msg)
ret = {}
name_pattern = re.compile(r"Unix user '(.+?)'")
access_pattern = re.compile(r"ALLOWED:(.+?):")
name = None
for line in out.splitlines():
if name is None:
names = name_pattern.findall(line)
if names:
name = names[0].lower()
else:
accesses = access_pattern.findall(line)
if accesses:
ret[name] = accesses[0].lower()
name = None
return ret
def clear_share_access(self, mover_name, share_name, domain,
white_list_users):
existing_users = self.get_share_access(mover_name, share_name)
white_list_users_set = set(user.lower() for user in white_list_users)
users_to_remove = set(existing_users.keys()) - white_list_users_set
for user in users_to_remove:
self.deny_share_access(mover_name, share_name, user, domain,
existing_users[user])
return users_to_remove
@vnx_utils.decorate_all_methods(vnx_utils.log_enter_exit,
debug_only=True)
@ -1948,9 +1989,45 @@ class NFSShare(StorageObject):
do_deny_access(share_name, host_ip, mover_name)
def clear_share_access(self, share_name, mover_name, white_list_hosts):
@utils.synchronized('emc-shareaccess-' + share_name)
def do_clear_access(share_name, mover_name, white_list_hosts):
def hosts_to_remove(orig_list):
if white_list_hosts is None:
ret = set()
else:
ret = set(white_list_hosts).intersection(set(orig_list))
return ret
status, share = self.get(share_name, mover_name)
if constants.STATUS_OK != status:
message = (_('Query nfs share %(path)s failed. '
'Reason %(err)s.') %
{'path': share_name, 'err': status})
raise exception.EMCVnxXMLAPIError(err=message)
self._set_share_access('/' + share_name,
mover_name,
hosts_to_remove(share['RwHosts']),
hosts_to_remove(share['RoHosts']),
hosts_to_remove(share['RootHosts']),
hosts_to_remove(share['AccessHosts']))
# Update self.nfs_share_map
self.get(share_name, mover_name, force=True,
check_exit_code=True)
do_clear_access(share_name, mover_name, white_list_hosts)
def _set_share_access(self, path, mover_name, rw_hosts, ro_hosts,
root_hosts, access_hosts):
if access_hosts is None:
access_hosts = set()
if '-0.0.0.0/0.0.0.0' not in access_hosts:
access_hosts.add('-0.0.0.0/0.0.0.0')
access_str = ('access=%(access)s'
% {'access': ':'.join(access_hosts)})
if root_hosts:
@ -1959,6 +2036,7 @@ class NFSShare(StorageObject):
access_str += ',rw=%(rw)s' % {'rw': ':'.join(rw_hosts)}
if ro_hosts:
access_str += ',ro=%(ro)s' % {'ro': ':'.join(ro_hosts)}
set_nfs_share_access_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
'-ignore',

@ -134,6 +134,15 @@ class FakeData(object):
emc_nas_password = 'fakepassword'
share_backend_name = 'EMC_NAS_Storage'
cifs_access = """
1478607389: SMB:11: Unix user 'Guest' UID=32769
1478607389: SMB:10: FindUserUid:Access_Password 'Guest',1=0x8001 T=0
1478607389: SHARE: 6: ALLOWED:fullcontrol:S-1-5-15-3399d125-6dcdf5f4
1478607389: SMB:11: Unix user 'Administrator' UID=32768
1478607389: SMB:10: FindUserUid:Access_Password 'Administrator',
1478607389: SHARE: 6: ALLOWED:fullcontrol:S-1-5-15-3399d125
"""
class StorageObjectTestData(object):
def __init__(self):
@ -1106,7 +1115,10 @@ class CIFSServerTestData(StorageObjectTestData):
)
@response
def resp_get_succeed(self, mover_id, is_vdm, join_domain):
def resp_get_succeed(self, mover_id, is_vdm, join_domain,
cifs_server_name=None):
if cifs_server_name is None:
cifs_server_name = self.cifs_server_name
return (
'<QueryStatus maxSeverity="ok"/>'
'<CifsServer interfaces="%(ip)s" type="W2K" '
@ -1122,7 +1134,7 @@ class CIFSServerTestData(StorageObjectTestData):
'alias': self.cifs_server_name[-12:],
'domain': self.domain_name,
'join_domain': 'true' if join_domain else 'false',
'comp_name': self.cifs_server_name}
'comp_name': cifs_server_name}
)
@response
@ -1256,8 +1268,10 @@ class CIFSShareTestData(StorageObjectTestData):
]
def cmd_change_access(self, access_level=const.ACCESS_LEVEL_RW,
action='grant'):
account = self.domain_user + '@' + self.domain_name
action='grant', user=None):
if user is None:
user = self.domain_user
account = user + '@' + self.domain_name
if access_level == const.ACCESS_LEVEL_RW:
str_access = 'fullcontrol'
@ -1277,6 +1291,14 @@ class CIFSShareTestData(StorageObjectTestData):
'-v', '%s' % allow_str,
]
def cmd_get_access(self):
get_str = 'sharesd %s dump' % self.share_name
return [
'env', 'NAS_DB=/nas',
'/nas/bin/.server_config', self.vdm_name,
'-v', '%s' % get_str,
]
def output_allow_access(self):
return (
"Command succeeded: :3 sharesd %(share)s grant "
@ -1373,7 +1395,7 @@ class NFSShareTestData(StorageObjectTestData):
'access=-0.0.0.0/0.0.0.0:%(host)s root=%(host)s '
'rw=%(rw_host)s\n'
% {'mover_name': self.vdm_name,
'host': rw_hosts,
'host': ":".join(rw_hosts),
'path': self.path,
'rw_host': ":".join(rw_hosts)}
)
@ -1383,7 +1405,7 @@ class NFSShareTestData(StorageObjectTestData):
'access=-0.0.0.0/0.0.0.0:%(host)s root=%(host)s '
'ro=%(ro_host)s\n'
% {'mover_name': self.vdm_name,
'host': ro_hosts,
'host': ":".join(ro_hosts),
'path': self.path,
'ro_host': ":".join(ro_hosts)}
)

@ -975,6 +975,158 @@ class StorageConnectionTestCase(test.TestCase):
]
ssh_cmd_mock.assert_has_calls(ssh_calls)
def test_update_access_add_cifs_rw(self):
share_server = fakes.SHARE_SERVER
share = fakes.CIFS_SHARE
access = fakes.CIFS_RW_ACCESS
hook = utils.RequestSideEffect()
hook.append(self.vdm.resp_get_succeed())
hook.append(self.cifs_server.resp_get_succeed(
mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True))
xml_req_mock = utils.EMCMock(side_effect=hook)
self.connection.manager.connectors['XML'].request = xml_req_mock
ssh_hook = utils.SSHSideEffect()
ssh_hook.append(self.cifs_share.output_allow_access())
ssh_cmd_mock = mock.Mock(side_effect=ssh_hook)
self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock
self.connection.update_access(None, share, [], [access], [],
share_server=share_server)
expected_calls = [
mock.call(self.vdm.req_get()),
mock.call(self.cifs_server.req_get(self.vdm.vdm_id)),
]
xml_req_mock.assert_has_calls(expected_calls)
ssh_calls = [
mock.call(self.cifs_share.cmd_change_access(), True),
]
ssh_cmd_mock.assert_has_calls(ssh_calls)
def test_update_access_deny_nfs(self):
share_server = fakes.SHARE_SERVER
share = fakes.NFS_SHARE
access = fakes.NFS_RW_ACCESS
rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts)
rw_hosts.append(access['access_to'])
ssh_hook = utils.SSHSideEffect()
ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=rw_hosts,
ro_hosts=fakes.FakeData.ro_hosts))
ssh_hook.append(self.nfs_share.output_set_access_success())
ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=fakes.FakeData.rw_hosts,
ro_hosts=fakes.FakeData.ro_hosts))
ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook)
self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock
self.connection.update_access(None, share, [], [], [access],
share_server=share_server)
ssh_calls = [
mock.call(self.nfs_share.cmd_get(), True),
mock.call(self.nfs_share.cmd_set_access(
rw_hosts=self.nfs_share.rw_hosts,
ro_hosts=self.nfs_share.ro_hosts), True),
mock.call(self.nfs_share.cmd_get(), True),
]
ssh_cmd_mock.assert_has_calls(ssh_calls)
def test_update_access_recover_nfs_rule(self):
share_server = fakes.SHARE_SERVER
share = fakes.NFS_SHARE
access = fakes.NFS_RW_ACCESS
hosts = ['192.168.1.5']
rw_hosts = copy.deepcopy(fakes.FakeData.rw_hosts)
rw_hosts.append(access['access_to'])
ssh_hook = utils.SSHSideEffect()
ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=rw_hosts,
ro_hosts=fakes.FakeData.ro_hosts))
ssh_hook.append(self.nfs_share.output_set_access_success())
ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=hosts,
ro_hosts=[]))
ssh_cmd_mock = utils.EMCNFSShareMock(side_effect=ssh_hook)
self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock
self.connection.update_access(None, share, [access], [], [],
share_server=share_server)
ssh_calls = [
mock.call(self.nfs_share.cmd_get(), True),
mock.call(self.nfs_share.cmd_set_access(
rw_hosts=hosts,
ro_hosts=[]), True),
mock.call(self.nfs_share.cmd_get(), True),
]
ssh_cmd_mock.assert_has_calls(ssh_calls)
def test_update_access_recover_cifs_rule(self):
share_server = fakes.SHARE_SERVER
share = fakes.CIFS_SHARE
access = fakes.CIFS_RW_ACCESS
hook = utils.RequestSideEffect()
hook.append(self.vdm.resp_get_succeed())
hook.append(self.cifs_server.resp_get_succeed(
mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True))
xml_req_mock = utils.EMCMock(side_effect=hook)
self.connection.manager.connectors['XML'].request = xml_req_mock
ssh_hook = utils.SSHSideEffect()
ssh_hook.append(self.cifs_share.output_allow_access())
ssh_hook.append(fakes.FakeData.cifs_access)
ssh_hook.append('Command succeeded')
ssh_cmd_mock = mock.Mock(side_effect=ssh_hook)
self.connection.manager.connectors['SSH'].run_ssh = ssh_cmd_mock
self.connection.update_access(None, share, [access], [], [],
share_server=share_server)
expected_calls = [
mock.call(self.vdm.req_get()),
mock.call(self.cifs_server.req_get(self.vdm.vdm_id)),
]
xml_req_mock.assert_has_calls(expected_calls)
ssh_calls = [
mock.call(self.cifs_share.cmd_change_access(), True),
mock.call(self.cifs_share.cmd_get_access(), True),
mock.call(self.cifs_share.cmd_change_access(
action='revoke', user='guest'), True),
]
ssh_cmd_mock.assert_has_calls(ssh_calls)
def test_cifs_clear_access_server_not_found(self):
server = fakes.SHARE_SERVER
hook = utils.RequestSideEffect()
hook.append(self.vdm.resp_get_succeed())
hook.append(self.cifs_server.resp_get_succeed(
mover_id=self.vdm.vdm_id, is_vdm=True, join_domain=True,
cifs_server_name='cifs_server_name'))
xml_req_mock = utils.EMCMock(side_effect=hook)
self.connection.manager.connectors['XML'].request = xml_req_mock
self.assertRaises(exception.EMCVnxXMLAPIError,
self.connection._cifs_clear_access,
'share_name', server, None)
expected_calls = [
mock.call(self.vdm.req_get()),
mock.call(self.cifs_server.req_get(self.vdm.vdm_id)),
]
xml_req_mock.assert_has_calls(expected_calls)
def test_allow_cifs_rw_access(self):
share_server = fakes.SHARE_SERVER
share = fakes.CIFS_SHARE

@ -2738,6 +2738,63 @@ class CIFSShareTestCase(StorageObjectTestCaseBase):
]
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_get_share_access(self):
self.ssh_hook.append(fakes.FakeData.cifs_access)
context = self.manager.getStorageContext('CIFSShare')
context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook)
ret = context.get_share_access(
mover_name=self.vdm.vdm_name,
share_name=self.cifs_share.share_name)
ssh_calls = [
mock.call(self.cifs_share.cmd_get_access(), True),
]
self.assertEqual(2, len(ret))
self.assertEqual(constants.CIFS_ACL_FULLCONTROL, ret['administrator'])
self.assertEqual(constants.CIFS_ACL_FULLCONTROL, ret['guest'])
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_get_share_access_failed(self):
expt_err = processutils.ProcessExecutionError(
stdout=self.nfs_share.fake_output)
self.ssh_hook.append(ex=expt_err)
context = self.manager.getStorageContext('CIFSShare')
context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook)
self.assertRaises(exception.EMCVnxXMLAPIError,
context.get_share_access,
mover_name=self.vdm.vdm_name,
share_name=self.cifs_share.share_name)
ssh_calls = [
mock.call(self.cifs_share.cmd_get_access(), True),
]
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_clear_share_access_has_white_list(self):
self.ssh_hook.append(fakes.FakeData.cifs_access)
self.ssh_hook.append('Command succeeded')
context = self.manager.getStorageContext('CIFSShare')
context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook)
to_remove = context.clear_share_access(
mover_name=self.vdm.vdm_name,
share_name=self.cifs_share.share_name,
domain=self.cifs_server.domain_name,
white_list_users=['guest'])
ssh_calls = [
mock.call(self.cifs_share.cmd_get_access(), True),
mock.call(self.cifs_share.cmd_change_access(action='revoke'),
True),
]
self.assertEqual({'administrator'}, to_remove)
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
class NFSShareTestCase(StorageObjectTestCaseBase):
def setUp(self):
@ -3010,6 +3067,32 @@ class NFSShareTestCase(StorageObjectTestCaseBase):
]
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_clear_share_access(self):
hosts = ['192.168.1.1', '192.168.1.3']
self.ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=self.nfs_share.rw_hosts,
ro_hosts=self.nfs_share.ro_hosts))
self.ssh_hook.append(self.nfs_share.output_set_access_success())
self.ssh_hook.append(self.nfs_share.output_get_succeed(
rw_hosts=[hosts[0]], ro_hosts=[hosts[1]]))
context = self.manager.getStorageContext('NFSShare')
context.conn['SSH'].run_ssh = utils.EMCNFSShareMock(
side_effect=self.ssh_hook)
context.clear_share_access(share_name=self.nfs_share.share_name,
mover_name=self.vdm.vdm_name,
white_list_hosts=hosts)
ssh_calls = [
mock.call(self.nfs_share.cmd_get()),
mock.call(self.nfs_share.cmd_set_access(
rw_hosts=[hosts[0]], ro_hosts=[hosts[1]])),
mock.call(self.nfs_share.cmd_get()),
]
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_deny_ro_share_access(self):
ro_hosts = copy.deepcopy(self.nfs_share.ro_hosts)
ro_hosts.append(self.nfs_share.nfs_host_ip)
@ -3085,3 +3168,18 @@ class NFSShareTestCase(StorageObjectTestCaseBase):
self.nfs_share.ro_hosts)),
]
context.conn['SSH'].run_ssh.assert_has_calls(ssh_calls)
def test_clear_share_access_failed_to_get_share(self):
self.ssh_hook.append("no output.")
context = self.manager.getStorageContext('NFSShare')
context.conn['SSH'].run_ssh = mock.Mock(side_effect=self.ssh_hook)
self.assertRaises(exception.EMCVnxXMLAPIError,
context.clear_share_access,
share_name=self.nfs_share.share_name,
mover_name=self.vdm.vdm_name,
white_list_hosts=None)
context.conn['SSH'].run_ssh.assert_called_once_with(
self.nfs_share.cmd_get(), False)

@ -135,6 +135,15 @@ class EMCNFSShareMock(mock.Mock):
return option_map
@staticmethod
def _opt_value_from_map(opt_map, key):
value = opt_map.get(key)
if value:
ret = set(value.split(':'))
else:
ret = set()
return ret
def _option_check(self, expect, actual):
if '-option' in actual and '-option' in expect:
exp_option = expect[expect.index('-option') + 1]
@ -144,8 +153,8 @@ class EMCNFSShareMock(mock.Mock):
act_opt_map = self._option_parser(act_option)
for key in exp_opt_map:
exp_set = set(exp_opt_map[key].split(':'))
act_set = set(act_opt_map[key].split(':'))
exp_set = self._opt_value_from_map(exp_opt_map, key)
act_set = self._opt_value_from_map(act_opt_map, key)
if exp_set != act_set:
return False