VMAX driver - Compression, replacing SMI-S with REST

In VMAX driver version 3.0, SMI-S has been replaced with
unisphere REST. This is porting Compression from SMIS to REST.
See original https://review.openstack.org/#/c/400824/ for more
details

Change-Id: I2d7cda0b5856ec5c3c1ca7df4f2ad93936f15b57
Partially-Implements: blueprint vmax-rest
This commit is contained in:
Helen Walsh 2017-04-26 16:54:41 +01:00
parent 95dd5b4881
commit 51252cf504
9 changed files with 339 additions and 55 deletions

View File

@ -60,6 +60,7 @@ class VMAXCommonData(object):
storagegroup_name_i = 'OS-HostX-SRP_1-Diamond-DSS-OS-iscsi-PG' storagegroup_name_i = 'OS-HostX-SRP_1-Diamond-DSS-OS-iscsi-PG'
defaultstoragegroup_name = 'OS-SRP_1-Diamond-DSS-SG' defaultstoragegroup_name = 'OS-SRP_1-Diamond-DSS-SG'
default_sg_no_slo = 'OS-no_SLO-SG' default_sg_no_slo = 'OS-no_SLO-SG'
default_sg_compr_disabled = 'OS-SRP_1-Diamond-DSS-CD-SG'
failed_resource = 'OS-failed-resource' failed_resource = 'OS-failed-resource'
fake_host = 'HostX@Backend#Diamond+DSS+SRP_1+000197800123' fake_host = 'HostX@Backend#Diamond+DSS+SRP_1+000197800123'
new_host = 'HostX@Backend#Silver+OLTP+SRP_1+000197800123' new_host = 'HostX@Backend#Silver+OLTP+SRP_1+000197800123'
@ -155,6 +156,9 @@ class VMAXCommonData(object):
# extra-specs # extra-specs
vol_type_extra_specs = {'pool_name': u'Diamond+DSS+SRP_1+000197800123'} vol_type_extra_specs = {'pool_name': u'Diamond+DSS+SRP_1+000197800123'}
vol_type_extra_specs_compr_disabled = {
'pool_name': u'Diamond+DSS+SRP_1+000197800123',
'storagetype:disablecompression': "true"}
extra_specs = {'pool_name': u'Diamond+DSS+SRP_1+000197800123', extra_specs = {'pool_name': u'Diamond+DSS+SRP_1+000197800123',
'slo': slo, 'slo': slo,
'workload': workload, 'workload': workload,
@ -162,6 +166,8 @@ class VMAXCommonData(object):
'array': array, 'array': array,
'interval': 3, 'interval': 3,
'retries': 120} 'retries': 120}
extra_specs_disable_compression = deepcopy(extra_specs)
extra_specs_disable_compression[utils.DISABLECOMPRESSION] = "true"
extra_specs_intervals_set = deepcopy(extra_specs) extra_specs_intervals_set = deepcopy(extra_specs)
extra_specs_intervals_set['interval'] = 1 extra_specs_intervals_set['interval'] = 1
extra_specs_intervals_set['retries'] = 1 extra_specs_intervals_set['retries'] = 1
@ -176,6 +182,7 @@ class VMAXCommonData(object):
'maskingview_name': masking_view_name_f, 'maskingview_name': masking_view_name_f,
'parent_sg_name': parent_sg_f, 'parent_sg_name': parent_sg_f,
'srp': srp, 'srp': srp,
'storagetype:disablecompression': False,
'port_group_name': port_group_name_f, 'port_group_name': port_group_name_f,
'slo': slo, 'slo': slo,
'storagegroup_name': storagegroup_name_f, 'storagegroup_name': storagegroup_name_f,
@ -190,6 +197,7 @@ class VMAXCommonData(object):
'initiator_check': False, 'initiator_check': False,
'maskingview_name': masking_view_name_f, 'maskingview_name': masking_view_name_f,
'srp': srp, 'srp': srp,
'storagetype:disablecompression': False,
'port_group_name': port_group_name_f, 'port_group_name': port_group_name_f,
'slo': None, 'slo': None,
'parent_sg_name': parent_sg_f, 'parent_sg_name': parent_sg_f,
@ -197,8 +205,25 @@ class VMAXCommonData(object):
'volume_name': test_volume.name, 'volume_name': test_volume.name,
'workload': None} 'workload': None}
masking_view_dict_compression_disabled = {
'array': array,
'connector': connector,
'device_id': '00001',
'init_group_name': initiatorgroup_name_f,
'initiator_check': False,
'maskingview_name': masking_view_name_f,
'srp': srp,
'storagetype:disablecompression': True,
'port_group_name': port_group_name_f,
'slo': slo,
'parent_sg_name': parent_sg_f,
'storagegroup_name': 'OS-HostX-SRP_1-DiamondDSS-OS-fibre-PG-CD',
'volume_name': test_volume['name'],
'workload': workload}
# vmax data # vmax data
# sloprovisioning # sloprovisioning
compression_info = {"symmetrixId": ["000197800128"]}
inititiatorgroup = [{"initiator": [wwpn1], inititiatorgroup = [{"initiator": [wwpn1],
"hostId": initiatorgroup_name_f, "hostId": initiatorgroup_name_f,
"maskingview": [masking_view_name_f]}, "maskingview": [masking_view_name_f]},
@ -452,6 +477,8 @@ class FakeRequestsSession(object):
return_object = self.data.srp_details return_object = self.data.srp_details
elif 'workloadtype' in url: elif 'workloadtype' in url:
return_object = self.data.workloadtype return_object = self.data.workloadtype
elif 'compressionCapable' in url:
return_object = self.data.compression_info
else: else:
return_object = self.data.slo_details return_object = self.data.slo_details
@ -857,6 +884,14 @@ class VMAXUtilsTest(test.TestCase):
srp_name, slo, workload) srp_name, slo, workload)
self.assertEqual(self.data.default_sg_no_slo, sg_name) self.assertEqual(self.data.default_sg_no_slo, sg_name)
def test_get_default_storage_group_name_compr_disabled(self):
srp_name = self.data.srp
slo = self.data.slo
workload = self.data.workload
sg_name = self.utils.get_default_storage_group_name(
srp_name, slo, workload, True)
self.assertEqual(self.data.default_sg_compr_disabled, sg_name)
def test_get_time_delta(self): def test_get_time_delta(self):
start_time = 1487781721.09 start_time = 1487781721.09
end_time = 1487781758.16 end_time = 1487781758.16
@ -910,6 +945,41 @@ class VMAXUtilsTest(test.TestCase):
pg3 = self.utils.get_pg_short_name(pg_over_12_chars) pg3 = self.utils.get_pg_short_name(pg_over_12_chars)
self.assertEqual(pg2, pg3) self.assertEqual(pg2, pg3)
def test_is_compression_disabled_true(self):
extra_specs = self.data.extra_specs_disable_compression
do_disable_compression = self.utils.is_compression_disabled(
extra_specs)
self.assertTrue(do_disable_compression)
def test_is_compression_disabled_false(self):
# Path 1: no compressiion extra spec set
extra_specs = self.data.extra_specs
do_disable_compression = self.utils.is_compression_disabled(
extra_specs)
self.assertFalse(do_disable_compression)
# Path 2: compression extra spec set to false
extra_specs2 = deepcopy(extra_specs)
extra_specs2.update({utils.DISABLECOMPRESSION: 'false'})
do_disable_compression2 = self.utils.is_compression_disabled(
extra_specs)
self.assertFalse(do_disable_compression2)
def test_change_compression_type_true(self):
source_compr_disabled_true = 'true'
new_type_compr_disabled = {
'extra_specs': {utils.DISABLECOMPRESSION: 'no'}}
ans = self.utils.change_compression_type(
source_compr_disabled_true, new_type_compr_disabled)
self.assertTrue(ans)
def test_change_compression_type_false(self):
source_compr_disabled_true = True
new_type_compr_disabled = {
'extra_specs': {utils.DISABLECOMPRESSION: 'true'}}
ans = self.utils.change_compression_type(
source_compr_disabled_true, new_type_compr_disabled)
self.assertFalse(ans)
class VMAXRestTest(test.TestCase): class VMAXRestTest(test.TestCase):
def setUp(self): def setUp(self):
@ -1115,6 +1185,17 @@ class VMAXRestTest(test.TestCase):
self.data.slo, self.data.workload) self.data.slo, self.data.workload)
self.assertIsNone(headroom_cap) self.assertIsNone(headroom_cap)
def test_is_compression_capable_true(self):
compr_capable = self.rest.is_compression_capable('000197800128')
self.assertTrue(compr_capable)
def test_is_compression_capable_false(self):
compr_capable = self.rest.is_compression_capable(self.data.array)
self.assertFalse(compr_capable)
with mock.patch.object(self.rest, 'request', return_value=(200, {})):
compr_capable = self.rest.is_compression_capable(self.data.array)
self.assertFalse(compr_capable)
def test_get_storage_group(self): def test_get_storage_group(self):
ref_details = self.data.sg_details[0] ref_details = self.data.sg_details[0]
sg_details = self.rest.get_storage_group( sg_details = self.rest.get_storage_group(
@ -1159,6 +1240,28 @@ class VMAXRestTest(test.TestCase):
None, None, self.data.extra_specs) None, None, self.data.extra_specs)
self.assertEqual(self.data.default_sg_no_slo, sg_name) self.assertEqual(self.data.default_sg_no_slo, sg_name)
def test_create_storage_group_compression_disabled(self):
with mock.patch.object(self.rest, '_create_storagegroup',
return_value=(200, self.data.job_list[0])):
self.rest.create_storage_group(
self.data.array, self.data.default_sg_compr_disabled,
self.data.srp, self.data.slo, self.data.workload,
self.data.extra_specs, True)
payload = {"srpId": self.data.srp,
"storageGroupId": self.data.default_sg_compr_disabled,
"emulation": "FBA",
"create_empty_storage_group": "true",
"sloBasedStorageGroupParam": [
{"num_of_vols": 0,
"sloId": self.data.slo,
"workloadSelection": self.data.workload,
"volumeAttribute": {
"volume_size": "0",
"capacityUnit": "GB"},
"noCompression": "true"}]}
self.rest._create_storagegroup.assert_called_once_with(
self.data.array, payload)
def test_modify_storage_group(self): def test_modify_storage_group(self):
array = self.data.array array = self.data.array
storagegroup = self.data.defaultstoragegroup_name storagegroup = self.data.defaultstoragegroup_name
@ -2651,6 +2754,17 @@ class VMAXCommonTest(test.TestCase):
volume, connector, extra_specs) volume, connector, extra_specs)
self.assertEqual(ref_mv_dict, masking_view_dict) self.assertEqual(ref_mv_dict, masking_view_dict)
def test_populate_masking_dict_compr_disabled(self):
volume = self.data.test_volume
connector = self.data.connector
extra_specs = deepcopy(self.data.extra_specs)
extra_specs['port_group_name'] = self.data.port_group_name_f
extra_specs[utils.DISABLECOMPRESSION] = "true"
ref_mv_dict = self.data.masking_view_dict_compression_disabled
masking_view_dict = self.common._populate_masking_dict(
volume, connector, extra_specs)
self.assertEqual(ref_mv_dict, masking_view_dict)
def test_create_cloned_volume(self): def test_create_cloned_volume(self):
volume = self.data.test_clone_volume volume = self.data.test_clone_volume
source_volume = self.data.test_volume source_volume = self.data.test_volume
@ -2823,6 +2937,27 @@ class VMAXCommonTest(test.TestCase):
extra_specs = self.common._set_vmax_extra_specs({}, srp_record) extra_specs = self.common._set_vmax_extra_specs({}, srp_record)
self.assertEqual('Optimized', extra_specs['slo']) self.assertEqual('Optimized', extra_specs['slo'])
def test_set_vmax_extra_specs_compr_disabled(self):
with mock.patch.object(self.rest, 'is_compression_capable',
return_value=True):
srp_record = self.utils.parse_file_to_get_array_map(
self.fake_xml)
extra_specs = self.common._set_vmax_extra_specs(
self.data.vol_type_extra_specs_compr_disabled, srp_record)
ref_extra_specs = deepcopy(self.data.extra_specs_intervals_set)
ref_extra_specs['port_group_name'] = self.data.port_group_name_f
ref_extra_specs[utils.DISABLECOMPRESSION] = "true"
self.assertEqual(ref_extra_specs, extra_specs)
def test_set_vmax_extra_specs_compr_disabled_not_compr_capable(self):
srp_record = self.utils.parse_file_to_get_array_map(
self.fake_xml)
extra_specs = self.common._set_vmax_extra_specs(
self.data.vol_type_extra_specs_compr_disabled, srp_record)
ref_extra_specs = deepcopy(self.data.extra_specs_intervals_set)
ref_extra_specs['port_group_name'] = self.data.port_group_name_f
self.assertEqual(ref_extra_specs, extra_specs)
def test_set_vmax_extra_specs_portgroup_as_spec(self): def test_set_vmax_extra_specs_portgroup_as_spec(self):
srp_record = self.utils.parse_file_to_get_array_map( srp_record = self.utils.parse_file_to_get_array_map(
self.fake_xml) self.fake_xml)
@ -3161,41 +3296,44 @@ class VMAXCommonTest(test.TestCase):
extra_specs = self.data.extra_specs_intervals_set extra_specs = self.data.extra_specs_intervals_set
extra_specs['port_group_name'] = self.data.port_group_name_f extra_specs['port_group_name'] = self.data.port_group_name_f
volume = self.data.test_volume volume = self.data.test_volume
new_type = {'extra_specs': {}}
host = {'host': self.data.new_host} host = {'host': self.data.new_host}
self.common.retype(volume, host) self.common.retype(volume, new_type, host)
mock_migrate.assert_called_once_with( mock_migrate.assert_called_once_with(
device_id, volume, host, volume_name, extra_specs) device_id, volume, host, volume_name, new_type, extra_specs)
mock_migrate.reset_mock() mock_migrate.reset_mock()
with mock.patch.object( with mock.patch.object(
self.common, '_find_device_on_array', return_value=None): self.common, '_find_device_on_array', return_value=None):
self.common.retype(volume, host) self.common.retype(volume, new_type, host)
mock_migrate.assert_not_called() mock_migrate.assert_not_called()
def test_slo_workload_migration_valid(self): def test_slo_workload_migration_valid(self):
device_id = self.data.volume_details[0]['volumeId'] device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
new_type = {'extra_specs': {}}
volume = self.data.test_volume volume = self.data.test_volume
host = {'host': self.data.new_host} host = {'host': self.data.new_host}
with mock.patch.object(self.common, '_migrate_volume'): with mock.patch.object(self.common, '_migrate_volume'):
self.common._slo_workload_migration( self.common._slo_workload_migration(
device_id, volume, host, volume_name, extra_specs) device_id, volume, host, volume_name, new_type, extra_specs)
self.common._migrate_volume.assert_called_once_with( self.common._migrate_volume.assert_called_once_with(
extra_specs[utils.ARRAY], device_id, extra_specs[utils.ARRAY], device_id,
extra_specs[utils.SRP], 'Silver', extra_specs[utils.SRP], 'Silver',
'OLTP', volume_name, extra_specs) 'OLTP', volume_name, new_type, extra_specs)
def test_slo_workload_migration_not_valid(self): def test_slo_workload_migration_not_valid(self):
device_id = self.data.volume_details[0]['volumeId'] device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
volume = self.data.test_volume volume = self.data.test_volume
new_type = {'extra_specs': {}}
host = {'host': self.data.new_host} host = {'host': self.data.new_host}
with mock.patch.object(self.common, with mock.patch.object(self.common,
'_is_valid_for_storage_assisted_migration', '_is_valid_for_storage_assisted_migration',
return_value=(False, 'Silver', 'OLTP')): return_value=(False, 'Silver', 'OLTP')):
migrate_status = self.common._slo_workload_migration( migrate_status = self.common._slo_workload_migration(
device_id, volume, host, volume_name, extra_specs) device_id, volume, host, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
def test_slo_workload_migration_same_hosts(self): def test_slo_workload_migration_same_hosts(self):
@ -3204,10 +3342,31 @@ class VMAXCommonTest(test.TestCase):
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
volume = self.data.test_volume volume = self.data.test_volume
host = {'host': self.data.fake_host} host = {'host': self.data.fake_host}
new_type = {'extra_specs': {}}
migrate_status = self.common._slo_workload_migration( migrate_status = self.common._slo_workload_migration(
device_id, volume, host, volume_name, extra_specs) device_id, volume, host, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
def test_slo_workload_migration_same_host_change_compression(self):
device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs
volume = self.data.test_volume
host = {'host': self.data.fake_host}
new_type = {'extra_specs': {utils.DISABLECOMPRESSION: "true"}}
with mock.patch.object(
self.common, '_is_valid_for_storage_assisted_migration',
return_value=(True, self.data.slo, self.data.workload)):
with mock.patch.object(self.common, '_migrate_volume'):
migrate_status = self.common._slo_workload_migration(
device_id, volume, host, volume_name, new_type,
extra_specs)
self.assertTrue(migrate_status)
self.common._migrate_volume.assert_called_once_with(
extra_specs[utils.ARRAY], device_id,
extra_specs[utils.SRP], self.data.slo,
self.data.workload, volume_name, new_type, extra_specs)
@mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members') @mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members')
def test_migrate_volume_success(self, mock_remove): def test_migrate_volume_success(self, mock_remove):
with mock.patch.object(self.rest, 'is_volume_in_storagegroup', with mock.patch.object(self.rest, 'is_volume_in_storagegroup',
@ -3215,9 +3374,10 @@ class VMAXCommonTest(test.TestCase):
device_id = self.data.volume_details[0]['volumeId'] device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
new_type = {'extra_specs': {}}
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, device_id, self.data.srp, self.data.slo,
self.data.workload, volume_name, extra_specs) self.data.workload, volume_name, new_type, extra_specs)
self.assertTrue(migrate_status) self.assertTrue(migrate_status)
mock_remove.assert_called_once_with( mock_remove.assert_called_once_with(
self.data.array, device_id, None, extra_specs, False) self.data.array, device_id, None, extra_specs, False)
@ -3227,7 +3387,7 @@ class VMAXCommonTest(test.TestCase):
return_value=[]): return_value=[]):
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, device_id, self.data.srp, self.data.slo,
self.data.workload, volume_name, extra_specs) self.data.workload, volume_name, new_type, extra_specs)
self.assertTrue(migrate_status) self.assertTrue(migrate_status)
mock_remove.assert_not_called() mock_remove.assert_not_called()
@ -3236,24 +3396,26 @@ class VMAXCommonTest(test.TestCase):
device_id = self.data.volume_details[0]['volumeId'] device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
new_type = {'extra_specs': {}}
with mock.patch.object( with mock.patch.object(
self.masking, 'get_or_create_default_storage_group', self.masking, 'get_or_create_default_storage_group',
side_effect=exception.VolumeBackendAPIException): side_effect=exception.VolumeBackendAPIException):
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, device_id, self.data.srp, self.data.slo,
self.data.workload, volume_name, extra_specs) self.data.workload, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
def test_migrate_volume_failed_vol_not_added(self): def test_migrate_volume_failed_vol_not_added(self):
device_id = self.data.volume_details[0]['volumeId'] device_id = self.data.volume_details[0]['volumeId']
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
extra_specs = self.data.extra_specs extra_specs = self.data.extra_specs
new_type = {'extra_specs': {}}
with mock.patch.object( with mock.patch.object(
self.rest, 'is_volume_in_storagegroup', self.rest, 'is_volume_in_storagegroup',
return_value=False): return_value=False):
migrate_status = self.common._migrate_volume( migrate_status = self.common._migrate_volume(
self.data.array, device_id, self.data.srp, self.data.slo, self.data.array, device_id, self.data.srp, self.data.slo,
self.data.workload, volume_name, extra_specs) self.data.workload, volume_name, new_type, extra_specs)
self.assertFalse(migrate_status) self.assertFalse(migrate_status)
def test_is_valid_for_storage_assisted_migration_true(self): def test_is_valid_for_storage_assisted_migration_true(self):
@ -3262,13 +3424,15 @@ class VMAXCommonTest(test.TestCase):
volume_name = self.data.test_volume['name'] volume_name = self.data.test_volume['name']
ref_return = (True, 'Silver', 'OLTP') ref_return = (True, 'Silver', 'OLTP')
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host, self.data.array, self.data.srp, volume_name) device_id, host, self.data.array,
self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
# No current sgs found # No current sgs found
with mock.patch.object(self.rest, 'get_storage_groups_from_volume', with mock.patch.object(self.rest, 'get_storage_groups_from_volume',
return_value=None): return_value=None):
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host, self.data.array, self.data.srp, volume_name) device_id, host, self.data.array, self.data.srp,
volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
def test_is_valid_for_storage_assisted_migration_false(self): def test_is_valid_for_storage_assisted_migration_false(self):
@ -3278,22 +3442,26 @@ class VMAXCommonTest(test.TestCase):
# IndexError # IndexError
host = {'host': 'HostX@Backend#Silver+SRP_1+000197800123'} host = {'host': 'HostX@Backend#Silver+SRP_1+000197800123'}
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host, self.data.array, self.data.srp, volume_name) device_id, host, self.data.array,
self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
# Wrong array # Wrong array
host2 = {'host': 'HostX@Backend#Silver+OLTP+SRP_1+00012345678'} host2 = {'host': 'HostX@Backend#Silver+OLTP+SRP_1+00012345678'}
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host2, self.data.array, self.data.srp, volume_name) device_id, host2, self.data.array,
self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
# Wrong srp # Wrong srp
host3 = {'host': 'HostX@Backend#Silver+OLTP+SRP_2+000197800123'} host3 = {'host': 'HostX@Backend#Silver+OLTP+SRP_2+000197800123'}
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host3, self.data.array, self.data.srp, volume_name) device_id, host3, self.data.array,
self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
# Already in correct sg # Already in correct sg
host4 = {'host': self.data.fake_host} host4 = {'host': self.data.fake_host}
return_val = self.common._is_valid_for_storage_assisted_migration( return_val = self.common._is_valid_for_storage_assisted_migration(
device_id, host4, self.data.array, self.data.srp, volume_name) device_id, host4, self.data.array,
self.data.srp, volume_name, False)
self.assertEqual(ref_return, return_val) self.assertEqual(ref_return, return_val)
@ -3509,11 +3677,12 @@ class VMAXFCTest(test.TestCase):
def test_retype(self): def test_retype(self):
host = {'host': self.data.new_host} host = {'host': self.data.new_host}
new_type = {'extra_specs': {}}
with mock.patch.object(self.common, 'retype', with mock.patch.object(self.common, 'retype',
return_value=True): return_value=True):
self.driver.retype({}, self.data.test_volume, '', '', host) self.driver.retype({}, self.data.test_volume, new_type, '', host)
self.common.retype.assert_called_once_with( self.common.retype.assert_called_once_with(
self.data.test_volume, host) self.data.test_volume, new_type, host)
class VMAXISCSITest(test.TestCase): class VMAXISCSITest(test.TestCase):
@ -3739,11 +3908,12 @@ class VMAXISCSITest(test.TestCase):
def test_retype(self): def test_retype(self):
host = {'host': self.data.new_host} host = {'host': self.data.new_host}
new_type = {'extra_specs': {}}
with mock.patch.object(self.common, 'retype', with mock.patch.object(self.common, 'retype',
return_value=True): return_value=True):
self.driver.retype({}, self.data.test_volume, '', '', host) self.driver.retype({}, self.data.test_volume, new_type, '', host)
self.common.retype.assert_called_once_with( self.common.retype.assert_called_once_with(
self.data.test_volume, host) self.data.test_volume, new_type, host)
class VMAXMaskingTest(test.TestCase): class VMAXMaskingTest(test.TestCase):

