Merge "Implement update_access in Isilon Driver."

This commit is contained in:
Jenkins 2017-05-31 05:02:54 +00:00 committed by Gerrit Code Review
commit 5b0a5b877e
3 changed files with 538 additions and 1 deletions

View File

@ -120,6 +120,12 @@ class EMCShareDriver(driver.ShareDriver):
"""Deny access to the share.""" """Deny access to the share."""
self.plugin.deny_access(context, share, access, share_server) self.plugin.deny_access(context, share, access, share_server)
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update access to the share."""
self.plugin.update_access(context, share, access_rules, add_rules,
delete_rules, share_server)
def check_for_setup_error(self): def check_for_setup_error(self):
"""Check for setup error.""" """Check for setup error."""
self.plugin.check_for_setup_error() self.plugin.check_for_setup_error()

View File

@ -20,6 +20,7 @@ import os
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
from oslo_utils import units from oslo_utils import units
from requests.exceptions import HTTPError
import six import six
from manila.common import constants as const from manila.common import constants as const
@ -253,7 +254,7 @@ class IsilonStorageConnection(base.StorageConnection):
host_acl.append(allowed_ip) host_acl.append(allowed_ip)
data = {'host_acl': host_acl} data = {'host_acl': host_acl}
url = ('{0}/platform/1/protocols/smb/shares/{1}' url = ('{0}/platform/1/protocols/smb/shares/{1}'
.format(self._server_url, smb_share['name'])) .format(self._server_url, share['name']))
r = self._isilon_api.request('PUT', url, data=data) r = self._isilon_api.request('PUT', url, data=data)
r.raise_for_status() r.raise_for_status()
@ -402,3 +403,114 @@ class IsilonStorageConnection(base.StorageConnection):
def teardown_server(self, server_details, security_services=None): def teardown_server(self, server_details, security_services=None):
"""Teardown share server.""" """Teardown share server."""
# TODO(Shaun Edwards): Look into supporting share servers # TODO(Shaun Edwards): Look into supporting share servers
def update_access(self, context, share, access_rules, add_rules,
delete_rules, share_server=None):
"""Update share access."""
if share['share_proto'] == 'NFS':
state_map = self._update_access_nfs(share, access_rules)
if share['share_proto'] == 'CIFS':
state_map = self._update_access_cifs(share, access_rules)
return state_map
def _update_access_nfs(self, share, access_rules):
"""Updates access on a NFS share."""
nfs_rw_ips = set()
nfs_ro_ips = set()
rule_state_map = {}
for rule in access_rules:
rule_state_map[rule['access_id']] = {
'state': 'error'
}
for rule in access_rules:
if rule['access_level'] == const.ACCESS_LEVEL_RW:
nfs_rw_ips.add(rule['access_to'])
elif rule['access_level'] == const.ACCESS_LEVEL_RO:
nfs_ro_ips.add(rule['access_to'])
export_id = self._isilon_api.lookup_nfs_export(
self._get_container_path(share))
if export_id is None:
# share does not exist on backend (set all rules to error state)
return rule_state_map
data = {
'clients': list(nfs_rw_ips),
'read_only_clients': list(nfs_ro_ips)
}
url = ('{0}/platform/1/protocols/nfs/exports/{1}'
.format(self._server_url, six.text_type(export_id)))
r = self._isilon_api.request('PUT', url, data=data)
try:
r.raise_for_status()
except HTTPError:
return rule_state_map
# if we finish the bulk rule update with no error set rules to active
for rule in access_rules:
rule_state_map[rule['access_id']]['state'] = 'active'
return rule_state_map
def _update_access_cifs(self, share, access_rules):
"""Clear access on a CIFS share."""
cifs_ip_set = set()
users = set()
for rule in access_rules:
if rule['access_type'] == 'ip':
cifs_ip_set.add('allow:' + rule['access_to'])
elif rule['access_type'] == 'user':
users.add(rule['access_to'])
smb_share = self._isilon_api.lookup_smb_share(share['name'])
backend_smb_user_permissions = smb_share['permissions']
perms_to_remove = []
for perm in backend_smb_user_permissions:
if perm['trustee']['name'] not in users:
perms_to_remove.append(perm)
for perm in perms_to_remove:
backend_smb_user_permissions.remove(perm)
data = {
'host_acl': list(cifs_ip_set),
'permissions': backend_smb_user_permissions,
}
url = ('{0}/platform/1/protocols/smb/shares/{1}'
.format(self._server_url, share['name']))
r = self._isilon_api.request('PUT', url, data=data)
try:
r.raise_for_status()
except HTTPError:
# clear access rules failed so set all access rules to error state
rule_state_map = {}
for rule in access_rules:
rule_state_map[rule['access_id']] = {
'state': 'error'
}
return rule_state_map
# add access rules that don't exist on backend
rule_state_map = {}
for rule in access_rules:
rule_state_map[rule['access_id']] = {
'state': 'error'
}
try:
if rule['access_type'] == 'ip':
self._cifs_allow_access_ip(rule['access_to'], share,
rule['access_level'])
rule_state_map[rule['access_id']]['state'] = 'active'
elif rule['access_type'] == 'user':
backend_users = set()
for perm in backend_smb_user_permissions:
backend_users.add(perm['trustee']['name'])
if rule['access_to'] not in backend_users:
self._cifs_allow_access_user(
rule['access_to'], share, rule['access_level'])
rule_state_map[rule['access_id']]['state'] = 'active'
else:
continue
except exception.ManilaException:
pass
return rule_state_map

View File

@ -17,10 +17,12 @@ import ddt
import mock import mock
from oslo_log import log from oslo_log import log
from oslo_utils import units from oslo_utils import units
from requests.exceptions import HTTPError
import six import six
from manila.common import constants as const from manila.common import constants as const
from manila import exception from manila import exception
from manila.i18n import _
from manila.share.drivers.dell_emc.plugins.isilon import isilon from manila.share.drivers.dell_emc.plugins.isilon import isilon
from manila.share.drivers.dell_emc.plugins.isilon import isilon_api from manila.share.drivers.dell_emc.plugins.isilon import isilon_api
from manila import test from manila import test
@ -764,3 +766,420 @@ class IsilonTest(test.TestCase):
expected_quota_size = new_share_size * units.Gi expected_quota_size = new_share_size * units.Gi
self._mock_isilon_api.quota_set.assert_called_once_with( self._mock_isilon_api.quota_set.assert_called_once_with(
share_path, 'directory', expected_quota_size) share_path, 'directory', expected_quota_size)
def test_update_access_add_nfs(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
}
fake_export_id = 4
self._mock_isilon_api.lookup_nfs_export.return_value = fake_export_id
self._mock_isilon_api.get_nfs_export.return_value = {
'clients': [],
'read_only_clients': []
}
nfs_access = {
'access_type': 'ip',
'access_to': '10.1.1.10',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = [nfs_access]
add_rules = [nfs_access]
delete_rules = []
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules,
delete_rules, share_server=None)
expected_url = (self.API_URL + '/platform/1/protocols/nfs/exports/' +
str(fake_export_id))
expected_data = {'clients': ['10.1.1.10'], 'read_only_clients': []}
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
}
}
self._mock_isilon_api.request.assert_called_once_with(
'PUT', expected_url, data=expected_data)
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_add_cifs(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
access = {
'access_type': 'user',
'access_to': 'foo',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
add_rules = [access]
access_rules = [access]
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
self._mock_isilon_api.smb_permissions_add.assert_called_once_with(
self.SHARE_NAME, 'foo', isilon_api.SmbPermission.rw)
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_delete_nfs(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
}
fake_export_id = 4
self._mock_isilon_api.lookup_nfs_export.return_value = fake_export_id
# simulate an IP added to the whitelist
ip_addr = '10.0.0.4'
ip_addr_ro = '10.0.0.50'
self._mock_isilon_api.get_nfs_export.return_value = {
'clients': [ip_addr], 'read_only_clients': [ip_addr_ro]}
nfs_access_del_1 = {
'access_type': 'ip',
'access_to': ip_addr,
'access_level': const.ACCESS_LEVEL_RW
}
nfs_access_del_2 = {
'access_type': 'ip',
'access_to': ip_addr,
'access_level': const.ACCESS_LEVEL_RW
}
access_rules = []
delete_rules = [nfs_access_del_1, nfs_access_del_2]
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, [], delete_rules)
expected_url = (self.API_URL + '/platform/1/protocols/nfs/exports/' +
six.text_type(fake_export_id))
expected_data = {'clients': [], 'read_only_clients': []}
self._mock_isilon_api.request.assert_called_once_with(
'PUT', expected_url, data=expected_data)
self.assertEqual({}, rule_map)
def test_update_access_delete_cifs(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
delete_rule = {
'access_type': 'user',
'access_to': 'newuser',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '29960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = []
delete_rules = [delete_rule]
self._mock_isilon_api.lookup_smb_share.return_value = {
'permissions': [
{
'permission': 'change',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-21',
'name': 'newuser',
'type': 'user',
}
}
]
}
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, [], delete_rules)
expected_url = (self.API_URL + '/platform/1/protocols/smb/shares/' +
self.SHARE_NAME)
self._mock_isilon_api.request.assert_called_once_with(
'PUT', expected_url, data={'permissions': [], 'host_acl': []})
self.assertEqual({}, rule_map)
def test_update_access_nfs_share_not_found(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
}
access = {
'access_type': 'user',
'access_to': 'foouser',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = [access]
add_rules = [access]
self._mock_isilon_api.lookup_nfs_export.return_value = None
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'error'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_nfs_http_error_on_clear_rules(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
}
access = {
'access_type': 'user',
'access_to': 'foouser',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = [access]
add_rules = [access]
self._mock_isilon_api.request.return_value.raise_for_status.\
side_effect = HTTPError
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'error'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_cifs_http_error_on_clear_rules(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
access = {
'access_type': 'user',
'access_to': 'foo',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
add_rules = [access]
access_rules = [access]
self._mock_isilon_api.request.return_value.raise_for_status.\
side_effect = HTTPError
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'error'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_cifs_share_backend_error(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
access = {
'access_type': 'user',
'access_to': 'foo',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
add_rules = [access]
access_rules = [access]
message = _('Only "RW" and "RO" access levels are supported.')
self._mock_isilon_api.smb_permissions_add.side_effect = \
exception.ShareBackendException(message=message)
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'error'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_cifs_invalid_access_type(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
access = {
'access_type': 'foo',
'access_to': 'foo',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
add_rules = [access]
access_rules = [access]
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, add_rules, [])
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'error'
}
}
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_recover_nfs(self):
# verify that new ips are added and ips not in rules are removed
share = {
"name": self.SHARE_NAME,
"share_proto": 'NFS',
}
fake_export_id = 4
self._mock_isilon_api.lookup_nfs_export.return_value = fake_export_id
self._mock_isilon_api.get_nfs_export.return_value = {
'clients': ['10.1.1.8'],
'read_only_clients': ['10.2.0.2']
}
nfs_access_1 = {
'access_type': 'ip',
'access_to': '10.1.1.10',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
nfs_access_2 = {
'access_type': 'ip',
'access_to': '10.1.1.2',
'access_level': const.ACCESS_LEVEL_RO,
'access_id': '19960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = [nfs_access_1, nfs_access_2]
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, [], [])
expected_url = (self.API_URL + '/platform/1/protocols/nfs/exports/' +
six.text_type(fake_export_id))
expected_data = {
'clients': ['10.1.1.10'],
'read_only_clients': ['10.1.1.2']
}
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
},
'19960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
}
}
self._mock_isilon_api.request.assert_called_once_with(
'PUT', expected_url, data=expected_data)
self.assertEqual(expected_rule_map, rule_map)
def test_update_access_recover_cifs(self):
share = {
"name": self.SHARE_NAME,
"share_proto": 'CIFS',
}
mock_smb_share_1 = {
'host_acl': ['allow:10.1.2.3', 'allow:10.1.1.12'],
'permissions': [
{
'permission': 'change',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-21',
'name': 'foouser',
'type': 'user',
}
},
{
'permission': 'ro',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-22',
'name': 'testuser',
'type': 'user',
}
}
]
}
mock_smb_share_2 = {
'host_acl': ['allow:10.1.2.3', 'allow:10.1.1.12',
'allow:10.1.1.10'],
'permissions': [
{
'permission': 'change',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-21',
'name': 'foouser',
'type': 'user',
}
},
{
'permission': 'ro',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-22',
'name': 'testuser',
'type': 'user',
}
}
]
}
self._mock_isilon_api.lookup_smb_share.side_effect = [
mock_smb_share_1, mock_smb_share_2]
access_1 = {
'access_type': 'ip',
'access_to': '10.1.1.10',
'access_level': const.ACCESS_LEVEL_RW,
'access_id': '09960614-8574-4e03-89cf-7cf267b0bd08'
}
access_2 = {
'access_type': 'user',
'access_to': 'testuser',
'access_level': const.ACCESS_LEVEL_RO,
'access_id': '19960614-8574-4e03-89cf-7cf267b0bd08'
}
access_rules = [access_1, access_2]
rule_map = self.storage_connection.update_access(
self.mock_context, share, access_rules, [], [])
expected_url = (self.API_URL + '/platform/1/protocols/smb/shares/' +
self.SHARE_NAME)
expected_data = {
'host_acl': ['allow:10.1.1.10'],
'permissions': [
{
'permission': 'ro',
'permission_type': 'allow',
'trustee': {
'id': 'SID:S-1-5-22',
'name': 'testuser',
'type': 'user',
}
}
]
}
expected_rule_map = {
'09960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
},
'19960614-8574-4e03-89cf-7cf267b0bd08': {
'state': 'active'
}
}
self.assertEqual(2, self._mock_isilon_api.lookup_smb_share.call_count)
http_method, url = self._mock_isilon_api.request.call_args[0]
data = self._mock_isilon_api.request.call_args[1]['data']
self.assertEqual('PUT', http_method)
self.assertEqual(expected_url, url)
self.assertEqual(expected_data['host_acl'], data['host_acl'])
self.assertEqual(1, len(data['permissions']))
self.assertEqual(expected_data['permissions'][0],
data['permissions'][0])
self.assertEqual(expected_rule_map, rule_map)