NetApp NFS: Clone image using copy file operation
The copy offload tool is no longer available available, so this patch adds an alternative approach using file copy operations to the scenarios where the tool was required. During the clone image operation, if the source volume is not available in the same pool or cache, the copy file operation is called to perform the action. The copy offload tool will keeping working and it will be removed in the next release (Antelope). There patch [1] deprecates the tool. [1] https://review.opendev.org/c/openstack/cinder/+/847733 Implements: blueprint netapp-nfs-copy-offload-image Change-Id: I4e2163661b913800a5a1179a5899ec1384f8c167
This commit is contained in:
parent
f97f7ff514
commit
147637b63f
@ -924,7 +924,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
drv._construct_image_nfs_url = mock.Mock(return_value=["nfs://1"])
|
||||
drv._check_get_nfs_path_segs = mock.Mock(
|
||||
return_value=("test:test", "dr"))
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value="192.128.1.1")
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value=("192.128.1.1",
|
||||
"vserver"))
|
||||
drv._get_mount_point_for_share = mock.Mock(return_value='mnt_point')
|
||||
drv._check_share_can_hold_size = mock.Mock()
|
||||
# Raise error as if the copyoffload file can not be found
|
||||
@ -937,9 +938,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
||||
drv._discover_file_till_timeout.assert_not_called()
|
||||
|
||||
@mock.patch.object(image_utils, 'qemu_img_info')
|
||||
@ddt.data(True, False)
|
||||
def test_copy_from_img_service_raw_copyoffload_workflow_success(
|
||||
self, mock_qemu_img_info):
|
||||
self, use_tool):
|
||||
drv = self.driver
|
||||
volume = {'id': 'vol_id', 'name': 'name', 'size': 1,
|
||||
'host': 'openstack@nfscmode#ip1:/mnt_point'}
|
||||
@ -952,16 +953,18 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
||||
drv._check_get_nfs_path_segs =\
|
||||
mock.Mock(return_value=('ip1', '/openstack'))
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value='ip1')
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value=('ip1',
|
||||
'vserver'))
|
||||
drv._get_host_ip = mock.Mock(return_value='ip2')
|
||||
drv._get_export_path = mock.Mock(return_value='/exp_path')
|
||||
drv._get_provider_location = mock.Mock(return_value='share')
|
||||
drv._execute = mock.Mock()
|
||||
drv._copy_file = mock.Mock()
|
||||
drv._get_mount_point_for_share = mock.Mock(return_value='mnt_point')
|
||||
drv._discover_file_till_timeout = mock.Mock(return_value=True)
|
||||
img_inf = mock.Mock()
|
||||
img_inf.file_format = 'raw'
|
||||
mock_qemu_img_info.return_value = img_inf
|
||||
image_utils.qemu_img_info.return_value = img_inf
|
||||
drv._check_share_can_hold_size = mock.Mock()
|
||||
drv._move_nfs_file = mock.Mock(return_value=True)
|
||||
drv._delete_file_at_path = mock.Mock()
|
||||
@ -969,13 +972,19 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
drv._post_clone_image = mock.Mock()
|
||||
|
||||
retval = drv._copy_from_img_service(
|
||||
context, volume, image_service, image_id)
|
||||
context, volume, image_service, image_id,
|
||||
use_copyoffload_tool=use_tool)
|
||||
|
||||
self.assertTrue(retval)
|
||||
drv._get_ip_verify_on_cluster.assert_any_call('ip1')
|
||||
drv._check_share_can_hold_size.assert_called_with(
|
||||
'ip1:/mnt_point', 1)
|
||||
self.assertEqual(1, drv._execute.call_count)
|
||||
if use_tool:
|
||||
self.assertEqual(1, drv._execute.call_count)
|
||||
self.assertEqual(0, drv._copy_file.call_count)
|
||||
else:
|
||||
self.assertEqual(1, drv._copy_file.call_count)
|
||||
self.assertEqual(0, drv._execute.call_count)
|
||||
|
||||
@mock.patch.object(image_utils, 'convert_image')
|
||||
@mock.patch.object(image_utils, 'qemu_img_info')
|
||||
@ -1007,7 +1016,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
mock.Mock(return_value=('203.0.113.122', '/openstack'))
|
||||
)
|
||||
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value='203.0.113.122')
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(
|
||||
return_value=('203.0.113.122', 'vserver'))
|
||||
drv._execute = mock.Mock()
|
||||
drv._execute_as_root = False
|
||||
drv._get_mount_point_for_share = mock.Mock(
|
||||
@ -1054,7 +1064,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
'host': 'openstack@nfscmode#192.128.1.1:/exp_path'}
|
||||
image_id = 'image_id'
|
||||
cache_result = [('ip1:/openstack', 'img-cache-imgid')]
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value='ip1')
|
||||
drv._get_ip_verify_on_cluster = mock.Mock(return_value=('ip1',
|
||||
'vserver'))
|
||||
drv._execute = mock.Mock()
|
||||
drv._register_image_in_cache = mock.Mock()
|
||||
drv._post_clone_image = mock.Mock()
|
||||
@ -1241,25 +1252,29 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
@ddt.unpack
|
||||
def test_get_source_ip_and_path(self, share, ip):
|
||||
self.driver._get_ip_verify_on_cluster = mock.Mock(
|
||||
return_value=ip)
|
||||
return_value=(ip, fake.VSERVER_NAME))
|
||||
|
||||
src_ip, src_path = self.driver._get_source_ip_and_path(
|
||||
share, fake.IMAGE_FILE_ID)
|
||||
src_ip, src_vserver, src_share, src_path = (
|
||||
self.driver._get_source_ip_and_path(
|
||||
share, fake.IMAGE_FILE_ID))
|
||||
|
||||
self.assertEqual(ip, src_ip)
|
||||
self.assertEqual(fake.VSERVER_NAME, src_vserver)
|
||||
self.assertEqual(fake.EXPORT_PATH, src_share)
|
||||
assert_path = fake.EXPORT_PATH + '/' + fake.IMAGE_FILE_ID
|
||||
self.assertEqual(assert_path, src_path)
|
||||
self.driver._get_ip_verify_on_cluster.assert_called_once_with(ip)
|
||||
|
||||
def test_get_destination_ip_and_path(self):
|
||||
self.driver._get_ip_verify_on_cluster = mock.Mock(
|
||||
return_value=fake.SHARE_IP)
|
||||
return_value=(fake.SHARE_IP, fake.VSERVER_NAME))
|
||||
mock_extract_host = self.mock_object(volume_utils, 'extract_host')
|
||||
mock_extract_host.return_value = fake.NFS_SHARE
|
||||
|
||||
dest_ip, dest_path = self.driver._get_destination_ip_and_path(
|
||||
fake.VOLUME)
|
||||
dest_ip, dest_vserver, dest_path = (
|
||||
self.driver._get_destination_ip_and_path(fake.VOLUME))
|
||||
|
||||
self.assertEqual(fake.VSERVER_NAME, dest_vserver)
|
||||
self.assertEqual(fake.SHARE_IP, dest_ip)
|
||||
assert_path = fake.EXPORT_PATH + '/' + fake.LUN_NAME
|
||||
self.assertEqual(assert_path, dest_path)
|
||||
@ -1309,7 +1324,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
self.driver._is_flexgroup.assert_called_once_with(host=volume['host'])
|
||||
mock_clone_file.assert_called_once_with()
|
||||
|
||||
def test_clone_image_copyoffload_from_img_service(self):
|
||||
@ddt.data(True, False)
|
||||
def test_clone_image_from_img_service(self, use_tool):
|
||||
drv = self.driver
|
||||
context = object()
|
||||
volume = {'id': 'vol_id', 'name': 'name',
|
||||
@ -1330,6 +1346,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
drv._copy_from_img_service = mock.Mock(return_value=True)
|
||||
drv._is_flexgroup = mock.Mock(return_value=False)
|
||||
drv._is_flexgroup_clone_file_supported = mock.Mock(return_value=True)
|
||||
if not use_tool:
|
||||
drv.configuration.netapp_copyoffload_tool_path = None
|
||||
|
||||
retval = drv.clone_image(
|
||||
context, volume, image_location, image_meta, image_service)
|
||||
@ -1338,7 +1356,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
{'provider_location': '192.128.1.1:/mnt_point',
|
||||
'bootable': True}, True))
|
||||
drv._copy_from_img_service.assert_called_once_with(
|
||||
context, volume, image_service, image_id)
|
||||
context, volume, image_service, image_id,
|
||||
use_copyoffload_tool=use_tool)
|
||||
|
||||
def test_clone_image_copyoffload_failure(self):
|
||||
mock_log = self.mock_object(nfs_cmode, 'LOG')
|
||||
@ -1365,35 +1384,57 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
self.assertEqual(retval, ({'bootable': False,
|
||||
'provider_location': None}, False))
|
||||
drv._copy_from_img_service.assert_called_once_with(
|
||||
context, volume, image_service, image_id)
|
||||
context, volume, image_service, image_id,
|
||||
use_copyoffload_tool=True)
|
||||
mock_log.info.assert_not_called()
|
||||
|
||||
def test_copy_from_remote_cache(self):
|
||||
@ddt.data(True, False)
|
||||
def test_copy_from_remote_cache(self, use_tool):
|
||||
source_ip = '192.0.1.1'
|
||||
source_path = '/openstack/img-cache-imgid'
|
||||
source_vserver = 'fake_vserver'
|
||||
source_share = 'vol_fake'
|
||||
cache_copy = ('192.0.1.1:/openstack', fake.IMAGE_FILE_ID)
|
||||
dest_vserver = 'fake_dest_vserver'
|
||||
dest_path = fake.EXPORT_PATH + '/' + fake.VOLUME['name']
|
||||
self.driver._execute = mock.Mock()
|
||||
self.driver._copy_file = mock.Mock()
|
||||
self.driver._get_source_ip_and_path = mock.Mock(
|
||||
return_value=(source_ip, source_path))
|
||||
return_value=(
|
||||
source_ip, source_vserver, source_share, source_path))
|
||||
self.driver._get_destination_ip_and_path = mock.Mock(
|
||||
return_value=(fake.SHARE_IP, dest_path))
|
||||
return_value=(fake.SHARE_IP, dest_vserver, dest_path))
|
||||
self.driver._register_image_in_cache = mock.Mock()
|
||||
ctxt = mock.Mock()
|
||||
vol_fields = {'id': fake.VOLUME_ID, 'name': fake.VOLUME_NAME}
|
||||
fake_vol = fake_volume.fake_volume_obj(ctxt, **vol_fields)
|
||||
|
||||
self.driver._copy_from_remote_cache(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_copy)
|
||||
fake_vol, fake.IMAGE_FILE_ID, cache_copy,
|
||||
use_copyoffload_tool=use_tool)
|
||||
|
||||
self.driver._execute.assert_called_once_with(
|
||||
'copyoffload_tool_path', source_ip, fake.SHARE_IP,
|
||||
source_path, dest_path, run_as_root=False, check_exit_code=0)
|
||||
if use_tool:
|
||||
self.driver._execute.assert_called_once_with(
|
||||
'copyoffload_tool_path', source_ip, fake.SHARE_IP,
|
||||
source_path, dest_path, run_as_root=False, check_exit_code=0)
|
||||
self.driver._copy_file.assert_not_called()
|
||||
else:
|
||||
dest_share_path = dest_path.rsplit("/", 1)[0]
|
||||
self.driver._copy_file.assert_called_once_with(
|
||||
fake.IMAGE_FILE_ID, fake.IMAGE_FILE_ID, source_share,
|
||||
source_vserver, dest_share_path, dest_vserver,
|
||||
dest_backend_name=self.driver.backend_name,
|
||||
dest_file_name=fake_vol.name)
|
||||
self.driver._execute.assert_not_called()
|
||||
self.driver._get_source_ip_and_path.assert_called_once_with(
|
||||
cache_copy[0], fake.IMAGE_FILE_ID)
|
||||
self.driver._get_destination_ip_and_path.assert_called_once_with(
|
||||
fake.VOLUME)
|
||||
fake_vol)
|
||||
self.driver._register_image_in_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID)
|
||||
fake_vol, fake.IMAGE_FILE_ID)
|
||||
|
||||
def test_copy_from_cache_workflow_remote_location(self):
|
||||
@ddt.data(True, False)
|
||||
def test_copy_from_cache_workflow_remote_location(self, use_tool):
|
||||
cache_result = [('ip1:/openstack', fake.IMAGE_FILE_ID),
|
||||
('ip2:/openstack', fake.IMAGE_FILE_ID),
|
||||
('ip3:/openstack', fake.IMAGE_FILE_ID)]
|
||||
@ -1401,29 +1442,20 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
cache_result[0], False])
|
||||
self.driver._copy_from_remote_cache = mock.Mock()
|
||||
self.driver._post_clone_image = mock.Mock()
|
||||
if not use_tool:
|
||||
self.driver.configuration.netapp_copyoffload_tool_path = None
|
||||
|
||||
copied = self.driver._copy_from_cache(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result)
|
||||
|
||||
self.assertTrue(copied)
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0])
|
||||
|
||||
def test_copy_from_cache_workflow_remote_location_no_copyoffload(self):
|
||||
cache_result = [('ip1:/openstack', fake.IMAGE_FILE_ID),
|
||||
('ip2:/openstack', fake.IMAGE_FILE_ID),
|
||||
('ip3:/openstack', fake.IMAGE_FILE_ID)]
|
||||
self.driver._find_image_location = mock.Mock(return_value=[
|
||||
cache_result[0], False])
|
||||
self.driver._copy_from_remote_cache = mock.Mock()
|
||||
self.driver._post_clone_image = mock.Mock()
|
||||
self.driver.configuration.netapp_copyoffload_tool_path = None
|
||||
|
||||
copied = self.driver._copy_from_cache(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result)
|
||||
|
||||
self.assertFalse(copied)
|
||||
self.driver._copy_from_remote_cache.assert_not_called()
|
||||
if use_tool:
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0])
|
||||
else:
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0],
|
||||
use_copyoffload_tool=False)
|
||||
|
||||
def test_copy_from_cache_workflow_local_location(self):
|
||||
local_share = '/share'
|
||||
@ -1456,20 +1488,28 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
|
||||
self.assertFalse(copied)
|
||||
|
||||
def test_copy_from_cache_workflow_exception(self):
|
||||
@ddt.data(True, False)
|
||||
def test_copy_from_cache_workflow_exception(self, use_tool):
|
||||
cache_result = [('ip1:/openstack', fake.IMAGE_FILE_ID)]
|
||||
self.driver._find_image_location = mock.Mock(return_value=[
|
||||
cache_result[0], False])
|
||||
self.driver._copy_from_remote_cache = mock.Mock(
|
||||
side_effect=Exception)
|
||||
self.driver._post_clone_image = mock.Mock()
|
||||
if not use_tool:
|
||||
self.driver.configuration.netapp_copyoffload_tool_path = None
|
||||
|
||||
copied = self.driver._copy_from_cache(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result)
|
||||
|
||||
self.assertFalse(copied)
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0])
|
||||
if use_tool:
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0])
|
||||
else:
|
||||
self.driver._copy_from_remote_cache.assert_called_once_with(
|
||||
fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0],
|
||||
use_copyoffload_tool=False)
|
||||
self.assertFalse(self.driver._post_clone_image.called)
|
||||
|
||||
@ddt.data({'secondary_id': 'dev0', 'configured_targets': ['dev1']},
|
||||
@ -1902,21 +1942,15 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
return_value=fake_job_status)
|
||||
mock_cancel_file_copy = self.mock_object(
|
||||
self.driver, '_cancel_file_copy')
|
||||
ctxt = mock.Mock()
|
||||
vol_fields = {
|
||||
'id': fake.VOLUME_ID,
|
||||
'name': fake.VOLUME_NAME,
|
||||
'status': fields.VolumeStatus.AVAILABLE
|
||||
}
|
||||
fake_vol = fake_volume.fake_volume_obj(ctxt, **vol_fields)
|
||||
|
||||
result = self.driver._copy_file(
|
||||
fake_vol, fake.POOL_NAME, fake.VSERVER_NAME, fake.DEST_POOL_NAME,
|
||||
fake.DEST_VSERVER_NAME, dest_file_name=fake.VOLUME_NAME,
|
||||
fake.VOLUME_NAME, fake.VOLUME_ID, fake.POOL_NAME,
|
||||
fake.VSERVER_NAME, fake.DEST_POOL_NAME, fake.DEST_VSERVER_NAME,
|
||||
dest_file_name=fake.VOLUME_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME, cancel_on_error=True)
|
||||
|
||||
mock_start_file_copy.assert_called_with(
|
||||
fake_vol.name, fake.DEST_POOL_NAME,
|
||||
fake.VOLUME_NAME, fake.DEST_POOL_NAME,
|
||||
src_ontap_volume=fake.POOL_NAME,
|
||||
dest_file_name=fake.VOLUME_NAME)
|
||||
mock_get_file_copy_status.assert_called_with(fake.JOB_UUID)
|
||||
@ -1943,29 +1977,23 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
return_value=fake_job_status)
|
||||
mock_cancel_file_copy = self.mock_object(
|
||||
self.driver, '_cancel_file_copy')
|
||||
ctxt = mock.Mock()
|
||||
vol_fields = {
|
||||
'id': fake.VOLUME_ID,
|
||||
'name': fake.VOLUME_NAME,
|
||||
'status': fields.VolumeStatus.AVAILABLE
|
||||
}
|
||||
fake_vol = fake_volume.fake_volume_obj(ctxt, **vol_fields)
|
||||
|
||||
self.assertRaises(copy_exception,
|
||||
self.driver._copy_file,
|
||||
fake_vol, fake.POOL_NAME, fake.VSERVER_NAME,
|
||||
fake.DEST_POOL_NAME, fake.DEST_VSERVER_NAME,
|
||||
fake.VOLUME_NAME, fake.VOLUME_ID, fake.POOL_NAME,
|
||||
fake.VSERVER_NAME, fake.DEST_POOL_NAME,
|
||||
fake.DEST_VSERVER_NAME,
|
||||
dest_file_name=fake.VOLUME_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
|
||||
mock_start_file_copy.assert_called_with(
|
||||
fake_vol.name, fake.DEST_POOL_NAME,
|
||||
fake.VOLUME_NAME, fake.DEST_POOL_NAME,
|
||||
src_ontap_volume=fake.POOL_NAME,
|
||||
dest_file_name=fake.VOLUME_NAME)
|
||||
mock_get_file_copy_status.assert_called_with(fake.JOB_UUID)
|
||||
mock_cancel_file_copy.assert_called_once_with(
|
||||
fake.JOB_UUID, fake_vol, fake.DEST_POOL_NAME,
|
||||
fake.JOB_UUID, fake.VOLUME_NAME, fake.DEST_POOL_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME)
|
||||
|
||||
def test_migrate_volume_to_vserver(self):
|
||||
@ -1984,9 +2012,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.DEST_VSERVER_NAME, fake.DEST_BACKEND_NAME)
|
||||
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.DEST_VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:],
|
||||
fake.DEST_VSERVER_NAME, dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_create_vserver_peer.assert_called_once_with(
|
||||
fake.VSERVER_NAME, fake.BACKEND_NAME, fake.DEST_VSERVER_NAME,
|
||||
@ -2051,9 +2079,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.VSERVER_NAME, fake.BACKEND_NAME, fake.DEST_VSERVER_NAME,
|
||||
['file_copy'])
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.DEST_VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:],
|
||||
fake.DEST_VSERVER_NAME, dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_finish_volume_migration.assert_not_called()
|
||||
|
||||
@ -2084,9 +2112,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.VSERVER_NAME, fake.BACKEND_NAME, fake.DEST_VSERVER_NAME,
|
||||
['file_copy'])
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.DEST_VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:],
|
||||
fake.DEST_VSERVER_NAME, dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_finish_volume_migration.assert_not_called()
|
||||
|
||||
@ -2105,8 +2133,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.DEST_BACKEND_NAME)
|
||||
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_finish_volume_migration.assert_called_once_with(
|
||||
@ -2133,8 +2161,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.DEST_BACKEND_NAME)
|
||||
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_finish_volume_migration.assert_not_called()
|
||||
@ -2159,8 +2187,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
fake.DEST_BACKEND_NAME)
|
||||
|
||||
mock_copy_file.assert_called_once_with(
|
||||
fake_vol, fake.EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
fake_vol.name, fake_vol.id, fake.EXPORT_PATH[1:],
|
||||
fake.VSERVER_NAME, fake.DEST_EXPORT_PATH[1:], fake.VSERVER_NAME,
|
||||
dest_backend_name=fake.DEST_BACKEND_NAME,
|
||||
cancel_on_error=True)
|
||||
mock_finish_volume_migration.assert_not_called()
|
||||
|
@ -648,7 +648,7 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
raise NotImplementedError()
|
||||
|
||||
def _copy_from_img_service(self, context, volume, image_service,
|
||||
image_id):
|
||||
image_id, use_copyoffload_tool=False):
|
||||
raise NotImplementedError()
|
||||
|
||||
def clone_image(self, context, volume,
|
||||
@ -683,10 +683,15 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
cloned = self._direct_nfs_clone(volume, image_location,
|
||||
image_id)
|
||||
|
||||
# Try to use the copy offload tool
|
||||
if not cloned and col_path and major == 1 and minor >= 20:
|
||||
cloned = self._copy_from_img_service(context, volume,
|
||||
image_service, image_id)
|
||||
# Try to use the deprecated copy offload tool or file copy.
|
||||
if not cloned:
|
||||
# We will use copy offload tool if the copy offload tool
|
||||
# path exists and the version is greater than or equal to
|
||||
# 1.20
|
||||
use_tool = bool(col_path) and (major == 1 and minor >= 20)
|
||||
cloned = self._copy_from_img_service(
|
||||
context, volume, image_service, image_id,
|
||||
use_copyoffload_tool=use_tool)
|
||||
|
||||
if cloned:
|
||||
self._do_qos_for_volume(volume, extra_specs)
|
||||
|
@ -620,7 +620,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
if not vserver:
|
||||
raise exception.NotFound(_("Unable to locate an SVM that is "
|
||||
"managing the IP address '%s'") % ip)
|
||||
return ip
|
||||
return ip, vserver
|
||||
|
||||
def _copy_from_cache(self, volume, image_id, cache_result):
|
||||
"""Try copying image file_name from cached file_name."""
|
||||
@ -642,6 +642,11 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
LOG.debug("Trying copy from cache using copy offload.")
|
||||
self._copy_from_remote_cache(volume, image_id, cache_copy)
|
||||
copied = True
|
||||
elif cache_copy:
|
||||
LOG.debug("Trying copy from cache using file copy.")
|
||||
self._copy_from_remote_cache(volume, image_id, cache_copy,
|
||||
use_copyoffload_tool=False)
|
||||
copied = True
|
||||
except Exception:
|
||||
LOG.exception('Error in workflow copy from cache.')
|
||||
return copied
|
||||
@ -670,41 +675,55 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
cache_copy = res
|
||||
return cache_copy, found_local_copy
|
||||
|
||||
def _copy_from_remote_cache(self, volume, image_id, cache_copy):
|
||||
def _copy_from_remote_cache(self, volume, image_id, cache_copy,
|
||||
use_copyoffload_tool=True):
|
||||
"""Copies the remote cached image to the provided volume.
|
||||
|
||||
Executes the copy offload binary which copies the cached image to
|
||||
the destination path of the provided volume. Also registers the new
|
||||
copy of the image as a cached image.
|
||||
Executes either the copy offload binary or the file copy operation,
|
||||
copying the cached image to the destination path of the provided
|
||||
volume. Also registers the new copy of the image as a cached image.
|
||||
"""
|
||||
|
||||
(nfs_share, file_name) = cache_copy
|
||||
col_path = self.configuration.netapp_copyoffload_tool_path
|
||||
src_ip, src_path = self._get_source_ip_and_path(nfs_share, file_name)
|
||||
dest_ip, dest_path = self._get_destination_ip_and_path(volume)
|
||||
(src_ip, src_vserver, src_share_path, src_path) = (
|
||||
self._get_source_ip_and_path(nfs_share, file_name))
|
||||
(dest_ip, dest_vserver, dest_path) = (
|
||||
self._get_destination_ip_and_path(volume))
|
||||
|
||||
# Always run copy offload as regular user, it's sufficient
|
||||
# and rootwrap doesn't allow copy offload to run as root anyways.
|
||||
self._execute(col_path, src_ip, dest_ip, src_path, dest_path,
|
||||
run_as_root=False, check_exit_code=0)
|
||||
# NOTE(felipe_rodrigues): the copy offload tool code will be removed in
|
||||
# the Antelope release.
|
||||
col_path = self.configuration.netapp_copyoffload_tool_path
|
||||
if use_copyoffload_tool and col_path:
|
||||
# Always run copy offload as regular user, it's sufficient
|
||||
# and rootwrap doesn't allow copy offload to run as root anyways.
|
||||
self._execute(col_path, src_ip, dest_ip, src_path, dest_path,
|
||||
run_as_root=False, check_exit_code=0)
|
||||
LOG.debug("Copied image from cache to volume %s using "
|
||||
"copy offload.", volume['id'])
|
||||
else:
|
||||
dest_share_path = dest_path.rsplit("/", 1)[0]
|
||||
self._copy_file(file_name, file_name, src_share_path, src_vserver,
|
||||
dest_share_path, dest_vserver,
|
||||
dest_backend_name=self.backend_name,
|
||||
dest_file_name=volume.name)
|
||||
LOG.debug("Copied image from cache to volume %s using "
|
||||
"file copy operation.", volume['id'])
|
||||
self._register_image_in_cache(volume, image_id)
|
||||
LOG.debug("Copied image from cache to volume %s using copy offload.",
|
||||
volume['id'])
|
||||
|
||||
def _get_source_ip_and_path(self, nfs_share, file_name):
|
||||
host, share_path = na_utils.get_export_host_junction_path(nfs_share)
|
||||
src_ip = self._get_ip_verify_on_cluster(host)
|
||||
(src_ip, src_vserver) = self._get_ip_verify_on_cluster(host)
|
||||
src_path = os.path.join(share_path, file_name)
|
||||
|
||||
return src_ip, src_path
|
||||
return src_ip, src_vserver, share_path, src_path
|
||||
|
||||
def _get_destination_ip_and_path(self, volume):
|
||||
share = volume_utils.extract_host(volume['host'], level='pool')
|
||||
share_ip, share_path = na_utils.get_export_host_junction_path(share)
|
||||
dest_ip = self._get_ip_verify_on_cluster(share_ip)
|
||||
(dest_ip, vserver) = self._get_ip_verify_on_cluster(share_ip)
|
||||
dest_path = os.path.join(share_path, volume['name'])
|
||||
|
||||
return dest_ip, dest_path
|
||||
return dest_ip, vserver, dest_path
|
||||
|
||||
def _clone_file_dst_exists(self, share, src_name, dst_name,
|
||||
dest_exists=False):
|
||||
@ -714,38 +733,39 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
dest_exists=dest_exists)
|
||||
|
||||
def _copy_from_img_service(self, context, volume, image_service,
|
||||
image_id):
|
||||
"""Copies from the image service using copy offload."""
|
||||
image_id, use_copyoffload_tool=True):
|
||||
"""Copies from the image service using copy offload or file copy."""
|
||||
|
||||
LOG.debug("Trying copy from image service using copy offload.")
|
||||
image_loc = image_service.get_location(context, image_id)
|
||||
locations = self._construct_image_nfs_url(image_loc)
|
||||
src_ip = None
|
||||
src_vserver = None
|
||||
src_volume = None
|
||||
selected_loc = None
|
||||
cloned = False
|
||||
|
||||
# this will match the first location that has a valid IP on cluster
|
||||
for location in locations:
|
||||
conn, dr = self._check_get_nfs_path_segs(location)
|
||||
conn, src_volume = self._check_get_nfs_path_segs(location)
|
||||
if conn:
|
||||
try:
|
||||
src_ip = self._get_ip_verify_on_cluster(conn.split(':')[0])
|
||||
(src_ip, src_vserver) = (
|
||||
self._get_ip_verify_on_cluster(conn.split(':')[0]))
|
||||
selected_loc = location
|
||||
break
|
||||
except exception.NotFound:
|
||||
pass
|
||||
if src_ip is None:
|
||||
if src_ip is None or src_vserver is None:
|
||||
raise exception.NotFound(_("Source host details not found."))
|
||||
(__, ___, img_file) = selected_loc.rpartition('/')
|
||||
src_path = os.path.join(dr, img_file)
|
||||
|
||||
dst_ip, vol_path = self._get_destination_ip_and_path(volume)
|
||||
share_path = vol_path.rsplit("/", 1)[0]
|
||||
dst_share = dst_ip + ':' + share_path
|
||||
(dst_ip, dest_vserver, vol_path) = (
|
||||
self._get_destination_ip_and_path(volume))
|
||||
dest_share_path = vol_path.rsplit("/", 1)[0]
|
||||
dst_share = dst_ip + ':' + dest_share_path
|
||||
|
||||
# tmp file is required to deal with img formats
|
||||
tmp_img_file = six.text_type(uuid.uuid4())
|
||||
col_path = self.configuration.netapp_copyoffload_tool_path
|
||||
img_info = image_service.show(context, image_id)
|
||||
self._check_share_can_hold_size(dst_share, img_info['size'])
|
||||
run_as_root = self._execute_as_root
|
||||
@ -754,14 +774,27 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
dst_img_local = os.path.join(dst_dir, tmp_img_file)
|
||||
|
||||
try:
|
||||
dst_img_serv_path = os.path.join(
|
||||
share_path, tmp_img_file)
|
||||
# Always run copy offload as regular user, it's sufficient
|
||||
# and rootwrap doesn't allow copy offload to run as root
|
||||
# anyways.
|
||||
self._execute(col_path, src_ip, dst_ip, src_path,
|
||||
dst_img_serv_path, run_as_root=False,
|
||||
check_exit_code=0)
|
||||
# NOTE(felipe_rodrigues): the copy offload tool code will be
|
||||
# removed in the AA release.
|
||||
col_path = self.configuration.netapp_copyoffload_tool_path
|
||||
if col_path and use_copyoffload_tool:
|
||||
LOG.debug("Trying copy from image service using copy offload.")
|
||||
dst_img_serv_path = os.path.join(dest_share_path, tmp_img_file)
|
||||
src_path = os.path.join(src_volume, img_file)
|
||||
# Always run copy offload as regular user, it's sufficient
|
||||
# and rootwrap doesn't allow copy offload to run as root
|
||||
# anyways.
|
||||
self._execute(col_path, src_ip, dst_ip, src_path,
|
||||
dst_img_serv_path, run_as_root=False,
|
||||
check_exit_code=0)
|
||||
else:
|
||||
LOG.debug("Trying copy from image service using file copy.")
|
||||
src_volume = ''.join(src_volume.split("/", 1))
|
||||
dest_share_path = ''.join(dest_share_path.split("/", 1))
|
||||
self._copy_file(img_file, img_file, src_volume, src_vserver,
|
||||
dest_share_path, dest_vserver,
|
||||
dest_backend_name=self.backend_name,
|
||||
dest_file_name=tmp_img_file)
|
||||
|
||||
self._discover_file_till_timeout(dst_img_local, timeout=120)
|
||||
LOG.debug('Copied image %(img)s to tmp file %(tmp)s.',
|
||||
@ -1072,7 +1105,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
"""Check whether storage can perform clone file for FlexGroup"""
|
||||
return self.zapi_client.features.FLEXGROUP_CLONE_FILE
|
||||
|
||||
def _cancel_file_copy(self, job_uuid, volume, dest_pool,
|
||||
def _cancel_file_copy(self, job_uuid, file_name, dest_pool,
|
||||
dest_backend_name=None):
|
||||
"""Cancel an on-going file copy operation."""
|
||||
try:
|
||||
@ -1082,7 +1115,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
self.zapi_client.destroy_file_copy(job_uuid)
|
||||
except na_utils.NetAppDriverException:
|
||||
dest_client = dot_utils.get_client_for_backend(dest_backend_name)
|
||||
file_path = '%s/%s' % (dest_pool, volume.name)
|
||||
file_path = '%s/%s' % (dest_pool, file_name)
|
||||
try:
|
||||
dest_client.delete_file(file_path)
|
||||
except Exception:
|
||||
@ -1091,17 +1124,17 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
'pool %s and delete it manually to avoid unused '
|
||||
'resources.', file_path, dest_pool)
|
||||
|
||||
def _copy_file(self, volume, src_ontap_volume, src_vserver,
|
||||
def _copy_file(self, file_name, volume_id, src_ontap_volume, src_vserver,
|
||||
dest_ontap_volume, dest_vserver, dest_file_name=None,
|
||||
dest_backend_name=None, cancel_on_error=False):
|
||||
"""Copies file from an ONTAP volume to another."""
|
||||
job_uuid = self.zapi_client.start_file_copy(
|
||||
volume.name, dest_ontap_volume, src_ontap_volume=src_ontap_volume,
|
||||
file_name, dest_ontap_volume, src_ontap_volume=src_ontap_volume,
|
||||
dest_file_name=dest_file_name)
|
||||
LOG.debug('Start copying file %(vol)s from '
|
||||
LOG.debug('Start copying file %(file)s from '
|
||||
'%(src_vserver)s:%(src_ontap_vol)s to '
|
||||
'%(dest_vserver)s:%(dest_ontap_vol)s. Job UUID is %(job)s.',
|
||||
{'vol': volume.name, 'src_vserver': src_vserver,
|
||||
{'file': file_name, 'src_vserver': src_vserver,
|
||||
'src_ontap_vol': src_ontap_volume,
|
||||
'dest_vserver': dest_vserver,
|
||||
'dest_ontap_vol': dest_ontap_volume,
|
||||
@ -1116,11 +1149,11 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
"corresponding Job UUID % doesn't "
|
||||
"exist."))
|
||||
raise na_utils.NetAppDriverException(
|
||||
status_error_msg % (volume.id, job_uuid))
|
||||
status_error_msg % (file_name, job_uuid))
|
||||
elif copy_status['job-status'] == 'destroyed':
|
||||
status_error_msg = (_('Error copying file %s. %s.'))
|
||||
raise na_utils.NetAppDriverException(
|
||||
status_error_msg % (volume.id,
|
||||
status_error_msg % (file_name,
|
||||
copy_status['last-failure-reason']))
|
||||
elif copy_status['job-status'] == 'complete':
|
||||
raise loopingcall.LoopingCallDone()
|
||||
@ -1137,7 +1170,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
if cancel_on_error:
|
||||
try:
|
||||
self._cancel_file_copy(
|
||||
job_uuid, volume, dest_ontap_volume,
|
||||
job_uuid, file_name, dest_ontap_volume,
|
||||
dest_backend_name=dest_backend_name)
|
||||
except na_utils.NetAppDriverException as ex:
|
||||
LOG.error("Failed to cancel file copy operation. %s",
|
||||
@ -1146,7 +1179,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
ctxt.reraise = False
|
||||
msg = (_('Timeout waiting volume %s to complete '
|
||||
'migration.'))
|
||||
raise na_utils.NetAppDriverTimeout(msg % volume.id)
|
||||
raise na_utils.NetAppDriverTimeout(msg % volume_id)
|
||||
|
||||
def _finish_volume_migration(self, src_volume, dest_pool):
|
||||
"""Finish volume migration to another ONTAP volume."""
|
||||
@ -1171,8 +1204,8 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
[vserver_peer_application])
|
||||
src_ontap_volume_name = src_pool.split(':/')[1]
|
||||
dest_ontap_volume_name = dest_pool.split(':/')[1]
|
||||
self._copy_file(volume, src_ontap_volume_name, src_vserver,
|
||||
dest_ontap_volume_name, dest_vserver,
|
||||
self._copy_file(volume.name, volume.id, src_ontap_volume_name,
|
||||
src_vserver, dest_ontap_volume_name, dest_vserver,
|
||||
dest_backend_name=dest_backend_name,
|
||||
cancel_on_error=True)
|
||||
updates = self._finish_volume_migration(volume, dest_pool)
|
||||
@ -1193,7 +1226,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
|
||||
'vserver': vserver})
|
||||
src_ontap_volume_name = src_pool.split(':/')[1]
|
||||
dest_ontap_volume_name = dest_pool.split(':/')[1]
|
||||
self._copy_file(volume, src_ontap_volume_name, vserver,
|
||||
self._copy_file(volume.name, volume.id, src_ontap_volume_name, vserver,
|
||||
dest_ontap_volume_name, vserver,
|
||||
dest_backend_name=dest_backend_name,
|
||||
cancel_on_error=True)
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
NetApp NFS driver: add an alternative approach to perform the efficient clone
|
||||
image when the Glance source store and Cinder destination pool are not in the
|
||||
same FlexVol, but they are in the same Cluster. Previously, the driver required
|
||||
the copy offload tool for doing it efficiently, which is no longer available.
|
||||
Now, the operators can maintain their efficient clone image by relying on the
|
||||
storage file copy operation.
|
Loading…
Reference in New Issue
Block a user