View File

@ -19,6 +19,7 @@ import sys
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils
import six import six
from cinder import exception from cinder import exception
@ -854,6 +855,7 @@ class VMAXCommon(object):
unique_name = self.utils.truncate_string(extra_specs[utils.SRP], 12) unique_name = self.utils.truncate_string(extra_specs[utils.SRP], 12)
protocol = self.utils.get_short_protocol_type(self.protocol) protocol = self.utils.get_short_protocol_type(self.protocol)
short_host_name = self.utils.get_host_short_name(host_name) short_host_name = self.utils.get_host_short_name(host_name)
masking_view_dict[utils.DISABLECOMPRESSION] = False
slo = extra_specs[utils.SLO] slo = extra_specs[utils.SLO]
workload = extra_specs[utils.WORKLOAD] workload = extra_specs[utils.WORKLOAD]
short_pg_name = self.utils.get_pg_short_name( short_pg_name = self.utils.get_pg_short_name(
@ -877,6 +879,12 @@ class VMAXCommon(object):
'srpName': unique_name, 'srpName': unique_name,
'combo': slo_wl_combo, 'combo': slo_wl_combo,
'pg': short_pg_name}) 'pg': short_pg_name})
do_disable_compression = self.utils.is_compression_disabled(
extra_specs)
if do_disable_compression:
child_sg_name = ("%(child_sg_name)s-CD"
% {'child_sg_name': child_sg_name})
masking_view_dict[utils.DISABLECOMPRESSION] = True
else: else:
child_sg_name = ( child_sg_name = (
"OS-%(shortHostName)s-No_SLO-%(pg)s" "OS-%(shortHostName)s-No_SLO-%(pg)s"
@ -1092,9 +1100,13 @@ class VMAXCommon(object):
'array': array, 'array': array,
'size': volume_size}) 'size': volume_size})
do_disable_compression = self.utils.is_compression_disabled(
extra_specs)
storagegroup_name = self.masking.get_or_create_default_storage_group( storagegroup_name = self.masking.get_or_create_default_storage_group(
array, extra_specs[utils.SRP], extra_specs[utils.SLO], array, extra_specs[utils.SRP], extra_specs[utils.SLO],
extra_specs[utils.WORKLOAD], extra_specs) extra_specs[utils.WORKLOAD], extra_specs,
do_disable_compression)
try: try:
volume_dict = self.provision.create_volume_from_sg( volume_dict = self.provision.create_volume_from_sg(
array, volume_name, storagegroup_name, array, volume_name, storagegroup_name,
@ -1187,6 +1199,14 @@ class VMAXCommon(object):
slo_from_extra_spec = None slo_from_extra_spec = None
extra_specs[utils.SLO] = slo_from_extra_spec extra_specs[utils.SLO] = slo_from_extra_spec
extra_specs[utils.WORKLOAD] = workload_from_extra_spec extra_specs[utils.WORKLOAD] = workload_from_extra_spec
if self.rest.is_compression_capable(extra_specs[utils.ARRAY]):
if extra_specs.get(utils.DISABLECOMPRESSION):
# If not True remove it.
if not strutils.bool_from_string(
extra_specs[utils.DISABLECOMPRESSION]):
extra_specs.pop(utils.DISABLECOMPRESSION, None)
else:
extra_specs.pop(utils.DISABLECOMPRESSION, None)
LOG.debug("SRP is: %(srp)s " LOG.debug("SRP is: %(srp)s "
"Array is: %(array)s " "Array is: %(array)s "
@ -1536,10 +1556,11 @@ class VMAXCommon(object):
self.rest.rename_volume( self.rest.rename_volume(
extra_specs[utils.ARRAY], device_id, volume_id) extra_specs[utils.ARRAY], device_id, volume_id)
def retype(self, volume, host): def retype(self, volume, new_type, host):
"""Migrate volume to another host using retype. """Migrate volume to another host using retype.
:param volume: the volume object including the volume_type_id :param volume: the volume object including the volume_type_id
:param new_type: the new volume type.
:param host: The host dict holding the relevant target(destination) :param host: The host dict holding the relevant target(destination)
information information
:returns: boolean -- True if retype succeeded, False if error :returns: boolean -- True if retype succeeded, False if error
@ -1558,23 +1579,30 @@ class VMAXCommon(object):
return False return False
return self._slo_workload_migration(device_id, volume, host, return self._slo_workload_migration(device_id, volume, host,
volume_name, extra_specs) volume_name, new_type, extra_specs)
def _slo_workload_migration(self, device_id, volume, host, def _slo_workload_migration(self, device_id, volume, host,
volume_name, extra_specs): volume_name, new_type, extra_specs):
"""Migrate from SLO/Workload combination to another. """Migrate from SLO/Workload combination to another.
:param device_id: the volume device id :param device_id: the volume device id
:param volume: the volume object :param volume: the volume object
:param host: the host dict :param host: the host dict
:param volume_name: the name of the volume :param volume_name: the name of the volume
:param new_type: the type to migrate to
:param extra_specs: extra specifications :param extra_specs: extra specifications
:returns: boolean -- True if migration succeeded, False if error. :returns: boolean -- True if migration succeeded, False if error.
""" """
is_compression_disabled = self.utils.is_compression_disabled(
extra_specs)
# Check if old type and new type have different compression types
do_change_compression = (self.utils.change_compression_type(
is_compression_disabled, new_type))
is_valid, target_slo, target_workload = ( is_valid, target_slo, target_workload = (
self._is_valid_for_storage_assisted_migration( self._is_valid_for_storage_assisted_migration(
device_id, host, extra_specs[utils.ARRAY], device_id, host, extra_specs[utils.ARRAY],
extra_specs[utils.SRP], volume_name)) extra_specs[utils.SRP], volume_name,
do_change_compression))
if not is_valid: if not is_valid:
LOG.error( LOG.error(
@ -1582,23 +1610,24 @@ class VMAXCommon(object):
"assisted migration using retype.", "assisted migration using retype.",
{'name': volume_name}) {'name': volume_name})
return False return False
if volume.host != host['host']: if volume.host != host['host'] or do_change_compression:
LOG.debug( LOG.debug(
"Retype Volume %(name)s from source host %(sourceHost)s " "Retype Volume %(name)s from source host %(sourceHost)s "
"to target host %(targetHost)s. ", "to target host %(targetHost)s. Compression change is %(cc)r.",
{'name': volume_name, {'name': volume_name,
'sourceHost': volume.host, 'sourceHost': volume.host,
'targetHost': host['host']}) 'targetHost': host['host'],
'cc': do_change_compression})
return self._migrate_volume( return self._migrate_volume(
extra_specs[utils.ARRAY], device_id, extra_specs[utils.ARRAY], device_id,
extra_specs[utils.SRP], target_slo, extra_specs[utils.SRP], target_slo,
target_workload, volume_name, extra_specs) target_workload, volume_name, new_type, extra_specs)
return False return False
def _migrate_volume( def _migrate_volume(
self, array, device_id, srp, target_slo, self, array, device_id, srp, target_slo,
target_workload, volume_name, extra_specs): target_workload, volume_name, new_type, extra_specs):
"""Migrate from one slo/workload combination to another. """Migrate from one slo/workload combination to another.
This requires moving the volume from its current SG to a This requires moving the volume from its current SG to a
@ -1609,23 +1638,28 @@ class VMAXCommon(object):
:param target_slo: the target service level :param target_slo: the target service level
:param target_workload: the target workload :param target_workload: the target workload
:param volume_name: the volume name :param volume_name: the volume name
:param new_type: the volume type to migrate to
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
:return: bool :return: bool
""" """
storagegroups = self.rest.get_storage_groups_from_volume( storagegroups = self.rest.get_storage_groups_from_volume(
array, device_id) array, device_id)
if not storagegroups: if not storagegroups:
LOG.warning( LOG.warning("Volume : %(volume_name)s does not currently "
"Volume : %(volume_name)s does not currently "
"belong to any storage groups.", "belong to any storage groups.",
{'volume_name': volume_name}) {'volume_name': volume_name})
else: else:
self.masking.remove_and_reset_members( self.masking.remove_and_reset_members(
array, device_id, None, extra_specs, False) array, device_id, None, extra_specs, False)
target_extra_specs = new_type['extra_specs']
is_compression_disabled = self.utils.is_compression_disabled(
target_extra_specs)
try: try:
target_sg_name = self.masking.get_or_create_default_storage_group( target_sg_name = self.masking.get_or_create_default_storage_group(
array, srp, target_slo, target_workload, extra_specs) array, srp, target_slo, target_workload, extra_specs,
is_compression_disabled)
except Exception as e: except Exception as e:
LOG.error("Failed to get or create storage group. " LOG.error("Failed to get or create storage group. "
"Exception received was %(e)s.", {'e': e}) "Exception received was %(e)s.", {'e': e})
@ -1648,7 +1682,7 @@ class VMAXCommon(object):
def _is_valid_for_storage_assisted_migration( def _is_valid_for_storage_assisted_migration(
self, device_id, host, source_array, self, device_id, host, source_array,
source_srp, volume_name): source_srp, volume_name, do_change_compression):
"""Check if volume is suitable for storage assisted (pool) migration. """Check if volume is suitable for storage assisted (pool) migration.
:param device_id: the volume device id :param device_id: the volume device id
@ -1656,6 +1690,7 @@ class VMAXCommon(object):
:param source_array: the volume's current array serial number :param source_array: the volume's current array serial number
:param source_srp: the volume's current pool name :param source_srp: the volume's current pool name
:param volume_name: the name of the volume to be migrated :param volume_name: the name of the volume to be migrated
:param do_change_compression: do change compression
:returns: boolean -- True/False :returns: boolean -- True/False
:returns: string -- targetSlo :returns: string -- targetSlo
:returns: string -- targetWorkload :returns: string -- targetWorkload
@ -1711,6 +1746,9 @@ class VMAXCommon(object):
% {'targetSlo': target_slo, % {'targetSlo': target_slo,
'targetWorkload': target_workload}) 'targetWorkload': target_workload})
if target_combination in emc_fast_setting: if target_combination in emc_fast_setting:
# Check if migration is from compression to non compression
# or vice versa
if not do_change_compression:
LOG.warning( LOG.warning(
"No action required. Volume: %(volume_name)s is " "No action required. Volume: %(volume_name)s is "
"already part of slo/workload combination: " "already part of slo/workload combination: "

View File

@ -78,6 +78,7 @@ class VMAXFCDriver(driver.FibreChannelDriver):
3.0.0 - REST based driver 3.0.0 - REST based driver
- Retype (storage-assisted migration) - Retype (storage-assisted migration)
- QoS support - QoS support
- Support for compression on All Flash
""" """
VERSION = "3.0.0" VERSION = "3.0.0"
@ -438,4 +439,4 @@ class VMAXFCDriver(driver.FibreChannelDriver):
target(destination) information target(destination) information
:returns: boolean -- True if retype succeeded, False if error :returns: boolean -- True if retype succeeded, False if error
""" """
return self.common.retype(volume, host) return self.common.retype(volume, new_type, host)

View File

@ -83,6 +83,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
3.0.0 - REST based driver 3.0.0 - REST based driver
- Retype (storage-assisted migration) - Retype (storage-assisted migration)
- QoS support - QoS support
- Support for compression on All Flash
""" """
VERSION = "3.0.0" VERSION = "3.0.0"
@ -382,4 +383,4 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
target(destination) information target(destination) information
:returns: boolean -- True if retype succeeded, False if error :returns: boolean -- True if retype succeeded, False if error
""" """
return self.common.retype(volume, host) return self.common.retype(volume, new_type, host)

View File

@ -141,7 +141,8 @@ class VMAXMasking(object):
default_sg_name = self.utils.get_default_storage_group_name( default_sg_name = self.utils.get_default_storage_group_name(
masking_view_dict[utils.SRP], masking_view_dict[utils.SRP],
masking_view_dict[utils.SLO], masking_view_dict[utils.SLO],
masking_view_dict[utils.WORKLOAD]) masking_view_dict[utils.WORKLOAD],
masking_view_dict[utils.DISABLECOMPRESSION])
check_vol = self.rest.is_volume_in_storagegroup( check_vol = self.rest.is_volume_in_storagegroup(
serial_number, device_id, default_sg_name) serial_number, device_id, default_sg_name)
@ -352,12 +353,14 @@ class VMAXMasking(object):
slo = None slo = None
else: else:
slo = extra_specs[utils.SLO] slo = extra_specs[utils.SLO]
do_disable_compression = (
masking_view_dict[utils.DISABLECOMPRESSION])
storagegroup = self.rest.get_storage_group( storagegroup = self.rest.get_storage_group(
serial_number, storagegroup_name) serial_number, storagegroup_name)
if storagegroup is None: if storagegroup is None:
storagegroup = self.provision.create_storage_group( storagegroup = self.provision.create_storage_group(
serial_number, storagegroup_name, srp, slo, workload, serial_number, storagegroup_name, srp, slo, workload,
extra_specs) extra_specs, do_disable_compression)
if storagegroup is None: if storagegroup is None:
msg = ("Cannot get or create a storage group: " msg = ("Cannot get or create a storage group: "
@ -1210,16 +1213,19 @@ class VMAXMasking(object):
:param volume_name: the volume name :param volume_name: the volume name
:param extra_specs: the extra specifications :param extra_specs: the extra specifications
""" """
do_disable_compression = self.utils.is_compression_disabled(
extra_specs)
storagegroup_name = self.get_or_create_default_storage_group( storagegroup_name = self.get_or_create_default_storage_group(
serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO], serial_number, extra_specs[utils.SRP], extra_specs[utils.SLO],
extra_specs[utils.WORKLOAD], extra_specs) extra_specs[utils.WORKLOAD], extra_specs, do_disable_compression)
self._check_adding_volume_to_storage_group( self._check_adding_volume_to_storage_group(
serial_number, device_id, storagegroup_name, volume_name, serial_number, device_id, storagegroup_name, volume_name,
extra_specs) extra_specs)
def get_or_create_default_storage_group( def get_or_create_default_storage_group(
self, serial_number, srp, slo, workload, extra_specs): self, serial_number, srp, slo, workload, extra_specs,
do_disable_compression=False):
"""Get or create a default storage group. """Get or create a default storage group.
:param serial_number: the array serial number :param serial_number: the array serial number
@ -1227,12 +1233,13 @@ class VMAXMasking(object):
:param slo: the SLO :param slo: the SLO
:param workload: the workload :param workload: the workload
:param extra_specs: extra specifications :param extra_specs: extra specifications
:param do_disable_compression: flag for compression
:returns: storagegroup_name :returns: storagegroup_name
:raises: VolumeBackendAPIException :raises: VolumeBackendAPIException
""" """
storagegroup, storagegroup_name = ( storagegroup, storagegroup_name = (
self.rest.get_vmax_default_storage_group( self.rest.get_vmax_default_storage_group(
serial_number, srp, slo, workload)) serial_number, srp, slo, workload, do_disable_compression))
if storagegroup is None: if storagegroup is None:
self.provision.create_storage_group( self.provision.create_storage_group(
serial_number, storagegroup_name, srp, slo, workload, serial_number, storagegroup_name, srp, slo, workload,

View File

@ -35,7 +35,8 @@ class VMAXProvision(object):
self.rest = rest self.rest = rest
def create_storage_group( def create_storage_group(
self, array, storagegroup_name, srp, slo, workload, extra_specs): self, array, storagegroup_name, srp, slo, workload,
extra_specs, do_disable_compression=False):
"""Create a new storage group. """Create a new storage group.
:param array: the array serial number :param array: the array serial number
@ -44,6 +45,7 @@ class VMAXProvision(object):
:param slo: the SLO (String) :param slo: the SLO (String)
:param workload: the workload (String) :param workload: the workload (String)
:param extra_specs: additional info :param extra_specs: additional info
:param do_disable_compression: disable compression flag
:returns: storagegroup - storage group object :returns: storagegroup - storage group object
""" """
start_time = time.time() start_time = time.time()
@ -51,7 +53,8 @@ class VMAXProvision(object):
@coordination.synchronized("emc-sg-{storage_group}") @coordination.synchronized("emc-sg-{storage_group}")
def do_create_storage_group(storage_group): def do_create_storage_group(storage_group):
storagegroup = self.rest.create_storage_group( storagegroup = self.rest.create_storage_group(
array, storage_group, srp, slo, workload, extra_specs) array, storage_group, srp, slo, workload, extra_specs,
do_disable_compression)
LOG.debug("Create storage group took: %(delta)s H:MM:SS.", LOG.debug("Create storage group took: %(delta)s H:MM:SS.",
{'delta': self.utils.get_time_delta(start_time, {'delta': self.utils.get_time_delta(start_time,

View File

@ -463,6 +463,22 @@ class VMAXRest(object):
remaining_capacity = None remaining_capacity = None
return remaining_capacity return remaining_capacity
def is_compression_capable(self, array):
"""Check if array is compression capable.
:param array: array serial number
:returns: bool
"""
is_compression_capable = False
target_uri = "/84/sloprovisioning/symmetrix?compressionCapable=true"
status_code, message = self.request(target_uri, GET)
self.check_status_code_success(
"Check if compression enabled", status_code, message)
if message.get('symmetrixId'):
if array in message['symmetrixId']:
is_compression_capable = True
return is_compression_capable
def get_storage_group(self, array, storage_group_name): def get_storage_group(self, array, storage_group_name):
"""Given a name, return storage group details. """Given a name, return storage group details.
@ -565,7 +581,8 @@ class VMAXRest(object):
array, SLOPROVISIONING, 'storagegroup', payload) array, SLOPROVISIONING, 'storagegroup', payload)
def create_storage_group(self, array, storagegroup_name, def create_storage_group(self, array, storagegroup_name,
srp, slo, workload, extra_specs): srp, slo, workload, extra_specs,
do_disable_compression=False):
"""Create the volume in the specified storage group. """Create the volume in the specified storage group.
:param array: the array serial number :param array: the array serial number
@ -573,6 +590,7 @@ class VMAXRest(object):
:param srp: the SRP (String) :param srp: the SRP (String)
:param slo: the SLO (String) :param slo: the SLO (String)
:param workload: the workload (String) :param workload: the workload (String)
:param do_disable_compression: flag for disabling compression
:param extra_specs: additional info :param extra_specs: additional info
:returns: storagegroup_name - string :returns: storagegroup_name - string
""" """
@ -589,6 +607,11 @@ class VMAXRest(object):
"volumeAttribute": { "volumeAttribute": {
"volume_size": "0", "volume_size": "0",
"capacityUnit": "GB"}} "capacityUnit": "GB"}}
if do_disable_compression:
slo_param.update({"noCompression": "true"})
elif self.is_compression_capable(array):
slo_param.update({"noCompression": "false"})
payload.update({"sloBasedStorageGroupParam": [slo_param]}) payload.update({"sloBasedStorageGroupParam": [slo_param]})
status_code, job = self._create_storagegroup(array, payload) status_code, job = self._create_storagegroup(array, payload)
@ -783,13 +806,15 @@ class VMAXRest(object):
return_value = False return_value = False
return return_value return return_value
def get_vmax_default_storage_group(self, array, srp, slo, workload): def get_vmax_default_storage_group(self, array, srp, slo, workload,
do_disable_compression=False):
"""Get the default storage group. """Get the default storage group.
:param array: the array serial number :param array: the array serial number
:param srp: the pool name :param srp: the pool name
:param slo: the SLO :param slo: the SLO
:param workload: the workload :param workload: the workload
:param do_disable_compression: flag for disabling compression
:returns: the storage group dict (or None), the storage group name :returns: the storage group dict (or None), the storage group name
""" """
storagegroup_name = self.utils.get_default_storage_group_name( storagegroup_name = self.utils.get_default_storage_group_name(

View File

@ -20,6 +20,7 @@ import re
from xml.dom import minidom from xml.dom import minidom
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils
import six import six
from cinder import exception from cinder import exception
@ -52,6 +53,7 @@ PARENT_SG_NAME = 'parent_sg_name'
CONNECTOR = 'connector' CONNECTOR = 'connector'
VOL_NAME = 'volume_name' VOL_NAME = 'volume_name'
EXTRA_SPECS = 'extra_specs' EXTRA_SPECS = 'extra_specs'
DISABLECOMPRESSION = 'storagetype:disablecompression'
class VMAXUtils(object): class VMAXUtils(object):
@ -144,18 +146,24 @@ class VMAXUtils(object):
return six.text_type(datetime.timedelta(seconds=int(delta))) return six.text_type(datetime.timedelta(seconds=int(delta)))
@staticmethod @staticmethod
def get_default_storage_group_name(srp_name, slo, workload): def get_default_storage_group_name(
srp_name, slo, workload, is_compression_disabled=False):
"""Determine default storage group from extra_specs. """Determine default storage group from extra_specs.
:param srp_name: the name of the srp on the array :param srp_name: the name of the srp on the array
:param slo: the service level string e.g Bronze :param slo: the service level string e.g Bronze
:param workload: the workload string e.g DSS :param workload: the workload string e.g DSS
:param is_compression_disabled: flag for disabling compression
:returns: storage_group_name :returns: storage_group_name
""" """
if slo and workload: if slo and workload:
prefix = ("OS-%(srpName)s-%(slo)s-%(workload)s" prefix = ("OS-%(srpName)s-%(slo)s-%(workload)s"
% {'srpName': srp_name, 'slo': slo, % {'srpName': srp_name, 'slo': slo,
'workload': workload}) 'workload': workload})
if is_compression_disabled:
prefix += "-CD"
else: else:
prefix = "OS-no_SLO" prefix = "OS-no_SLO"
@ -399,3 +407,30 @@ class VMAXUtils(object):
raise exception.VolumeBackendAPIException( raise exception.VolumeBackendAPIException(
data=exception_message) data=exception_message)
return array, device_id return array, device_id
@staticmethod
def is_compression_disabled(extra_specs):
"""Check is compression is to be disabled.
:param extra_specs: extra specifications
:returns: boolean
"""
do_disable_compression = False
if DISABLECOMPRESSION in extra_specs:
if strutils.bool_from_string(extra_specs[DISABLECOMPRESSION]):
do_disable_compression = True
return do_disable_compression
def change_compression_type(self, is_source_compr_disabled, new_type):
"""Check if volume type have different compression types
:param is_source_compr_disabled: from source
:param new_type: from target
:returns: boolean
"""
extra_specs = new_type['extra_specs']
is_target_compr_disabled = self.is_compression_disabled(extra_specs)
if is_target_compr_disabled == is_source_compr_disabled:
return False
else:
return True

View File

@ -0,0 +1,4 @@
---
features:
- |
Adding compression functionality to VMAX driver version 3.0.