diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py index c28117c66de..c9e3444b21d 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py @@ -450,6 +450,7 @@ class PowerMaxData(object): rep_extra_specs['sync_retries'] = 200 rep_extra_specs['rdf_group_label'] = rdf_group_name_1 rep_extra_specs['rdf_group_no'] = rdf_group_no_1 + rep_extra_specs[utils.DISABLE_PROTECTED_SNAP] = False rep_extra_specs2 = deepcopy(rep_extra_specs) rep_extra_specs2[utils.PORTGROUPNAME] = port_group_name_f rep_extra_specs3 = deepcopy(rep_extra_specs) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py index 2feeab1c913..8eb31b3952b 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py @@ -105,6 +105,7 @@ class PowerMaxReplicationTest(test.TestCase): extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f extra_specs[utils.IS_RE] = True extra_specs[utils.FORCE_VOL_EDIT] = True + extra_specs[utils.DISABLE_PROTECTED_SNAP] = False rep_config = self.data.rep_config_sync rep_config = deepcopy(self.data.rep_config_sync) rep_config[utils.RDF_CONS_EXEMPT] = False diff --git a/cinder/volume/drivers/dell_emc/powermax/common.py b/cinder/volume/drivers/dell_emc/powermax/common.py index eae078f2737..3fdaaf67b72 100644 --- a/cinder/volume/drivers/dell_emc/powermax/common.py +++ b/cinder/volume/drivers/dell_emc/powermax/common.py @@ -1574,7 +1574,8 @@ class PowerMaxCommon(object): if rep_config.get(utils.METROBIAS): extra_specs[utils.METROBIAS] = ( rep_config[utils.METROBIAS]) - + extra_specs[utils.DISABLE_PROTECTED_SNAP] =\ + self.utils.is_protected_snap_disabled(extra_specs) return extra_specs, qos_specs def _get_replicated_volume_backend_id(self, volume): @@ -2074,14 +2075,23 @@ class PowerMaxCommon(object): return volume_name array = extra_specs[utils.ARRAY] - if self.utils.is_replication_enabled(extra_specs): - self._validate_rdfg_status(array, extra_specs) + dps = self.utils.is_protected_snap_disabled(extra_specs) + # If a volume is not replicated and has the + # powermax:disable_protected_snap set to True, + # then clean up the volume without replication cleanup. + if dps and volume.replication_status is None: + self.masking.remove_and_reset_members( + array, volume, device_id, volume_name, extra_specs, False) + self._cleanup_device_retry(array, device_id, extra_specs) + else: + if self.utils.is_replication_enabled(extra_specs): + self._validate_rdfg_status(array, extra_specs) - self._cleanup_device_retry(array, device_id, extra_specs) + self._cleanup_device_retry(array, device_id, extra_specs) - # Remove from any storage groups and cleanup replication - self._remove_vol_and_cleanup_replication( - array, device_id, volume_name, extra_specs, volume) + # Remove from any storage groups and cleanup replication + self._remove_vol_and_cleanup_replication( + array, device_id, volume_name, extra_specs, volume) self._delete_from_srp( array, device_id, volume_name, extra_specs) return volume_name @@ -2898,6 +2908,10 @@ class PowerMaxCommon(object): create_snap, copy_mode, rep_extra_specs = False, False, dict() volume_dict = self.rest.get_volume(array, source_device_id) replication_enabled = self.utils.is_replication_enabled(extra_specs) + if self.utils.is_protected_snap_disabled(extra_specs): + extra_specs.pop(utils.IS_RE, None) + replication_enabled = False + if replication_enabled: copy_mode = True __, rep_extra_specs, __, __ = ( @@ -5778,6 +5792,9 @@ class PowerMaxCommon(object): bias = True if rep_config.get(utils.METROBIAS) else False rep_extra_specs[utils.METROBIAS] = bias + rep_extra_specs[utils.DISABLE_PROTECTED_SNAP] =\ + self.utils.is_protected_snap_disabled(extra_specs) + # If disable compression is set, check if target array is all flash do_disable_compression = self.utils.is_compression_disabled( extra_specs) @@ -7800,3 +7817,20 @@ class PowerMaxCommon(object): 'dev_ident': dev_id_from_identifier}) self.rest.rename_volume( array, dev_id_from_identifier, None) + + @staticmethod + def get_vendor_properties(self): + """Retrieves the vendor properties for the powermax driver. + + :param self: The object instance. + :return: A tuple containing the properties dictionary and the + driver name. + """ + properties = {} + self._set_property( + properties, + utils.DISABLE_PROTECTED_SNAP, + "Disable protected snap", + _("Prevent protected snap being created on SRDF device."), + "boolean") + return properties, "powermax" diff --git a/cinder/volume/drivers/dell_emc/powermax/fc.py b/cinder/volume/drivers/dell_emc/powermax/fc.py index 03d944c9d11..7123521cdce 100644 --- a/cinder/volume/drivers/dell_emc/powermax/fc.py +++ b/cinder/volume/drivers/dell_emc/powermax/fc.py @@ -134,9 +134,10 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver): 4.4.1 - Report trim/discard support 4.5.0 - Add PowerMax v4 support 4.5.1 - Add active/active compliance + 4.5.2 - Add 'disable_protected_snap' option """ - VERSION = "4.5.1" + VERSION = "4.5.2" SUPPORTS_ACTIVE_ACTIVE = True # ThirdPartySystems wiki @@ -166,6 +167,9 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver): def check_for_setup_error(self): pass + def _init_vendor_properties(self): + return self.common.get_vendor_properties(self) + def create_volume(self, volume): """Creates a PowerMax/VMAX volume. diff --git a/cinder/volume/drivers/dell_emc/powermax/iscsi.py b/cinder/volume/drivers/dell_emc/powermax/iscsi.py index c453f9da445..95d64a73b46 100644 --- a/cinder/volume/drivers/dell_emc/powermax/iscsi.py +++ b/cinder/volume/drivers/dell_emc/powermax/iscsi.py @@ -139,9 +139,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): 4.4.1 - Report trim/discard support 4.5.0 - Add PowerMax v4 support 4.5.1 - Add active/active compliance + 4.5.2 - Add 'disable_protected_snap' option """ - VERSION = "4.5.1" + VERSION = "4.5.2" SUPPORTS_ACTIVE_ACTIVE = True # ThirdPartySystems wiki @@ -171,6 +172,9 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): def check_for_setup_error(self): pass + def _init_vendor_properties(self): + return self.common.get_vendor_properties(self) + def create_volume(self, volume): """Creates a PowerMax/VMAX volume. @@ -255,7 +259,6 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): :param context: the context :param volume_id: the volume id """ - pass def initialize_connection(self, volume, connector): """Initializes the connection and returns connection info. @@ -461,15 +464,15 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): data['driver_version'] = self.VERSION self._stats = data - def manage_existing(self, volume, external_ref): + def manage_existing(self, volume, existing_ref): """Manages an existing PowerMax/VMAX Volume (import to Cinder). Renames the Volume to match the expected name for the volume. Also need to consider things like QoS, Emulation, account/tenant. """ - return self.common.manage_existing(volume, external_ref) + return self.common.manage_existing(volume, existing_ref) - def manage_existing_get_size(self, volume, external_ref): + def manage_existing_get_size(self, volume, existing_ref): """Return size of an existing PowerMax/VMAX volume to manage_existing. :param self: reference to class @@ -477,7 +480,7 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): :param external_ref: reference to the existing volume :returns: size of the volume in GB """ - return self.common.manage_existing_get_size(volume, external_ref) + return self.common.manage_existing_get_size(volume, existing_ref) def unmanage(self, volume): """Export PowerMax/VMAX volume from Cinder. @@ -550,10 +553,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): return self.common.get_manageable_snapshots(marker, limit, offset, sort_keys, sort_dirs) - def retype(self, ctxt, volume, new_type, diff, host): + def retype(self, context, volume, new_type, diff, host): """Migrate volume to another host using retype. - :param ctxt: context + :param context: context :param volume: the volume object including the volume_type_id :param new_type: the new volume type. :param diff: difference between old and new volume types. diff --git a/cinder/volume/drivers/dell_emc/powermax/utils.py b/cinder/volume/drivers/dell_emc/powermax/utils.py index eb3f1f95dc4..bc4f4e20aaa 100644 --- a/cinder/volume/drivers/dell_emc/powermax/utils.py +++ b/cinder/volume/drivers/dell_emc/powermax/utils.py @@ -140,6 +140,7 @@ REST_API_CONNECT_TIMEOUT = 'rest_api_connect_timeout' REST_API_READ_TIMEOUT = 'rest_api_read_timeout' REST_API_CONNECT_TIMEOUT_KEY = 'RestAPIConnectTimeout' REST_API_READ_TIMEOUT_KEY = 'RestAPIReadTimeout' +DISABLE_PROTECTED_SNAP = 'powermax:disable_protected_snap' # Array Models, Service Levels & Workloads VMAX_HYBRID_MODELS = ['VMAX100K', 'VMAX200K', 'VMAX400K'] @@ -530,6 +531,15 @@ class PowerMaxUtils(object): host_list = host.split('+') return host_list[-1] + def is_protected_snap_disabled(self, extra_specs): + """Check is the disable_protected_snap flag set. + + :param extra_specs: extra specifications :returns: boolean + """ + if extra_specs.get(DISABLE_PROTECTED_SNAP, False) in IS_TRUE: + return True + return False + def is_compression_disabled(self, extra_specs): """Check is compression is to be disabled. diff --git a/releasenotes/notes/bp-powermax-protected-snap-82eb6731553356d9.yaml b/releasenotes/notes/bp-powermax-protected-snap-82eb6731553356d9.yaml new file mode 100644 index 00000000000..a04c476bab9 --- /dev/null +++ b/releasenotes/notes/bp-powermax-protected-snap-82eb6731553356d9.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Dell EMC PowerMax driver: Added SRDF ``powermax:disable_protected_snap`` + volume-type extra-spec property for the purpose of avoiding + overconsumption on both source and target storage arrays. + + An operator may enable this functionality by creating a specific volume + type with the property:: + + "powermax:disable_protected_snap": " True" + + When disabled (which is the default and current behavior), a + replicated source volume will be protected with a snapshot of the + same volume type. + + When enabled, snapshots of replicated source volumes will be treated + as regular, non-replicated devices. +