diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py index 06f5d1a1f..b975cbf1e 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py @@ -16,6 +16,9 @@ Mock unit tests for the NetApp cmode nfs storage driver """ +import hashlib +import uuid + import ddt import mock from os_brick.remotefs import remotefs as remotefs_brick @@ -809,23 +812,35 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): def test__copy_from_img_service_qcow2_copyoffload_workflow_success( self, mock_exists, mock_qemu_img_info, mock_cvrt_image): drv = self.driver + cinder_mount_point_base = '/opt/stack/data/cinder/mnt/' + # To get the cinder mount point directory, we use: + mount_dir = hashlib.md5( + '203.0.113.122:/cinder-flexvol1'.encode('utf-8')).hexdigest() + cinder_mount_point = cinder_mount_point_base + mount_dir + destination_copied_file = ( + '/cinder-flexvol1/a155308c-0290-497b-b278-4cdd01de0253' + ) volume = {'id': 'vol_id', 'name': 'name', 'size': 1} image_id = 'image_id' context = object() image_service = mock.Mock() - image_service.get_location.return_value = ('nfs://ip1/openstack/img', - None) + image_service.get_location.return_value = ( + 'nfs://203.0.113.122/glance-flexvol1', None) image_service.show.return_value = {'size': 1, 'disk_format': 'qcow2'} - drv._check_get_nfs_path_segs =\ - mock.Mock(return_value=('ip1', '/openstack')) + drv._check_get_nfs_path_segs = ( + mock.Mock(return_value=('203.0.113.122', '/openstack')) + ) - drv._get_ip_verify_on_cluster = mock.Mock(return_value='ip1') - drv._get_host_ip = mock.Mock(return_value='ip2') - drv._get_export_path = mock.Mock(return_value='/exp_path') + drv._get_ip_verify_on_cluster = mock.Mock(return_value='203.0.113.122') + drv._get_host_ip = mock.Mock(return_value='203.0.113.122') + drv._get_export_path = mock.Mock( + return_value='/cinder-flexvol1') drv._get_provider_location = mock.Mock(return_value='share') drv._execute = mock.Mock() - drv._get_mount_point_for_share = mock.Mock(return_value='mnt_point') + drv._execute_as_root = False + drv._get_mount_point_for_share = mock.Mock( + return_value=cinder_mount_point) img_inf = mock.Mock() img_inf.file_format = 'raw' mock_qemu_img_info.return_value = img_inf @@ -835,17 +850,33 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): drv._delete_file_at_path = mock.Mock() drv._clone_file_dst_exists = mock.Mock() drv._post_clone_image = mock.Mock() + self.mock_object(uuid, 'uuid4', mock.Mock( + return_value='a155308c-0290-497b-b278-4cdd01de0253')) retval = drv._copy_from_img_service( context, volume, image_service, image_id) self.assertIsNone(retval) - drv._get_ip_verify_on_cluster.assert_any_call('ip1') + drv._get_ip_verify_on_cluster.assert_any_call('203.0.113.122') drv._get_export_path.assert_called_with('vol_id') drv._check_share_can_hold_size.assert_called_with('share', 1) drv._post_clone_image.assert_called_with(volume) self.assertEqual(1, mock_cvrt_image.call_count) - self.assertEqual(1, drv._execute.call_count) + + # _execute must be called once for copy-offload and again to touch + # the top directory to refresh cache + drv._execute.assert_has_calls( + [ + mock.call( + 'copyoffload_tool_path', '203.0.113.122', + '203.0.113.122', '/openstack/glance-flexvol1', + destination_copied_file, run_as_root=False, + check_exit_code=0 + ), + mock.call('touch', cinder_mount_point, run_as_root=False) + ] + ) + self.assertEqual(2, drv._execute.call_count) self.assertEqual(2, drv._delete_file_at_path.call_count) self.assertEqual(1, drv._clone_file_dst_exists.call_count) diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py index 694289e67..2cf9a0457 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py @@ -645,6 +645,15 @@ class NetAppNfsDriver(driver.ManageableVD, else: return False + @utils.trace_method + def _touch_path_to_refresh(self, path): + try: + # Touching parent directory forces NFS client to flush its cache. + self._execute('touch', path, run_as_root=self._execute_as_root) + except processutils.ProcessExecutionError: + LOG.exception(_LE("Failed to touch path %s."), path) + + @utils.trace_method def _discover_file_till_timeout(self, path, timeout=75): """Checks if file size at path is equal to size.""" # Sometimes nfs takes time to discover file @@ -656,6 +665,9 @@ class NetAppNfsDriver(driver.ManageableVD, # retries to ensure that this cache has refreshed. retry_seconds = timeout sleep_interval = 2 + base_path = os.path.dirname(path) + self._touch_path_to_refresh(base_path) + while True: if os.path.exists(path): return True @@ -666,6 +678,7 @@ class NetAppNfsDriver(driver.ManageableVD, else: time.sleep(sleep_interval) retry_seconds -= sleep_interval + self._touch_path_to_refresh(base_path) def _is_cloneable_share(self, image_location): """Finds if the image at location is cloneable.""" diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters index f7810c46f..1aa971183 100644 --- a/etc/cinder/rootwrap.d/volume.filters +++ b/etc/cinder/rootwrap.d/volume.filters @@ -119,8 +119,9 @@ rm: CommandFilter, rm, root # cinder/volume/drivers/remotefs.py mkdir: CommandFilter, mkdir, root -# cinder/volume/drivers/netapp/nfs.py: +# cinder/volume/drivers/netapp/dataontap/nfs_base.py: netapp_nfs_find: RegExpFilter, find, root, find, ^[/]*([^/\0]+(/+)?)*$, -maxdepth, \d+, -name, img-cache.*, -amin, \+\d+ +netapp_nfs_touch: CommandFilter, touch, root # cinder/volume/drivers/glusterfs.py chgrp: CommandFilter, chgrp, root