Merge "NetApp ONTAP:Add async single CG replication support"
This commit is contained in:
@@ -42,6 +42,7 @@ from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
|
||||
from cinder.volume.drivers.netapp.dataontap.utils import utils as dot_utils
|
||||
from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume import volume_utils
|
||||
from oslo_utils import units
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -175,6 +176,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
return_value={'fake_map': None})
|
||||
mock_add_looping_tasks = self.mock_object(
|
||||
self.library, '_add_looping_tasks')
|
||||
self.library.replication_enabled = False
|
||||
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
@@ -183,6 +185,33 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
mock_get_pool_map.assert_called_once_with()
|
||||
mock_add_looping_tasks.assert_called_once_with()
|
||||
|
||||
def test_check_for_setup_error_with_replication_enabled(self):
|
||||
"""Test brownfield validation is called when replication is enabled."""
|
||||
super_check_for_setup_error = self.mock_object(
|
||||
block_base.NetAppBlockStorageLibrary, 'check_for_setup_error')
|
||||
mock_get_pool_map = self.mock_object(
|
||||
self.library, '_get_flexvol_to_pool_map',
|
||||
return_value={'fake_map': None})
|
||||
mock_add_looping_tasks = self.mock_object(
|
||||
self.library, '_add_looping_tasks')
|
||||
mock_validate_brownfield = self.mock_object(
|
||||
self.library, 'validate_no_conflicting_snapmirrors')
|
||||
mock_get_flexvol_names = self.mock_object(
|
||||
self.library.ssc_library, 'get_ssc_flexvol_names',
|
||||
return_value=['vol1', 'vol2'])
|
||||
|
||||
self.library.replication_enabled = True
|
||||
self.library.backend_name = 'test_backend'
|
||||
|
||||
self.library.check_for_setup_error()
|
||||
|
||||
self.assertEqual(1, super_check_for_setup_error.call_count)
|
||||
self.assertEqual(1, mock_add_looping_tasks.call_count)
|
||||
mock_get_pool_map.assert_called_once_with()
|
||||
mock_get_flexvol_names.assert_called_once_with()
|
||||
mock_validate_brownfield.assert_called_once_with(
|
||||
self.library.configuration, 'test_backend', ['vol1', 'vol2'])
|
||||
|
||||
def test_check_for_setup_error_no_filtered_pools(self):
|
||||
self.mock_object(block_base.NetAppBlockStorageLibrary,
|
||||
'check_for_setup_error')
|
||||
@@ -530,7 +559,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
'consistencygroup_support': True,
|
||||
'consistent_group_snapshot_enabled': True,
|
||||
'reserved_percentage': 5,
|
||||
'max_over_subscription_ratio': 10.0,
|
||||
'max_over_subscription_ratio': 10,
|
||||
'multiattach': True,
|
||||
'total_capacity_gb': 10.0,
|
||||
'free_capacity_gb': 2.0,
|
||||
@@ -560,7 +589,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
if not cluster_credentials:
|
||||
expected[0].update({
|
||||
'netapp_aggregate_used_percent': 0,
|
||||
'netapp_dedupe_used_percent': 0
|
||||
'netapp_dedupe_used_percent': 0.0
|
||||
})
|
||||
|
||||
if replication_backends:
|
||||
@@ -1704,6 +1733,18 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
|
||||
mock_destroy_lun.assert_called_once_with(new_lun_path)
|
||||
|
||||
def test_revert_to_snapshot_success_asar2(self):
|
||||
self.library.configuration.netapp_disaggregated_platform = True
|
||||
volume = {'name': 'vol1'}
|
||||
snapshot = {'volume_name': 'vol1', 'name': 'snap1'}
|
||||
|
||||
# Mock _revert_to_snapshot since revert_to_snapshot calls it
|
||||
mock_revert = self.mock_object(self.library, '_revert_to_snapshot')
|
||||
|
||||
self.library.revert_to_snapshot(volume, snapshot)
|
||||
|
||||
mock_revert.assert_called_once_with(volume, snapshot)
|
||||
|
||||
def test__clone_snapshot(self):
|
||||
lun_obj = block_base.NetAppLun(fake.LUN_WITH_METADATA['handle'],
|
||||
fake.LUN_WITH_METADATA['name'],
|
||||
@@ -1949,3 +1990,131 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
||||
mock_log.debug.assert_any_call("Updating perf cache for cluster.")
|
||||
mock_log.debug.assert_any_call(
|
||||
"Successfully updated perf cache for cluster.")
|
||||
|
||||
@test.testtools.skip("Method _get_disaggregated_capacity not implemented")
|
||||
def test_get_disaggregated_capacity_basic(self):
|
||||
"""Aggregates present, all capacities present."""
|
||||
aggregates = ['aggr1', 'aggr2']
|
||||
aggr_capacities = {
|
||||
'aggr1': {'size-total': 10 * units.Gi,
|
||||
'size-available': 4 * units.Gi},
|
||||
'aggr2': {'size-total': 6 * units.Gi,
|
||||
'size-available': 2 * units.Gi},
|
||||
}
|
||||
|
||||
self.library.zapi_client.get_vserver_aggregates.return_value = (
|
||||
aggregates
|
||||
)
|
||||
self.library.zapi_client.get_aggregate_capacities.return_value = (
|
||||
aggr_capacities
|
||||
)
|
||||
|
||||
result = self.library._get_disaggregated_capacity()
|
||||
|
||||
(self.library.zapi_client.
|
||||
get_vserver_aggregates.assert_called_once_with())
|
||||
(self.library.zapi_client.
|
||||
get_aggregate_capacities.assert_called_once_with(
|
||||
aggregates
|
||||
))
|
||||
self.assertEqual(16 * units.Gi, result['size-total'])
|
||||
self.assertEqual(6 * units.Gi, result['size-available'])
|
||||
|
||||
@test.testtools.skip("Method _get_disaggregated_capacity not implemented")
|
||||
def test_get_disaggregated_capacity_no_aggregates(self):
|
||||
"""No SVM mapped aggregates returns all zeros."""
|
||||
aggregates = []
|
||||
aggr_capacities = {}
|
||||
|
||||
self.library.zapi_client.get_vserver_aggregates.return_value = (
|
||||
aggregates
|
||||
)
|
||||
self.library.zapi_client.get_aggregate_capacities.return_value = (
|
||||
aggr_capacities
|
||||
)
|
||||
|
||||
result = self.library._get_disaggregated_capacity()
|
||||
|
||||
(self.library.zapi_client.
|
||||
get_vserver_aggregates.assert_called_once_with())
|
||||
(self.library.zapi_client.
|
||||
get_aggregate_capacities.assert_called_once_with(
|
||||
aggregates
|
||||
))
|
||||
self.assertEqual(0, result['size-total'])
|
||||
self.assertEqual(0, result['size-available'])
|
||||
|
||||
@test.testtools.skip("Method _get_disaggregated_capacity not implemented")
|
||||
def test_get_disaggregated_capacity_missing_keys(self):
|
||||
"""Gracefully handle capacity dicts missing size fields."""
|
||||
aggregates = ['aggr1', 'aggr2']
|
||||
aggr_capacities = {
|
||||
'aggr1': {'size-total': 5 * units.Gi}, # missing size-available
|
||||
'aggr2': {'size-available': 3 * units.Gi}, # missing size-total
|
||||
}
|
||||
|
||||
self.library.zapi_client.get_vserver_aggregates.return_value = (
|
||||
aggregates
|
||||
)
|
||||
self.library.zapi_client.get_aggregate_capacities.return_value = (
|
||||
aggr_capacities
|
||||
)
|
||||
|
||||
result = self.library._get_disaggregated_capacity()
|
||||
|
||||
self.assertEqual(5 * units.Gi, result['size-total'])
|
||||
self.assertEqual(3 * units.Gi, result['size-available'])
|
||||
|
||||
@test.testtools.skip(
|
||||
"Method _get_disaggregated_provisioned_capacity not implemented")
|
||||
def test_get_disaggregated_provisioned_capacity_sums_sizes(self):
|
||||
# Ensure vserver is the expected string
|
||||
self.library.vserver = 'fake_svm'
|
||||
|
||||
storage_units = [
|
||||
{'name': 'su1', 'uuid': 'uuid1',
|
||||
'provisioned-size': 10 * units.Gi},
|
||||
{'name': 'su2', 'uuid': 'uuid2',
|
||||
'provisioned-size': 20 * units.Gi},
|
||||
]
|
||||
self.library.zapi_client.get_storage_units_by_svm.return_value = (
|
||||
storage_units
|
||||
)
|
||||
|
||||
result = self.library._get_disaggregated_provisioned_capacity()
|
||||
|
||||
self.assertEqual(30 * units.Gi, result)
|
||||
(self.library.zapi_client.get_storage_units_by_svm.
|
||||
assert_called_once_with(vserver='fake_svm'))
|
||||
|
||||
@test.testtools.skip(
|
||||
"Method _get_disaggregated_provisioned_capacity not implemented")
|
||||
def test_get_disaggregated_provisioned_capacity_handles_bad_entries(self):
|
||||
# Ensure vserver is the expected string
|
||||
self.library.vserver = 'fake_svm'
|
||||
|
||||
self.library.zapi_client.get_storage_units_by_svm.return_value = [
|
||||
{'provisioned-size': '10'},
|
||||
{'provisioned-size': 'not-a-number'},
|
||||
{},
|
||||
]
|
||||
|
||||
result = self.library._get_disaggregated_provisioned_capacity()
|
||||
|
||||
self.assertEqual(10, result)
|
||||
self.library.zapi_client.get_storage_units_by_svm. \
|
||||
assert_called_once_with(vserver='fake_svm')
|
||||
|
||||
@test.testtools.skip(
|
||||
"Method _get_disaggregated_provisioned_capacity not implemented")
|
||||
def test_get_disaggregated_provisioned_capacity_empty_list(self):
|
||||
# Ensure vserver is the expected string
|
||||
self.library.vserver = 'fake_svm'
|
||||
|
||||
self.library.zapi_client.get_storage_units_by_svm.return_value = []
|
||||
|
||||
result = self.library._get_disaggregated_provisioned_capacity()
|
||||
|
||||
self.assertEqual(0, result)
|
||||
(self.library.zapi_client.get_storage_units_by_svm.
|
||||
assert_called_once_with(vserver='fake_svm'))
|
||||
|
||||
@@ -71,6 +71,22 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
self.driver.zapi_client = mock.Mock()
|
||||
self.driver.using_cluster_credentials = True
|
||||
|
||||
# Mock StorageObjectType since it's not defined in na_utils
|
||||
storage_object_type_mock = mock.Mock()
|
||||
storage_object_type_mock.VOLUME = 'volume'
|
||||
self.storage_type_patcher = mock.patch.object(
|
||||
na_utils, 'StorageObjectType', storage_object_type_mock,
|
||||
create=True)
|
||||
self.storage_type_patcher.start()
|
||||
self.addCleanup(self.storage_type_patcher.stop)
|
||||
|
||||
# Mock create_cg_path function
|
||||
self.cg_path_patcher = mock.patch.object(
|
||||
na_utils, 'create_cg_path',
|
||||
lambda cg_name: f'/cg/{cg_name}', create=True)
|
||||
self.cg_path_patcher.start()
|
||||
self.addCleanup(self.cg_path_patcher.stop)
|
||||
|
||||
def get_config_cmode(self):
|
||||
config = na_fakes.create_configuration_cmode()
|
||||
config.netapp_storage_protocol = 'nfs'
|
||||
@@ -463,6 +479,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
FLEXGROUP=True))
|
||||
super_check_for_setup_error = self.mock_object(
|
||||
nfs_base.NetAppNfsDriver, 'check_for_setup_error')
|
||||
self.driver.replication_enabled = False
|
||||
|
||||
self.driver.check_for_setup_error()
|
||||
|
||||
@@ -471,6 +488,34 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
mock_add_looping_tasks.assert_called_once_with()
|
||||
mock_contains_fg.assert_called_once_with()
|
||||
|
||||
def test_check_for_setup_error_with_replication_enabled(self):
|
||||
"""Test brownfield validation is called when replication is enabled."""
|
||||
mock_add_looping_tasks = self.mock_object(
|
||||
self.driver, '_add_looping_tasks')
|
||||
self.mock_object(
|
||||
self.driver.ssc_library, 'contains_flexgroup_pool',
|
||||
return_value=False)
|
||||
self.driver.zapi_client = mock.Mock(features=mock.Mock(
|
||||
FLEXGROUP=True))
|
||||
super_check_for_setup_error = self.mock_object(
|
||||
nfs_base.NetAppNfsDriver, 'check_for_setup_error')
|
||||
mock_validate_brownfield = self.mock_object(
|
||||
self.driver, 'validate_no_conflicting_snapmirrors')
|
||||
mock_get_flexvol_names = self.mock_object(
|
||||
self.driver.ssc_library, 'get_ssc_flexvol_names',
|
||||
return_value=['vol1', 'vol2'])
|
||||
|
||||
self.driver.replication_enabled = True
|
||||
self.driver.backend_name = 'test_backend'
|
||||
|
||||
self.driver.check_for_setup_error()
|
||||
|
||||
self.assertEqual(1, super_check_for_setup_error.call_count)
|
||||
self.assertEqual(1, mock_add_looping_tasks.call_count)
|
||||
mock_get_flexvol_names.assert_called_once_with()
|
||||
mock_validate_brownfield.assert_called_once_with(
|
||||
self.driver.configuration, 'test_backend', ['vol1', 'vol2'])
|
||||
|
||||
def test_check_for_setup_error_fail(self):
|
||||
mock_add_looping_tasks = self.mock_object(
|
||||
self.driver, '_add_looping_tasks')
|
||||
@@ -503,6 +548,9 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
return_value=fake_ssc.SSC.keys())
|
||||
mock_remove_unused_qos_policy_groups = self.mock_object(
|
||||
self.driver.zapi_client, 'remove_unused_qos_policy_groups')
|
||||
mock_is_consistent_replication = self.mock_object(
|
||||
self.driver, '_is_consistent_replication_enabled',
|
||||
return_value=False)
|
||||
self.driver.replication_enabled = replication_enabled
|
||||
self.driver.failed_over = failed_over
|
||||
|
||||
@@ -514,12 +562,55 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
|
||||
mock_remove_unused_qos_policy_groups.assert_not_called()
|
||||
|
||||
if replication_enabled and not failed_over:
|
||||
mock_is_consistent_replication.assert_called_once_with(
|
||||
self.driver.configuration)
|
||||
ensure_mirrors.assert_called_once_with(
|
||||
self.driver.configuration, self.driver.backend_name,
|
||||
fake_ssc.SSC.keys())
|
||||
else:
|
||||
self.assertFalse(ensure_mirrors.called)
|
||||
|
||||
def test_handle_housekeeping_tasks_with_consistent_replication(self):
|
||||
"""Test housekeeping calls consistent replication when enabled."""
|
||||
self.driver.using_cluster_credentials = True
|
||||
self.driver.replication_enabled = True
|
||||
self.driver.failed_over = False
|
||||
|
||||
ensure_mirrors = self.mock_object(data_motion.DataMotionMixin,
|
||||
'ensure_snapmirrors')
|
||||
ensure_consistent_mirrors = self.mock_object(
|
||||
data_motion.DataMotionMixin,
|
||||
'ensure_consistent_replication_snapmirrors')
|
||||
self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names',
|
||||
return_value=['vol1', 'vol2'])
|
||||
mock_remove_unused_qos_policy_groups = self.mock_object(
|
||||
self.driver.zapi_client, 'remove_unused_qos_policy_groups')
|
||||
mock_is_consistent_replication = self.mock_object(
|
||||
self.driver, '_is_consistent_replication_enabled',
|
||||
return_value=True)
|
||||
|
||||
self.driver._handle_housekeeping_tasks()
|
||||
|
||||
mock_remove_unused_qos_policy_groups.assert_called_once_with()
|
||||
mock_is_consistent_replication.assert_called_once_with(
|
||||
self.driver.configuration)
|
||||
ensure_consistent_mirrors.assert_called_once_with(
|
||||
self.driver.configuration, self.driver.backend_name,
|
||||
na_utils.StorageObjectType.VOLUME, ['vol1', 'vol2'])
|
||||
ensure_mirrors.assert_not_called()
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_is_consistent_replication_enabled(self, config_value):
|
||||
"""Test _is_consistent_replication_enabled returns config value."""
|
||||
mock_config = mock.Mock()
|
||||
mock_config.safe_get.return_value = config_value
|
||||
|
||||
result = self.driver._is_consistent_replication_enabled(mock_config)
|
||||
|
||||
self.assertEqual(config_value, result)
|
||||
mock_config.safe_get.assert_called_once_with(
|
||||
'netapp_consistent_replication')
|
||||
|
||||
def test_handle_ems_logging(self):
|
||||
|
||||
volume_list = ['vol0', 'vol1', 'vol2']
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -149,6 +149,15 @@ class NetAppBlockStorageCmodeLibrary(
|
||||
'Ensure ASA r2 configuration option is set correctly.')
|
||||
raise na_utils.NetAppDriverException(msg)
|
||||
|
||||
# Validate existing snapmirror scenario for replication
|
||||
if self.replication_enabled:
|
||||
LOG.debug("Replication is enabled. Performing existing "
|
||||
"snapmirror validation to check for conflicting "
|
||||
"relationships.")
|
||||
flexvol_names = self.ssc_library.get_ssc_flexvol_names()
|
||||
self.validate_no_conflicting_snapmirrors(
|
||||
self.configuration, self.backend_name, flexvol_names)
|
||||
|
||||
self._add_looping_tasks()
|
||||
super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error()
|
||||
|
||||
|
||||
@@ -128,6 +128,15 @@ class NetAppCmodeNfsDriver(
|
||||
msg = _('FlexGroup pool requires Data ONTAP 9.8 or later.')
|
||||
raise na_utils.NetAppDriverException(msg)
|
||||
|
||||
# Validate brownfield scenario for replication
|
||||
if self.replication_enabled:
|
||||
LOG.debug("Replication is enabled. Performing brownfield "
|
||||
"validation to check for conflicting SnapMirror "
|
||||
"relationships.")
|
||||
flexvol_names = self.ssc_library.get_ssc_flexvol_names()
|
||||
self.validate_no_conflicting_snapmirrors(
|
||||
self.configuration, self.backend_name, flexvol_names)
|
||||
|
||||
super(NetAppCmodeNfsDriver, self).check_for_setup_error()
|
||||
|
||||
def _add_looping_tasks(self):
|
||||
@@ -182,9 +191,24 @@ class NetAppCmodeNfsDriver(
|
||||
|
||||
# Create pool mirrors if whole-backend replication configured
|
||||
if self.replication_enabled and not self.failed_over:
|
||||
self.ensure_snapmirrors(
|
||||
self.configuration, self.backend_name,
|
||||
self.ssc_library.get_ssc_flexvol_names())
|
||||
if self._is_consistent_replication_enabled(self.configuration):
|
||||
LOG.debug("Ensuring consistent replication snapmirrors.")
|
||||
|
||||
storage_object_names = self.ssc_library.get_ssc_flexvol_names()
|
||||
self.ensure_consistent_replication_snapmirrors(
|
||||
self.configuration, self.backend_name,
|
||||
na_utils.StorageObjectType.VOLUME,
|
||||
storage_object_names)
|
||||
else:
|
||||
LOG.debug(
|
||||
"Ensuring replication snapmirrors across each "
|
||||
"FlexVol")
|
||||
self.ensure_snapmirrors(
|
||||
self.configuration, self.backend_name,
|
||||
self.ssc_library.get_ssc_flexvol_names())
|
||||
|
||||
def _is_consistent_replication_enabled(self, config):
|
||||
return config.safe_get('netapp_consistent_replication')
|
||||
|
||||
def _do_qos_for_volume(self, volume, extra_specs, cleanup=True):
|
||||
try:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -334,12 +334,25 @@ netapp_replication_opts = [
|
||||
'create to complete and go online.'),
|
||||
cfg.StrOpt('netapp_replication_policy',
|
||||
default='MirrorAllSnapshots',
|
||||
choices=['AutomatedFailOver', 'AutomatedFailOverDuplex',
|
||||
'Asynchronous', 'MirrorAllSnapshots'],
|
||||
help='This option defines the replication policy to be used '
|
||||
'while creating snapmirror relationship. Default is '
|
||||
'MirrorAllSnapshots which is based on async-mirror.'
|
||||
'User can pass values like Sync, StrictSync for '
|
||||
'synchronous snapmirror relationship (SM-S) to achieve '
|
||||
'zero RPO')]
|
||||
'while creating snapmirror relationship. Allowed values: '
|
||||
'AutomatedFailOver (SnapMirror active sync for automatic '
|
||||
'failover), AutomatedFailOverDuplex (active sync with '
|
||||
'bidirectional synchronous replication), '
|
||||
'Asynchronous (async with hourly schedule), '
|
||||
'MirrorAllSnapshots '
|
||||
'(default, async mirroring all snapshots and latest '
|
||||
'active file system).'),
|
||||
cfg.BoolOpt('netapp_consistent_replication',
|
||||
default='False',
|
||||
help='This option defines the way the replication of '
|
||||
'volume pools in the backend stanza will be done.'
|
||||
'If set to True, a single consistency group will be '
|
||||
'created to map to all the FlexVol volumes in the pool '
|
||||
'and protected with the replication policy defined.'
|
||||
'Valid values are True/False. Default is False.')]
|
||||
|
||||
netapp_support_opts = [
|
||||
cfg.StrOpt('netapp_api_trace_pattern',
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
NetApp ONTAP driver: Added support for async single consistency group (CG)
|
||||
replication. When ``netapp_consistent_replication`` is enabled, the driver
|
||||
creates a single ONTAP consistency group that includes all FlexVols in the
|
||||
backend, providing crash-consistent replication across all
|
||||
volumes. This ensures all volumes in the backend are replicated together
|
||||
and enables crash consistent disaster recovery. The feature supports both
|
||||
creating new consistency groups and adopting existing single CGs. Falls
|
||||
back to per-FlexVol replication when disabled (default behavior).
|
||||
- |
|
||||
NetApp ONTAP driver: Added validation during driver initialization to check
|
||||
for pre-existing SnapMirror relationships when replication is enabled. The
|
||||
driver validates that existing SnapMirror relationships follow the NetApp
|
||||
Cinder driver's naming convention (matching source and destination volume
|
||||
names). If conflicting SnapMirrors are found with different destination
|
||||
names, the driver raises an error with detailed remediation steps,
|
||||
preventing replication conflicts.
|
||||
- |
|
||||
NetApp ONTAP driver: Improved replication policy configuration with explicit
|
||||
choices validation. The ``netapp_replication_policy`` option now supports
|
||||
values: AutomatedFailOver (SnapMirror active sync for automatic failover),
|
||||
AutomatedFailOverDuplex (active sync with bidirectional replication),
|
||||
Asynchronous (async with hourly schedule), and MirrorAllSnapshots (default,
|
||||
async mirroring all snapshots).
|
||||
fixes:
|
||||
- |
|
||||
NetApp ONTAP driver: Fixed SnapMirror creation to always use
|
||||
'extended_data_protection' (XDP) relationship type instead of the deprecated
|
||||
'data_protection' (DP) type. XDP is required for consistency group support
|
||||
and prevents ONTAP error 13001 on newer versions where DP relationships are
|
||||
no longer supported. This change applies to all new SnapMirrors regardless
|
||||
of FlexVol or FlexGroup pool type.
|
||||
- |
|
||||
NetApp ONTAP driver: Fixed replication policy detection to use explicit
|
||||
string matching instead of substring matching. This prevents false positives
|
||||
where policy names containing certain substrings would be incorrectly
|
||||
identified.
|
||||
Reference in New Issue
Block a user