diff --git a/manila/common/constants.py b/manila/common/constants.py index 7f8db23abd..f2654765a9 100644 --- a/manila/common/constants.py +++ b/manila/common/constants.py @@ -25,6 +25,9 @@ STATUS_DEACTIVATING = 'DEACTIVATING' SECURITY_SERVICES_ALLOWED_TYPES = ['active_directory', 'ldap', 'kerberos'] +NFS_EXPORTS_FILE = '/etc/exports' +NFS_EXPORTS_FILE_TEMP = '/var/lib/nfs/etab' + # Below represented ports are ranges (from, to) CIFS_PORTS = ( ("tcp", (445, 445)), diff --git a/manila/share/drivers/generic.py b/manila/share/drivers/generic.py index 2b163b1414..402a51d786 100644 --- a/manila/share/drivers/generic.py +++ b/manila/share/drivers/generic.py @@ -22,6 +22,7 @@ import time from oslo.config import cfg import six +from manila.common import constants as const from manila import compute from manila import context from manila import exception @@ -614,6 +615,20 @@ class NASHelperBase(object): raise NotImplementedError() +def nfs_synchronized(f): + + def wrapped_func(self, *args, **kwargs): + key = "nfs-%s" % args[0]["instance_id"] + + @utils.synchronized(key) + def source_func(self, *args, **kwargs): + return f(self, *args, **kwargs) + + return source_func(self, *args, **kwargs) + + return wrapped_func + + class NFSHelper(NASHelperBase): """Interface to work with share.""" @@ -637,6 +652,7 @@ class NFSHelper(NASHelperBase): """Remove export.""" pass + @nfs_synchronized def allow_access(self, server, share_name, access_type, access): """Allow access to the host.""" local_path = os.path.join(self.configuration.share_mount_path, @@ -654,7 +670,9 @@ class NFSHelper(NASHelperBase): self._ssh_exec(server, ['sudo', 'exportfs', '-o', 'rw,no_subtree_check', ':'.join([access, local_path])]) + self._sync_nfs_temp_and_perm_files(server) + @nfs_synchronized def deny_access(self, server, share_name, access_type, access, force=False): """Deny access to the host.""" @@ -662,6 +680,20 @@ class NFSHelper(NASHelperBase): share_name) self._ssh_exec(server, ['sudo', 'exportfs', '-u', ':'.join([access, local_path])]) + self._sync_nfs_temp_and_perm_files(server) + + def _sync_nfs_temp_and_perm_files(self, server): + """Sync changes of exports with permanent NFS config file. + + This is required to ensure, that after share server reboot, exports + still exist. + """ + sync_cmd = [ + 'sudo', 'cp ', const.NFS_EXPORTS_FILE_TEMP, const.NFS_EXPORTS_FILE, + '&&', + 'sudo', 'exportfs', '-a', + ] + self._ssh_exec(server, sync_cmd) class CIFSHelper(NASHelperBase): diff --git a/manila/tests/test_share_generic.py b/manila/tests/test_share_generic.py index 399ccbedc8..72da2e058d 100644 --- a/manila/tests/test_share_generic.py +++ b/manila/tests/test_share_generic.py @@ -793,39 +793,53 @@ class NFSHelperTestCase(test.TestCase): self._execute = mock.Mock(return_value=('', '')) self._helper = generic.NFSHelper(self._execute, self._ssh_exec, self.fake_conf) + ip = '10.254.0.3' + self.server = fake_compute.FakeServer( + ip=ip, public_address=ip, instance_id='fake_instance_id') def test_create_export(self): - fake_server = fake_compute.FakeServer(public_address='10.254.0.3') - ret = self._helper.create_export(fake_server, 'volume-00001') - expected_location = ':'.join([fake_server['public_address'], + ret = self._helper.create_export(self.server, 'fake_share') + expected_location = ':'.join([self.server['public_address'], os.path.join(CONF.share_mount_path, - 'volume-00001')]) + 'fake_share')]) self.assertEqual(ret, expected_location) def test_allow_access(self): - fake_server = fake_compute.FakeServer(ip='10.254.0.3') - self._helper.allow_access(fake_server, 'volume-00001', + self.stubs.Set(self._helper, '_sync_nfs_temp_and_perm_files', + mock.Mock()) + self._helper.allow_access(self.server, 'fake_share', 'ip', '10.0.0.2') - local_path = os.path.join(CONF.share_mount_path, 'volume-00001') + local_path = os.path.join(CONF.share_mount_path, 'fake_share') self._ssh_exec.assert_has_calls([ - mock.call(fake_server, ['sudo', 'exportfs']), - mock.call(fake_server, ['sudo', 'exportfs', '-o', + mock.call(self.server, ['sudo', 'exportfs']), + mock.call(self.server, ['sudo', 'exportfs', '-o', 'rw,no_subtree_check', ':'.join(['10.0.0.2', 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, 'fake_server', 'share0', - 'fake', 'fakerule') + self.assertRaises( + exception.InvalidShareAccess, + self._helper.allow_access, + self.server, 'fake_share', 'fake', 'fakerule', + ) def test_deny_access(self): - fake_server = fake_compute.FakeServer(ip='10.254.0.3') - local_path = os.path.join(CONF.share_mount_path, 'volume-00001') - self._helper.deny_access(fake_server, 'volume-00001', 'ip', '10.0.0.2') + self.stubs.Set(self._helper, '_sync_nfs_temp_and_perm_files', + mock.Mock()) + local_path = os.path.join(CONF.share_mount_path, 'fake_share') + self._helper.deny_access(self.server, 'fake_share', 'ip', '10.0.0.2') export_string = ':'.join(['10.0.0.2', local_path]) expected_exec = ['sudo', 'exportfs', '-u', export_string] - self._ssh_exec.assert_called_once_with(fake_server, expected_exec) + self._ssh_exec.assert_called_once_with(self.server, expected_exec) + self._helper._sync_nfs_temp_and_perm_files.assert_called_once_with( + self.server) + + def test_sync_nfs_temp_and_perm_files(self): + self._helper._sync_nfs_temp_and_perm_files(self.server) + self._helper._ssh_exec.assert_called_once_with(self.server, mock.ANY) class CIFSHelperTestCase(test.TestCase):