VMAX driver - Add VMAX specific volume metadata to logs
Log VMAX specific metadata of a volume and version information, when debug is enabled. It enables the user to trace the vmax location of a volume after every operation i.e. storage group, masking view etc., as well as useful debug information like OS, VMAX and python versions. Change-Id: Ib727797da7624dec5662a35de1db05ad6dc866a0 Implements: blueprint vmax-metadata
This commit is contained in:
parent
512fd07124
commit
4662ead8c3
@ -16,6 +16,7 @@
|
|||||||
import ast
|
import ast
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import datetime
|
import datetime
|
||||||
|
import platform
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
@ -35,10 +36,12 @@ from cinder.tests.unit import fake_group
|
|||||||
from cinder.tests.unit import fake_snapshot
|
from cinder.tests.unit import fake_snapshot
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
from cinder.tests.unit import utils as test_utils
|
from cinder.tests.unit import utils as test_utils
|
||||||
|
from cinder import version as openstack_version
|
||||||
from cinder.volume.drivers.dell_emc.vmax import common
|
from cinder.volume.drivers.dell_emc.vmax import common
|
||||||
from cinder.volume.drivers.dell_emc.vmax import fc
|
from cinder.volume.drivers.dell_emc.vmax import fc
|
||||||
from cinder.volume.drivers.dell_emc.vmax import iscsi
|
from cinder.volume.drivers.dell_emc.vmax import iscsi
|
||||||
from cinder.volume.drivers.dell_emc.vmax import masking
|
from cinder.volume.drivers.dell_emc.vmax import masking
|
||||||
|
from cinder.volume.drivers.dell_emc.vmax import metadata
|
||||||
from cinder.volume.drivers.dell_emc.vmax import provision
|
from cinder.volume.drivers.dell_emc.vmax import provision
|
||||||
from cinder.volume.drivers.dell_emc.vmax import rest
|
from cinder.volume.drivers.dell_emc.vmax import rest
|
||||||
from cinder.volume.drivers.dell_emc.vmax import utils
|
from cinder.volume.drivers.dell_emc.vmax import utils
|
||||||
@ -909,6 +912,42 @@ class VMAXCommonData(object):
|
|||||||
"getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
|
"getDynamicRDFCapability": "RDF1_Capable", "RDFA": False},
|
||||||
"timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}}]
|
"timeFinderInfo": {"snapVXTgt": False, "snapVXSrc": False}}]
|
||||||
|
|
||||||
|
volume_info_dict = {
|
||||||
|
'volume_id': volume_id,
|
||||||
|
'service_level': 'Diamond',
|
||||||
|
'masking_view': 'OS-HostX-F-OS-fibre-PG-MV',
|
||||||
|
'host': fake_host,
|
||||||
|
'display_name': 'attach_vol_name',
|
||||||
|
'volume_updated_time': '2018-03-05 20:32:41',
|
||||||
|
'port_group': 'OS-fibre-PG',
|
||||||
|
'operation': 'attach', 'srp': 'SRP_1',
|
||||||
|
'initiator_group': 'OS-HostX-F-IG',
|
||||||
|
'serial_number': '000197800123',
|
||||||
|
'parent_storage_group': 'OS-HostX-F-OS-fibre-PG-SG',
|
||||||
|
'workload': 'DSS',
|
||||||
|
'child_storage_group': 'OS-HostX-SRP_1-DiamondDSS-OS-fibre-PG'}
|
||||||
|
|
||||||
|
data_dict = {volume_id: volume_info_dict}
|
||||||
|
platform = 'Linux-4.4.0-104-generic-x86_64-with-Ubuntu-16.04-xenial'
|
||||||
|
unisphere_version = u'V9.0.0.1'
|
||||||
|
openstack_release = '12.0.0.0b3.dev401'
|
||||||
|
openstack_version = '12.0.0'
|
||||||
|
python_version = '2.7.12'
|
||||||
|
vmax_driver_version = '3.1'
|
||||||
|
vmax_firmware_version = u'5977.1125.1125'
|
||||||
|
vmax_model = u'VMAX250F'
|
||||||
|
|
||||||
|
version_dict = {
|
||||||
|
'unisphere_version': unisphere_version,
|
||||||
|
'openstack_release': openstack_release,
|
||||||
|
'openstack_version': openstack_version,
|
||||||
|
'python_version': python_version,
|
||||||
|
'vmax_driver_version': vmax_driver_version,
|
||||||
|
'platform': platform,
|
||||||
|
'vmax_firmware_version': vmax_firmware_version,
|
||||||
|
'serial_number': array,
|
||||||
|
'vmax_model': vmax_model}
|
||||||
|
|
||||||
|
|
||||||
class FakeLookupService(object):
|
class FakeLookupService(object):
|
||||||
def get_device_mapping_from_network(self, initiator_wwns, target_wwns):
|
def get_device_mapping_from_network(self, initiator_wwns, target_wwns):
|
||||||
@ -7197,9 +7236,9 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
@mock.patch.object(masking.VMAXMasking, 'add_volume_to_storage_group')
|
@mock.patch.object(masking.VMAXMasking, 'add_volume_to_storage_group')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon, '_replicate_volume',
|
common.VMAXCommon, '_replicate_volume',
|
||||||
return_value={
|
return_value=({
|
||||||
'replication_driver_data':
|
'replication_driver_data':
|
||||||
VMAXCommonData.test_volume.replication_driver_data})
|
VMAXCommonData.test_volume.replication_driver_data}, {}))
|
||||||
def test_create_replicated_volume(self, mock_rep, mock_add, mock_match,
|
def test_create_replicated_volume(self, mock_rep, mock_add, mock_match,
|
||||||
mock_check, mock_get, mock_cg):
|
mock_check, mock_get, mock_cg):
|
||||||
extra_specs = deepcopy(self.extra_specs)
|
extra_specs = deepcopy(self.extra_specs)
|
||||||
@ -7219,7 +7258,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
extra_specs = deepcopy(self.extra_specs)
|
extra_specs = deepcopy(self.extra_specs)
|
||||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||||
with mock.patch.object(self.common, '_replicate_volume',
|
with mock.patch.object(self.common, '_replicate_volume',
|
||||||
return_value={}) as mock_rep:
|
return_value=({}, {})) as mock_rep:
|
||||||
self.common.create_cloned_volume(
|
self.common.create_cloned_volume(
|
||||||
self.data.test_clone_volume, self.data.test_volume)
|
self.data.test_clone_volume, self.data.test_volume)
|
||||||
volume_dict = self.data.provider_location
|
volume_dict = self.data.provider_location
|
||||||
@ -7231,7 +7270,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
extra_specs = deepcopy(self.extra_specs)
|
extra_specs = deepcopy(self.extra_specs)
|
||||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||||
with mock.patch.object(self.common, '_replicate_volume',
|
with mock.patch.object(self.common, '_replicate_volume',
|
||||||
return_value={}) as mock_rep:
|
return_value=({}, {})) as mock_rep:
|
||||||
self.common.create_volume_from_snapshot(
|
self.common.create_volume_from_snapshot(
|
||||||
self.data.test_clone_volume, self.data.test_snapshot)
|
self.data.test_clone_volume, self.data.test_snapshot)
|
||||||
volume_dict = self.data.provider_location
|
volume_dict = self.data.provider_location
|
||||||
@ -7245,7 +7284,8 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
volume_dict = self.data.provider_location
|
volume_dict = self.data.provider_location
|
||||||
rs_enabled = fields.ReplicationStatus.ENABLED
|
rs_enabled = fields.ReplicationStatus.ENABLED
|
||||||
with mock.patch.object(self.common, 'setup_volume_replication',
|
with mock.patch.object(self.common, 'setup_volume_replication',
|
||||||
return_value=(rs_enabled, {})) as mock_setup:
|
return_value=(rs_enabled, {}, {}))\
|
||||||
|
as mock_setup:
|
||||||
self.common._replicate_volume(
|
self.common._replicate_volume(
|
||||||
self.data.test_volume, "1", volume_dict, self.extra_specs)
|
self.data.test_volume, "1", volume_dict, self.extra_specs)
|
||||||
mock_setup.assert_called_once_with(
|
mock_setup.assert_called_once_with(
|
||||||
@ -7396,7 +7436,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
|
|
||||||
@mock.patch.object(common.VMAXCommon,
|
@mock.patch.object(common.VMAXCommon,
|
||||||
'_replicate_volume',
|
'_replicate_volume',
|
||||||
return_value={})
|
return_value=({}, {}))
|
||||||
def test_manage_existing_is_replicated(self, mock_rep):
|
def test_manage_existing_is_replicated(self, mock_rep):
|
||||||
extra_specs = deepcopy(self.extra_specs)
|
extra_specs = deepcopy(self.extra_specs)
|
||||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||||
@ -7414,7 +7454,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
|
|
||||||
@mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members')
|
@mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members')
|
||||||
def test_setup_volume_replication(self, mock_rm):
|
def test_setup_volume_replication(self, mock_rm):
|
||||||
rep_status, rep_data = self.common.setup_volume_replication(
|
rep_status, rep_data, __ = self.common.setup_volume_replication(
|
||||||
self.data.array, self.data.test_volume, self.data.device_id,
|
self.data.array, self.data.test_volume, self.data.device_id,
|
||||||
self.extra_specs)
|
self.extra_specs)
|
||||||
self.assertEqual(fields.ReplicationStatus.ENABLED, rep_status)
|
self.assertEqual(fields.ReplicationStatus.ENABLED, rep_status)
|
||||||
@ -7424,7 +7464,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
@mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members')
|
@mock.patch.object(masking.VMAXMasking, 'remove_and_reset_members')
|
||||||
@mock.patch.object(common.VMAXCommon, '_create_volume')
|
@mock.patch.object(common.VMAXCommon, '_create_volume')
|
||||||
def test_setup_volume_replication_target(self, mock_create, mock_rm):
|
def test_setup_volume_replication_target(self, mock_create, mock_rm):
|
||||||
rep_status, rep_data = self.common.setup_volume_replication(
|
rep_status, rep_data, __ = self.common.setup_volume_replication(
|
||||||
self.data.array, self.data.test_volume, self.data.device_id,
|
self.data.array, self.data.test_volume, self.data.device_id,
|
||||||
self.extra_specs, self.data.device_id2)
|
self.extra_specs, self.data.device_id2)
|
||||||
self.assertEqual(fields.ReplicationStatus.ENABLED, rep_status)
|
self.assertEqual(fields.ReplicationStatus.ENABLED, rep_status)
|
||||||
@ -7911,7 +7951,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
def test_setup_volume_replication_async(self, mock_rm, mock_add):
|
def test_setup_volume_replication_async(self, mock_rm, mock_add):
|
||||||
extra_specs = deepcopy(self.extra_specs)
|
extra_specs = deepcopy(self.extra_specs)
|
||||||
extra_specs['rep_mode'] = utils.REP_ASYNC
|
extra_specs['rep_mode'] = utils.REP_ASYNC
|
||||||
rep_status, rep_data = (
|
rep_status, rep_data, __ = (
|
||||||
self.async_driver.common.setup_volume_replication(
|
self.async_driver.common.setup_volume_replication(
|
||||||
self.data.array, self.data.test_volume,
|
self.data.array, self.data.test_volume,
|
||||||
self.data.device_id, extra_specs))
|
self.data.device_id, extra_specs))
|
||||||
@ -7936,7 +7976,8 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
@mock.patch.object(common.VMAXCommon, '_retype_remote_volume',
|
@mock.patch.object(common.VMAXCommon, '_retype_remote_volume',
|
||||||
return_value=True)
|
return_value=True)
|
||||||
@mock.patch.object(common.VMAXCommon, 'setup_volume_replication',
|
@mock.patch.object(common.VMAXCommon, 'setup_volume_replication',
|
||||||
return_value=VMAXCommonData.provider_location2)
|
return_value=(
|
||||||
|
'', VMAXCommonData.provider_location2, ''))
|
||||||
@mock.patch.object(common.VMAXCommon,
|
@mock.patch.object(common.VMAXCommon,
|
||||||
'_remove_vol_and_cleanup_replication')
|
'_remove_vol_and_cleanup_replication')
|
||||||
@mock.patch.object(utils.VMAXUtils, 'is_replication_enabled',
|
@mock.patch.object(utils.VMAXUtils, 'is_replication_enabled',
|
||||||
@ -7970,3 +8011,230 @@ class VMAXCommonReplicationTest(test.TestCase):
|
|||||||
self.data.test_volume.name, utils.REP_SYNC,
|
self.data.test_volume.name, utils.REP_SYNC,
|
||||||
True, self.data.extra_specs)
|
True, self.data.extra_specs)
|
||||||
mock_retype.assert_called_once()
|
mock_retype.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
class VMAXVolumeMetadataNoDebugTest(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = VMAXCommonData()
|
||||||
|
|
||||||
|
super(VMAXVolumeMetadataNoDebugTest, self).setUp()
|
||||||
|
is_debug = False
|
||||||
|
self.volume_metadata = metadata.VMAXVolumeMetadata(
|
||||||
|
rest.VMAXRest, '3.1', is_debug)
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata, '_fill_volume_trace_dict',
|
||||||
|
return_value={})
|
||||||
|
def test_gather_volume_info(self, mock_fvtd):
|
||||||
|
self.volume_metadata.gather_volume_info(
|
||||||
|
self.data.volume_id, 'create', False, volume_size=1)
|
||||||
|
mock_fvtd.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class VMAXVolumeMetadataDebugTest(test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = VMAXCommonData()
|
||||||
|
|
||||||
|
super(VMAXVolumeMetadataDebugTest, self).setUp()
|
||||||
|
is_debug = True
|
||||||
|
self.volume_metadata = metadata.VMAXVolumeMetadata(
|
||||||
|
rest.VMAXRest, '3.1', is_debug)
|
||||||
|
self.utils = self.volume_metadata.utils
|
||||||
|
self.rest = self.volume_metadata.rest
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata, '_fill_volume_trace_dict',
|
||||||
|
return_value={})
|
||||||
|
def test_gather_volume_info(self, mock_fvtd):
|
||||||
|
self.volume_metadata.gather_volume_info(
|
||||||
|
self.data.volume_id, 'create', False, volume_size=1)
|
||||||
|
mock_fvtd.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_attach_info(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_attach_info(
|
||||||
|
self.data.test_volume, self.data.extra_specs,
|
||||||
|
self.data.masking_view_dict, self.data.fake_host,
|
||||||
|
False, False)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_create_volume(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_create_volume(
|
||||||
|
self.data.device_id, self.data.test_volume, "test_group",
|
||||||
|
"test_group_id", self.data.extra_specs, {}, 'create', None)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_manage_existing(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_manage_existing(
|
||||||
|
self.data.test_volume, {}, self.data.device_id,
|
||||||
|
self.data.extra_specs)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_failover_volume(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_failover_volume(
|
||||||
|
self.data.test_volume, self.data.device_id2,
|
||||||
|
self.data.remote_array, self.data.rdf_group_name,
|
||||||
|
self.data.device_id, self.data.array,
|
||||||
|
self.data.extra_specs, True, None,
|
||||||
|
fields.ReplicationStatus.FAILED_OVER, utils.REP_SYNC)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_modify_group(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_modify_group(
|
||||||
|
"test_group", "test_group_id", [self.data.test_volume],
|
||||||
|
[], self.data.array)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_extend_info(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_extend_info(
|
||||||
|
self.data.test_volume, 5, self.data.device_id,
|
||||||
|
self.data.extra_specs, self.data.array)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_detach_info(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_detach_info(
|
||||||
|
self.data.test_volume, self.data.extra_specs, self.data.device_id,
|
||||||
|
None, None)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_snapshot_info(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_snapshot_info(
|
||||||
|
self.data.test_volume, self.data.extra_specs, 'createSnapshot',
|
||||||
|
'ss-test-vol')
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(metadata.VMAXVolumeMetadata,
|
||||||
|
'update_volume_info_metadata',
|
||||||
|
return_value={})
|
||||||
|
def test_capture_retype_info(self, mock_uvim):
|
||||||
|
self.volume_metadata.capture_retype_info(
|
||||||
|
self.data.test_volume.id, 20, self.data.device_id, self.data.array,
|
||||||
|
self.data.srp, self.data.slo, self.data.workload,
|
||||||
|
self.data.storagegroup_name_target, False, None,
|
||||||
|
False)
|
||||||
|
mock_uvim.assert_called_once()
|
||||||
|
|
||||||
|
def test_update_volume_info_metadata(self):
|
||||||
|
volume_metadata = self.volume_metadata.update_volume_info_metadata(
|
||||||
|
self.data.data_dict, self.data.version_dict)
|
||||||
|
self.assertEqual('2.7.12', volume_metadata['python_version'])
|
||||||
|
self.assertEqual('VMAX250F', volume_metadata['vmax_model'])
|
||||||
|
self.assertEqual('DSS', volume_metadata['workload'])
|
||||||
|
self.assertEqual('OS-fibre-PG', volume_metadata['port_group'])
|
||||||
|
|
||||||
|
def test_fill_volume_trace_dict(self):
|
||||||
|
datadict = {}
|
||||||
|
volume_trace_dict = {}
|
||||||
|
volume_key_value = {}
|
||||||
|
result_dict = {'operation': 'create',
|
||||||
|
'volume_id': self.data.test_volume.id}
|
||||||
|
volume_metadata = self.volume_metadata._fill_volume_trace_dict(
|
||||||
|
self.data.test_volume.id, 'create', False, target_name=None,
|
||||||
|
datadict=datadict, volume_key_value=volume_key_value,
|
||||||
|
volume_trace_dict=volume_trace_dict)
|
||||||
|
self.assertEqual(result_dict, volume_metadata)
|
||||||
|
|
||||||
|
def test_fill_volume_trace_dict_multi_attach(self):
|
||||||
|
mv_list = ['mv1', 'mv2', 'mv3']
|
||||||
|
sg_list = ['sg1', 'sg2', 'sg3']
|
||||||
|
datadict = {}
|
||||||
|
volume_trace_dict = {}
|
||||||
|
volume_key_value = {}
|
||||||
|
result_dict = {'masking_view_1': 'mv1',
|
||||||
|
'masking_view_2': 'mv2',
|
||||||
|
'masking_view_3': 'mv3',
|
||||||
|
'operation': 'attach',
|
||||||
|
'storage_group_1': 'sg1',
|
||||||
|
'storage_group_2': 'sg2',
|
||||||
|
'storage_group_3': 'sg3',
|
||||||
|
'volume_id': self.data.test_volume.id}
|
||||||
|
volume_metadata = self.volume_metadata._fill_volume_trace_dict(
|
||||||
|
self.data.test_volume.id, 'attach', False, target_name=None,
|
||||||
|
datadict=datadict, volume_trace_dict=volume_trace_dict,
|
||||||
|
volume_key_value=volume_key_value, mv_list=mv_list,
|
||||||
|
sg_list=sg_list)
|
||||||
|
self.assertEqual(result_dict, volume_metadata)
|
||||||
|
|
||||||
|
@mock.patch.object(utils.VMAXUtils, 'merge_dicts',
|
||||||
|
return_value={})
|
||||||
|
def test_consolidate_volume_trace_list(self, mock_m2d):
|
||||||
|
self.volume_metadata.volume_trace_list = [self.data.data_dict]
|
||||||
|
volume_trace_dict = {'volume_updated_time': '2018-03-06 16:51:40',
|
||||||
|
'operation': 'delete',
|
||||||
|
'volume_id': self.data.volume_id}
|
||||||
|
volume_key_value = {self.data.volume_id: volume_trace_dict}
|
||||||
|
self.volume_metadata._consolidate_volume_trace_list(
|
||||||
|
self.data.volume_id, volume_trace_dict, volume_key_value)
|
||||||
|
mock_m2d.assert_called_once()
|
||||||
|
|
||||||
|
def test_merge_dicts_multiple(self):
|
||||||
|
d1 = {'a': 1, 'b': 2}
|
||||||
|
d2 = {'c': 3, 'd': 4}
|
||||||
|
d3 = {'e': 5, 'f': 6}
|
||||||
|
res_d = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
|
||||||
|
result_dict = self.utils.merge_dicts(
|
||||||
|
d1, d2, d3)
|
||||||
|
self.assertEqual(res_d, result_dict)
|
||||||
|
|
||||||
|
def test_merge_dicts_multiple_2(self):
|
||||||
|
d1 = {'a': 1, 'b': 2}
|
||||||
|
d2 = {'b': 3, 'd': 4}
|
||||||
|
d3 = {'d': 5, 'e': 6}
|
||||||
|
res_d = {'a': 1, 'b': 2, 'd': 4, 'e': 6}
|
||||||
|
result_dict = self.utils.merge_dicts(
|
||||||
|
d1, d2, d3)
|
||||||
|
self.assertEqual(res_d, result_dict)
|
||||||
|
|
||||||
|
def test_merge_dicts(self):
|
||||||
|
self.volume_metadata.volume_trace_list = [self.data.data_dict]
|
||||||
|
volume_trace_dict = {'volume_updated_time': '2018-03-06 16:51:40',
|
||||||
|
'operation': 'delete',
|
||||||
|
'volume_id': self.data.volume_id}
|
||||||
|
result_dict = self.utils.merge_dicts(
|
||||||
|
volume_trace_dict, self.data.volume_info_dict)
|
||||||
|
self.assertEqual('delete', result_dict['operation'])
|
||||||
|
self.assertEqual(
|
||||||
|
'2018-03-06 16:51:40', result_dict['volume_updated_time'])
|
||||||
|
self.assertEqual('OS-fibre-PG', result_dict['port_group'])
|
||||||
|
|
||||||
|
@mock.patch.object(platform, 'platform',
|
||||||
|
return_value=VMAXCommonData.platform)
|
||||||
|
@mock.patch.object(platform, 'python_version',
|
||||||
|
return_value=VMAXCommonData.python_version)
|
||||||
|
@mock.patch.object(openstack_version.version_info, 'version_string',
|
||||||
|
return_value=VMAXCommonData.openstack_version)
|
||||||
|
@mock.patch.object(openstack_version.version_info, 'release_string',
|
||||||
|
return_value=VMAXCommonData.openstack_release)
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'get_unisphere_version',
|
||||||
|
return_value={
|
||||||
|
'version': VMAXCommonData.unisphere_version})
|
||||||
|
@mock.patch.object(rest.VMAXRest, 'get_array_serial',
|
||||||
|
return_value={
|
||||||
|
'ucode': VMAXCommonData.vmax_firmware_version,
|
||||||
|
'model': VMAXCommonData.vmax_model})
|
||||||
|
def test_gather_version_info(
|
||||||
|
self, mock_vi, mock_ur, mock_or, mock_ov, mock_pv, mock_p):
|
||||||
|
self.volume_metadata.gather_version_info(self.data.array)
|
||||||
|
self.assertEqual(
|
||||||
|
self.data.version_dict, self.volume_metadata.version_dict)
|
||||||
|
@ -31,6 +31,7 @@ from cinder.i18n import _
|
|||||||
from cinder.objects import fields
|
from cinder.objects import fields
|
||||||
from cinder.volume import configuration
|
from cinder.volume import configuration
|
||||||
from cinder.volume.drivers.dell_emc.vmax import masking
|
from cinder.volume.drivers.dell_emc.vmax import masking
|
||||||
|
from cinder.volume.drivers.dell_emc.vmax import metadata as volume_metadata
|
||||||
from cinder.volume.drivers.dell_emc.vmax import provision
|
from cinder.volume.drivers.dell_emc.vmax import provision
|
||||||
from cinder.volume.drivers.dell_emc.vmax import rest
|
from cinder.volume.drivers.dell_emc.vmax import rest
|
||||||
from cinder.volume.drivers.dell_emc.vmax import utils
|
from cinder.volume.drivers.dell_emc.vmax import utils
|
||||||
@ -118,6 +119,8 @@ class VMAXCommon(object):
|
|||||||
self.masking = masking.VMAXMasking(prtcl, self.rest)
|
self.masking = masking.VMAXMasking(prtcl, self.rest)
|
||||||
self.provision = provision.VMAXProvision(self.rest)
|
self.provision = provision.VMAXProvision(self.rest)
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.volume_metadata = volume_metadata.VMAXVolumeMetadata(
|
||||||
|
self.rest, version, LOG.isEnabledFor(logging.DEBUG))
|
||||||
# replication
|
# replication
|
||||||
self.replication_enabled = False
|
self.replication_enabled = False
|
||||||
self.extend_replicated_vol = False
|
self.extend_replicated_vol = False
|
||||||
@ -126,6 +129,7 @@ class VMAXCommon(object):
|
|||||||
self.failover = False
|
self.failover = False
|
||||||
self._get_replication_info()
|
self._get_replication_info()
|
||||||
self._gather_info()
|
self._gather_info()
|
||||||
|
self.version_dict = {}
|
||||||
|
|
||||||
def _gather_info(self):
|
def _gather_info(self):
|
||||||
"""Gather the relevant information for update_volume_stats."""
|
"""Gather the relevant information for update_volume_stats."""
|
||||||
@ -249,8 +253,11 @@ class VMAXCommon(object):
|
|||||||
:returns: model_update - dict
|
:returns: model_update - dict
|
||||||
"""
|
"""
|
||||||
model_update = {}
|
model_update = {}
|
||||||
|
rep_info_dict = {}
|
||||||
rep_driver_data = {}
|
rep_driver_data = {}
|
||||||
volume_id = volume.id
|
volume_id = volume.id
|
||||||
|
group_name = None
|
||||||
|
group_id = None
|
||||||
extra_specs = self._initial_setup(volume)
|
extra_specs = self._initial_setup(volume)
|
||||||
if 'qos' in extra_specs:
|
if 'qos' in extra_specs:
|
||||||
del extra_specs['qos']
|
del extra_specs['qos']
|
||||||
@ -264,8 +271,8 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
# Set-up volume replication, if enabled
|
# Set-up volume replication, if enabled
|
||||||
if self.utils.is_replication_enabled(extra_specs):
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
rep_update = self._replicate_volume(volume, volume_name,
|
rep_update, rep_info_dict = self._replicate_volume(
|
||||||
volume_dict, extra_specs)
|
volume, volume_name, volume_dict, extra_specs)
|
||||||
rep_driver_data = rep_update['replication_driver_data']
|
rep_driver_data = rep_update['replication_driver_data']
|
||||||
model_update.update(rep_update)
|
model_update.update(rep_update)
|
||||||
|
|
||||||
@ -273,16 +280,20 @@ class VMAXCommon(object):
|
|||||||
if volume.group_id is not None:
|
if volume.group_id is not None:
|
||||||
if (volume_utils.is_group_a_cg_snapshot_type(volume.group)
|
if (volume_utils.is_group_a_cg_snapshot_type(volume.group)
|
||||||
or volume.group.is_replicated):
|
or volume.group.is_replicated):
|
||||||
LOG.debug("Adding volume %(vol_id)s to group %(grp_id)s",
|
group_id = volume.group_id
|
||||||
{'vol_id': volume.id, 'grp_id': volume.group_id})
|
group_name = self._add_new_volume_to_volume_group(
|
||||||
self._add_new_volume_to_volume_group(
|
|
||||||
volume, volume_dict['device_id'], volume_name,
|
volume, volume_dict['device_id'], volume_name,
|
||||||
extra_specs, rep_driver_data)
|
extra_specs, rep_driver_data)
|
||||||
|
model_update.update(
|
||||||
|
{'provider_location': six.text_type(volume_dict)})
|
||||||
|
|
||||||
|
self.volume_metadata.capture_create_volume(
|
||||||
|
volume_dict['device_id'], volume, group_name, group_id,
|
||||||
|
extra_specs, rep_info_dict, 'create', None)
|
||||||
|
|
||||||
LOG.info("Leaving create_volume: %(name)s. Volume dict: %(dict)s.",
|
LOG.info("Leaving create_volume: %(name)s. Volume dict: %(dict)s.",
|
||||||
{'name': volume_name, 'dict': volume_dict})
|
{'name': volume_name, 'dict': volume_dict})
|
||||||
model_update.update(
|
|
||||||
{'provider_location': six.text_type(volume_dict)})
|
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def _add_new_volume_to_volume_group(self, volume, device_id, volume_name,
|
def _add_new_volume_to_volume_group(self, volume, device_id, volume_name,
|
||||||
@ -295,6 +306,7 @@ class VMAXCommon(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
|
||||||
:param rep_driver_data: the replication driver data, optional
|
:param rep_driver_data: the replication driver data, optional
|
||||||
|
:returns: group_name string
|
||||||
"""
|
"""
|
||||||
self.utils.check_replication_matched(volume, extra_specs)
|
self.utils.check_replication_matched(volume, extra_specs)
|
||||||
group_name = self.provision.get_or_create_volume_group(
|
group_name = self.provision.get_or_create_volume_group(
|
||||||
@ -306,6 +318,7 @@ class VMAXCommon(object):
|
|||||||
if volume.group.is_replicated:
|
if volume.group.is_replicated:
|
||||||
self.masking.add_remote_vols_to_volume_group(
|
self.masking.add_remote_vols_to_volume_group(
|
||||||
volume, volume.group, extra_specs, rep_driver_data)
|
volume, volume.group, extra_specs, rep_driver_data)
|
||||||
|
return group_name
|
||||||
|
|
||||||
def create_volume_from_snapshot(self, volume, snapshot):
|
def create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Creates a volume from a snapshot.
|
"""Creates a volume from a snapshot.
|
||||||
@ -316,7 +329,7 @@ class VMAXCommon(object):
|
|||||||
:raises: VolumeBackendAPIException:
|
:raises: VolumeBackendAPIException:
|
||||||
"""
|
"""
|
||||||
LOG.debug("Entering create_volume_from_snapshot.")
|
LOG.debug("Entering create_volume_from_snapshot.")
|
||||||
model_update = {}
|
model_update, rep_info_dict = {}, {}
|
||||||
extra_specs = self._initial_setup(volume)
|
extra_specs = self._initial_setup(volume)
|
||||||
|
|
||||||
# Check if legacy snapshot
|
# Check if legacy snapshot
|
||||||
@ -330,12 +343,19 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
# Set-up volume replication, if enabled
|
# Set-up volume replication, if enabled
|
||||||
if self.utils.is_replication_enabled(extra_specs):
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
rep_update = self._replicate_volume(volume, snapshot['name'],
|
rep_update, rep_info_dict = (
|
||||||
clone_dict, extra_specs)
|
self._replicate_volume(
|
||||||
|
volume, snapshot['name'], clone_dict, extra_specs))
|
||||||
model_update.update(rep_update)
|
model_update.update(rep_update)
|
||||||
|
|
||||||
model_update.update(
|
model_update.update(
|
||||||
{'provider_location': six.text_type(clone_dict)})
|
{'provider_location': six.text_type(clone_dict)})
|
||||||
|
|
||||||
|
self.volume_metadata.capture_create_volume(
|
||||||
|
clone_dict['device_id'], volume, None, None,
|
||||||
|
extra_specs, rep_info_dict, 'createFromSnapshot',
|
||||||
|
snapshot.id)
|
||||||
|
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def create_cloned_volume(self, clone_volume, source_volume):
|
def create_cloned_volume(self, clone_volume, source_volume):
|
||||||
@ -345,19 +365,23 @@ class VMAXCommon(object):
|
|||||||
:param source_volume: volume object
|
:param source_volume: volume object
|
||||||
:returns: model_update, dict
|
:returns: model_update, dict
|
||||||
"""
|
"""
|
||||||
model_update = {}
|
model_update, rep_info_dict = {}, {}
|
||||||
extra_specs = self._initial_setup(clone_volume)
|
extra_specs = self._initial_setup(clone_volume)
|
||||||
clone_dict = self._create_cloned_volume(clone_volume, source_volume,
|
clone_dict = self._create_cloned_volume(clone_volume, source_volume,
|
||||||
extra_specs)
|
extra_specs)
|
||||||
|
|
||||||
# Set-up volume replication, if enabled
|
# Set-up volume replication, if enabled
|
||||||
if self.utils.is_replication_enabled(extra_specs):
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
rep_update = self._replicate_volume(
|
rep_update, rep_info_dict = self._replicate_volume(
|
||||||
clone_volume, clone_volume.name, clone_dict, extra_specs)
|
clone_volume, clone_volume.name, clone_dict, extra_specs)
|
||||||
model_update.update(rep_update)
|
model_update.update(rep_update)
|
||||||
|
|
||||||
model_update.update(
|
model_update.update(
|
||||||
{'provider_location': six.text_type(clone_dict)})
|
{'provider_location': six.text_type(clone_dict)})
|
||||||
|
self.volume_metadata.capture_create_volume(
|
||||||
|
clone_dict['device_id'], clone_volume, None, None,
|
||||||
|
extra_specs, rep_info_dict, 'createFromVolume',
|
||||||
|
None)
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def _replicate_volume(self, volume, volume_name, volume_dict, extra_specs,
|
def _replicate_volume(self, volume, volume_name, volume_dict, extra_specs,
|
||||||
@ -370,12 +394,12 @@ class VMAXCommon(object):
|
|||||||
:param extra_specs: the extra specifications
|
:param extra_specs: the extra specifications
|
||||||
:param delete_src: flag to indicate if source should be deleted on
|
:param delete_src: flag to indicate if source should be deleted on
|
||||||
if replication fails
|
if replication fails
|
||||||
:returns: replication model_update
|
:returns: replication model_update, rep_info_dict
|
||||||
"""
|
"""
|
||||||
array = volume_dict['array']
|
array = volume_dict['array']
|
||||||
try:
|
try:
|
||||||
device_id = volume_dict['device_id']
|
device_id = volume_dict['device_id']
|
||||||
replication_status, replication_driver_data = (
|
replication_status, replication_driver_data, rep_info_dict = (
|
||||||
self.setup_volume_replication(
|
self.setup_volume_replication(
|
||||||
array, volume, device_id, extra_specs))
|
array, volume, device_id, extra_specs))
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -385,7 +409,7 @@ class VMAXCommon(object):
|
|||||||
raise
|
raise
|
||||||
return ({'replication_status': replication_status,
|
return ({'replication_status': replication_status,
|
||||||
'replication_driver_data': six.text_type(
|
'replication_driver_data': six.text_type(
|
||||||
replication_driver_data)})
|
replication_driver_data)}, rep_info_dict)
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
"""Deletes a EMC(VMAX) volume.
|
"""Deletes a EMC(VMAX) volume.
|
||||||
@ -408,6 +432,8 @@ class VMAXCommon(object):
|
|||||||
extra_specs = self._initial_setup(volume)
|
extra_specs = self._initial_setup(volume)
|
||||||
snapshot_dict = self._create_cloned_volume(
|
snapshot_dict = self._create_cloned_volume(
|
||||||
snapshot, volume, extra_specs, is_snapshot=True)
|
snapshot, volume, extra_specs, is_snapshot=True)
|
||||||
|
self.volume_metadata.capture_snapshot_info(
|
||||||
|
volume, extra_specs, 'createSnapshot', snapshot_dict['snap_name'])
|
||||||
model_update = {'provider_location': six.text_type(snapshot_dict)}
|
model_update = {'provider_location': six.text_type(snapshot_dict)}
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
@ -443,6 +469,8 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
LOG.info("Leaving delete_snapshot: %(ssname)s.",
|
LOG.info("Leaving delete_snapshot: %(ssname)s.",
|
||||||
{'ssname': snap_name})
|
{'ssname': snap_name})
|
||||||
|
self.volume_metadata.capture_snapshot_info(
|
||||||
|
volume, extra_specs, 'deleteSnapshot', None)
|
||||||
|
|
||||||
def _remove_members(self, array, volume, device_id,
|
def _remove_members(self, array, volume, device_id,
|
||||||
extra_specs, connector, is_multiattach,
|
extra_specs, connector, is_multiattach,
|
||||||
@ -474,6 +502,7 @@ class VMAXCommon(object):
|
|||||||
:param volume: the volume Object
|
:param volume: the volume Object
|
||||||
:param connector: the connector Object
|
:param connector: the connector Object
|
||||||
"""
|
"""
|
||||||
|
mv_list, sg_list = None, None
|
||||||
extra_specs = self._initial_setup(volume)
|
extra_specs = self._initial_setup(volume)
|
||||||
if 'qos' in extra_specs:
|
if 'qos' in extra_specs:
|
||||||
del extra_specs['qos']
|
del extra_specs['qos']
|
||||||
@ -535,6 +564,14 @@ class VMAXCommon(object):
|
|||||||
self.masking.attempt_ig_cleanup(
|
self.masking.attempt_ig_cleanup(
|
||||||
connector, self.protocol, rep_extra_specs[utils.ARRAY],
|
connector, self.protocol, rep_extra_specs[utils.ARRAY],
|
||||||
True)
|
True)
|
||||||
|
if is_multiattach and LOG.isEnabledFor(logging.DEBUG):
|
||||||
|
mv_list, sg_list = (
|
||||||
|
self._get_mvs_and_sgs_from_volume(
|
||||||
|
extra_specs[utils.ARRAY],
|
||||||
|
device_info['device_id']))
|
||||||
|
self.volume_metadata.capture_detach_info(
|
||||||
|
volume, extra_specs, device_info['device_id'], mv_list,
|
||||||
|
sg_list)
|
||||||
|
|
||||||
def initialize_connection(self, volume, connector):
|
def initialize_connection(self, volume, connector):
|
||||||
"""Initializes the connection and returns device and connection info.
|
"""Initializes the connection and returns device and connection info.
|
||||||
@ -640,6 +677,17 @@ class VMAXCommon(object):
|
|||||||
self._find_ip_and_iqns(
|
self._find_ip_and_iqns(
|
||||||
rep_extra_specs[utils.ARRAY], remote_port_group))
|
rep_extra_specs[utils.ARRAY], remote_port_group))
|
||||||
device_info_dict['is_multipath'] = is_multipath
|
device_info_dict['is_multipath'] = is_multipath
|
||||||
|
|
||||||
|
if is_multiattach and LOG.isEnabledFor(logging.DEBUG):
|
||||||
|
masking_view_dict['mv_list'], masking_view_dict['sg_list'] = (
|
||||||
|
self._get_mvs_and_sgs_from_volume(
|
||||||
|
extra_specs[utils.ARRAY],
|
||||||
|
masking_view_dict[utils.DEVICE_ID]))
|
||||||
|
|
||||||
|
self.volume_metadata.capture_attach_info(
|
||||||
|
volume, extra_specs, masking_view_dict, connector['host'],
|
||||||
|
is_multipath, is_multiattach)
|
||||||
|
|
||||||
return device_info_dict
|
return device_info_dict
|
||||||
|
|
||||||
def _attach_metro_volume(self, volume, connector, is_multiattach,
|
def _attach_metro_volume(self, volume, connector, is_multiattach,
|
||||||
@ -772,6 +820,9 @@ class VMAXCommon(object):
|
|||||||
self.provision.extend_volume(
|
self.provision.extend_volume(
|
||||||
array, device_id, new_size, extra_specs)
|
array, device_id, new_size, extra_specs)
|
||||||
|
|
||||||
|
self.volume_metadata.capture_extend_info(
|
||||||
|
volume, new_size, device_id, extra_specs, array)
|
||||||
|
|
||||||
LOG.debug("Leaving extend_volume: %(volume_name)s. ",
|
LOG.debug("Leaving extend_volume: %(volume_name)s. ",
|
||||||
{'volume_name': volume_name})
|
{'volume_name': volume_name})
|
||||||
|
|
||||||
@ -1095,21 +1146,33 @@ class VMAXCommon(object):
|
|||||||
"""
|
"""
|
||||||
LOG.debug("Getting masking views from volume")
|
LOG.debug("Getting masking views from volume")
|
||||||
host_maskingview_list, all_masking_view_list = [], []
|
host_maskingview_list, all_masking_view_list = [], []
|
||||||
storage_group_list = self.rest.get_storage_groups_from_volume(
|
|
||||||
array, device_id)
|
|
||||||
host_compare = True if host else False
|
host_compare = True if host else False
|
||||||
for sg in storage_group_list:
|
mvs, __ = self._get_mvs_and_sgs_from_volume(array, device_id)
|
||||||
mvs = self.rest.get_masking_views_from_storage_group(
|
for mv in mvs:
|
||||||
array, sg)
|
all_masking_view_list.append(mv)
|
||||||
for mv in mvs:
|
if host_compare:
|
||||||
all_masking_view_list.append(mv)
|
if host.lower() in mv.lower():
|
||||||
if host_compare:
|
host_maskingview_list.append(mv)
|
||||||
if host.lower() in mv.lower():
|
|
||||||
host_maskingview_list.append(mv)
|
|
||||||
maskingview_list = (host_maskingview_list if host_compare else
|
maskingview_list = (host_maskingview_list if host_compare else
|
||||||
all_masking_view_list)
|
all_masking_view_list)
|
||||||
return maskingview_list, all_masking_view_list
|
return maskingview_list, all_masking_view_list
|
||||||
|
|
||||||
|
def _get_mvs_and_sgs_from_volume(self, array, device_id):
|
||||||
|
"""Helper function to retrieve masking views and storage groups.
|
||||||
|
|
||||||
|
:param array: array serial number
|
||||||
|
:param device_id: the volume device id
|
||||||
|
:returns: masking view list, storage group list
|
||||||
|
"""
|
||||||
|
final_masking_view_list = []
|
||||||
|
storage_group_list = self.rest.get_storage_groups_from_volume(
|
||||||
|
array, device_id)
|
||||||
|
for sg in storage_group_list:
|
||||||
|
masking_view_list = self.rest.get_masking_views_from_storage_group(
|
||||||
|
array, sg)
|
||||||
|
final_masking_view_list.extend(masking_view_list)
|
||||||
|
return final_masking_view_list, storage_group_list
|
||||||
|
|
||||||
def _initial_setup(self, volume, volume_type_id=None):
|
def _initial_setup(self, volume, volume_type_id=None):
|
||||||
"""Necessary setup to accumulate the relevant information.
|
"""Necessary setup to accumulate the relevant information.
|
||||||
|
|
||||||
@ -1372,7 +1435,6 @@ class VMAXCommon(object):
|
|||||||
:param volume_name: the volume name
|
:param volume_name: the volume name
|
||||||
:param volume_size: the volume size
|
:param volume_size: the volume size
|
||||||
:param extra_specs: extra specifications
|
:param extra_specs: extra specifications
|
||||||
:returns: int -- return code
|
|
||||||
:returns: dict -- volume_dict
|
:returns: dict -- volume_dict
|
||||||
:raises: VolumeBackendAPIException:
|
:raises: VolumeBackendAPIException:
|
||||||
"""
|
"""
|
||||||
@ -1521,6 +1583,12 @@ class VMAXCommon(object):
|
|||||||
'array': extra_specs[utils.ARRAY],
|
'array': extra_specs[utils.ARRAY],
|
||||||
'slo': extra_specs[utils.SLO],
|
'slo': extra_specs[utils.SLO],
|
||||||
'workload': extra_specs[utils.WORKLOAD]})
|
'workload': extra_specs[utils.WORKLOAD]})
|
||||||
|
if self.version_dict:
|
||||||
|
self.volume_metadata.print_pretty_table(self.version_dict)
|
||||||
|
else:
|
||||||
|
self.version_dict = (
|
||||||
|
self.volume_metadata.gather_version_info(
|
||||||
|
extra_specs[utils.ARRAY]))
|
||||||
return extra_specs
|
return extra_specs
|
||||||
|
|
||||||
def _delete_from_srp(self, array, device_id, volume_name,
|
def _delete_from_srp(self, array, device_id, volume_name,
|
||||||
@ -1803,6 +1871,7 @@ class VMAXCommon(object):
|
|||||||
:returns: dict -- model_update
|
:returns: dict -- model_update
|
||||||
"""
|
"""
|
||||||
LOG.info("Beginning manage existing volume process")
|
LOG.info("Beginning manage existing volume process")
|
||||||
|
rep_info_dict = {}
|
||||||
array, device_id = self.utils.get_array_and_device_id(
|
array, device_id = self.utils.get_array_and_device_id(
|
||||||
volume, external_ref)
|
volume, external_ref)
|
||||||
volume_id = volume.id
|
volume_id = volume.id
|
||||||
@ -1822,9 +1891,9 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
# Set-up volume replication, if enabled
|
# Set-up volume replication, if enabled
|
||||||
if self.utils.is_replication_enabled(extra_specs):
|
if self.utils.is_replication_enabled(extra_specs):
|
||||||
rep_update = self._replicate_volume(volume, volume_name,
|
rep_update, rep_info_dict = self._replicate_volume(
|
||||||
provider_location,
|
volume, volume_name, provider_location,
|
||||||
extra_specs, delete_src=False)
|
extra_specs, delete_src=False)
|
||||||
model_update.update(rep_update)
|
model_update.update(rep_update)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -1832,6 +1901,9 @@ class VMAXCommon(object):
|
|||||||
self.masking.add_volume_to_default_storage_group(
|
self.masking.add_volume_to_default_storage_group(
|
||||||
array, device_id, volume_name, extra_specs)
|
array, device_id, volume_name, extra_specs)
|
||||||
|
|
||||||
|
self.volume_metadata.capture_manage_existing(
|
||||||
|
volume, rep_info_dict, device_id, extra_specs)
|
||||||
|
|
||||||
return model_update
|
return model_update
|
||||||
|
|
||||||
def _check_lun_valid_for_cinder_management(
|
def _check_lun_valid_for_cinder_management(
|
||||||
@ -2423,7 +2495,7 @@ class VMAXCommon(object):
|
|||||||
else:
|
else:
|
||||||
if is_rep_enabled:
|
if is_rep_enabled:
|
||||||
# Setup_volume_replication will put volume in correct sg
|
# Setup_volume_replication will put volume in correct sg
|
||||||
rep_status, rdf_dict = self.setup_volume_replication(
|
rep_status, rdf_dict, __ = self.setup_volume_replication(
|
||||||
array, volume, device_id, target_extra_specs)
|
array, volume, device_id, target_extra_specs)
|
||||||
model_update = {
|
model_update = {
|
||||||
'replication_status': rep_status,
|
'replication_status': rep_status,
|
||||||
@ -2447,6 +2519,11 @@ class VMAXCommon(object):
|
|||||||
array, volume, device_id, volume_name,
|
array, volume, device_id, volume_name,
|
||||||
rep_mode, is_rep_enabled, target_extra_specs)
|
rep_mode, is_rep_enabled, target_extra_specs)
|
||||||
|
|
||||||
|
self.volume_metadata.capture_retype_info(
|
||||||
|
volume.id, volume.size, device_id, array, srp, target_slo,
|
||||||
|
target_workload, target_sg_name, is_rep_enabled, rep_mode,
|
||||||
|
is_compression_disabled)
|
||||||
|
|
||||||
return success, model_update
|
return success, model_update
|
||||||
|
|
||||||
def _retype_volume(self, array, device_id, volume_name, target_sg_name,
|
def _retype_volume(self, array, device_id, volume_name, target_sg_name,
|
||||||
@ -2642,7 +2719,9 @@ class VMAXCommon(object):
|
|||||||
:param extra_specs: the extra specifications
|
:param extra_specs: the extra specifications
|
||||||
:param target_device_id: the target device id
|
:param target_device_id: the target device id
|
||||||
:returns: replication_status -- str, replication_driver_data -- dict
|
:returns: replication_status -- str, replication_driver_data -- dict
|
||||||
|
rep_info_dict -- dict
|
||||||
"""
|
"""
|
||||||
|
rep_extra_specs = {'rep_mode': None}
|
||||||
source_name = volume.name
|
source_name = volume.name
|
||||||
LOG.debug('Starting replication setup '
|
LOG.debug('Starting replication setup '
|
||||||
'for volume: %s.', source_name)
|
'for volume: %s.', source_name)
|
||||||
@ -2686,8 +2765,15 @@ class VMAXCommon(object):
|
|||||||
target_name)
|
target_name)
|
||||||
replication_status = REPLICATION_ENABLED
|
replication_status = REPLICATION_ENABLED
|
||||||
replication_driver_data = rdf_dict
|
replication_driver_data = rdf_dict
|
||||||
|
rep_info_dict = self.volume_metadata.gather_replication_info(
|
||||||
|
rdf_group_no=rdf_group_no,
|
||||||
|
target_name=target_name, remote_array=remote_array,
|
||||||
|
target_device_id=target_device_id,
|
||||||
|
replication_status=replication_status,
|
||||||
|
rep_mode=rep_extra_specs['rep_mode'],
|
||||||
|
rdf_group_label=self.rep_config['rdf_group_label'])
|
||||||
|
|
||||||
return replication_status, replication_driver_data
|
return replication_status, replication_driver_data, rep_info_dict
|
||||||
|
|
||||||
def _add_volume_to_async_rdf_managed_grp(
|
def _add_volume_to_async_rdf_managed_grp(
|
||||||
self, array, device_id, volume_name, remote_array,
|
self, array, device_id, volume_name, remote_array,
|
||||||
@ -3779,6 +3865,9 @@ class VMAXCommon(object):
|
|||||||
LOG.exception(exception_message)
|
LOG.exception(exception_message)
|
||||||
raise exception.VolumeBackendAPIException(data=exception_message)
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
||||||
|
|
||||||
|
self.volume_metadata.capture_modify_group(
|
||||||
|
vol_grp_name, group.id, add_vols, remove_volumes, array)
|
||||||
|
|
||||||
return model_update, None, None
|
return model_update, None, None
|
||||||
|
|
||||||
def _remove_remote_vols_from_volume_group(
|
def _remove_remote_vols_from_volume_group(
|
||||||
@ -4211,6 +4300,13 @@ class VMAXCommon(object):
|
|||||||
if vol_rep_status != fields.ReplicationStatus.ERROR:
|
if vol_rep_status != fields.ReplicationStatus.ERROR:
|
||||||
loc = vol.replication_driver_data
|
loc = vol.replication_driver_data
|
||||||
rep_data = vol.provider_location
|
rep_data = vol.provider_location
|
||||||
|
local = ast.literal_eval(loc)
|
||||||
|
remote = ast.literal_eval(rep_data)
|
||||||
|
self.volume_metadata.capture_failover_volume(
|
||||||
|
vol, local['device_id'], local['array'], rdf_group_no,
|
||||||
|
remote['device_id'], remote['array'], extra_specs,
|
||||||
|
failover, vol_grp_name, vol_rep_status, utils.REP_ASYNC)
|
||||||
|
|
||||||
update = {'id': vol.id,
|
update = {'id': vol.id,
|
||||||
'replication_status': vol_rep_status,
|
'replication_status': vol_rep_status,
|
||||||
'provider_location': loc,
|
'provider_location': loc,
|
||||||
|
@ -96,6 +96,7 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
|||||||
- Support for list manageable volumes and snapshots
|
- Support for list manageable volumes and snapshots
|
||||||
(bp/vmax-list-manage-existing)
|
(bp/vmax-list-manage-existing)
|
||||||
- Fix for SSL verification/cert application (bug #1772924)
|
- Fix for SSL verification/cert application (bug #1772924)
|
||||||
|
- Log VMAX metadata of a volume (bp vmax-metadata)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "3.2.0"
|
VERSION = "3.2.0"
|
||||||
|
@ -101,6 +101,7 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
|
|||||||
- Support for list manageable volumes and snapshots
|
- Support for list manageable volumes and snapshots
|
||||||
(bp/vmax-list-manage-existing)
|
(bp/vmax-list-manage-existing)
|
||||||
- Fix for SSL verification/cert application (bug #1772924)
|
- Fix for SSL verification/cert application (bug #1772924)
|
||||||
|
- Log VMAX metadata of a volume (bp vmax-metadata)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "3.2.0"
|
VERSION = "3.2.0"
|
||||||
|
653
cinder/volume/drivers/dell_emc/vmax/metadata.py
Normal file
653
cinder/volume/drivers/dell_emc/vmax/metadata.py
Normal file
@ -0,0 +1,653 @@
|
|||||||
|
# Copyright (c) 2018 Dell Inc. or its subsidiaries.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import datetime
|
||||||
|
import platform
|
||||||
|
import prettytable
|
||||||
|
import six
|
||||||
|
import time
|
||||||
|
import types
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from cinder.objects import volume
|
||||||
|
from cinder import version
|
||||||
|
|
||||||
|
from cinder.volume.drivers.dell_emc.vmax import utils
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CLEANUP_LIST = ['masking_view', 'child_storage_group', 'parent_storage_group',
|
||||||
|
'initiator_group', 'port_group', 'storage_group']
|
||||||
|
|
||||||
|
|
||||||
|
def debug_required(func):
|
||||||
|
"""Only execute the function if debug is enabled."""
|
||||||
|
|
||||||
|
def func_wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
if args[0].is_debug:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Volume metadata logging failure. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
return func_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class VMAXVolumeMetadata(object):
|
||||||
|
"""Gathers VMAX specific volume information.
|
||||||
|
|
||||||
|
Also gathers Unisphere, Microcode OS/distribution and python versions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, rest, version, is_debug):
|
||||||
|
self.version_dict = {}
|
||||||
|
self.rest = rest
|
||||||
|
self.utils = utils.VMAXUtils()
|
||||||
|
self.volume_trace_list = []
|
||||||
|
self.is_debug = is_debug
|
||||||
|
self.vmax_driver_version = version
|
||||||
|
|
||||||
|
def _update_platform(self):
|
||||||
|
"""Update the platform."""
|
||||||
|
try:
|
||||||
|
self.version_dict['platform'] = platform.platform()
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to determine the platform. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
def _get_python_version(self):
|
||||||
|
"""Get the python version."""
|
||||||
|
try:
|
||||||
|
self.version_dict['python_version'] = platform.python_version()
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to determine the python version. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
def _update_version_from_version_string(self):
|
||||||
|
"""Update the version from the version string."""
|
||||||
|
try:
|
||||||
|
self.version_dict['openstack_version'] = (
|
||||||
|
version.version_info.version_string())
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to determine the OS version. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
def _update_release_from_release_string(self):
|
||||||
|
"""Update the release from the release string."""
|
||||||
|
try:
|
||||||
|
self.version_dict['openstack_release'] = (
|
||||||
|
version.version_info.release_string())
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to get release info. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_version_info_version():
|
||||||
|
"""Gets the version.
|
||||||
|
|
||||||
|
:returns: string -- version
|
||||||
|
"""
|
||||||
|
return version.version_info.version
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_version_info_release():
|
||||||
|
"""Gets the release.
|
||||||
|
|
||||||
|
:returns: string -- release
|
||||||
|
"""
|
||||||
|
return version.version_info.release
|
||||||
|
|
||||||
|
def _update_info_from_version_info(self):
|
||||||
|
"""Update class variables from version info."""
|
||||||
|
try:
|
||||||
|
ver = self._get_version_info_version()
|
||||||
|
if ver:
|
||||||
|
self.version_dict['openstack_version'] = ver
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to get version info. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
try:
|
||||||
|
rel = self._get_version_info_release()
|
||||||
|
if rel:
|
||||||
|
self.version_dict['openstack_release'] = rel
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to get release info. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
|
||||||
|
def _update_openstack_info(self):
|
||||||
|
"""Update openstack info."""
|
||||||
|
self._update_version_from_version_string()
|
||||||
|
self._update_release_from_release_string()
|
||||||
|
self._update_platform()
|
||||||
|
self._get_python_version()
|
||||||
|
# Some distributions override with more meaningful information
|
||||||
|
self._update_info_from_version_info()
|
||||||
|
|
||||||
|
def _update_vmax_info(self, serial_number):
|
||||||
|
"""Update VMAX info.
|
||||||
|
|
||||||
|
:param serial_number: the serial number of the array
|
||||||
|
"""
|
||||||
|
vmax_u4v_version_dict = (
|
||||||
|
self.rest.get_unisphere_version())
|
||||||
|
self.version_dict['unisphere_version'] = (
|
||||||
|
vmax_u4v_version_dict['version'])
|
||||||
|
self.version_dict['serial_number'] = serial_number
|
||||||
|
vmax_info_dict = self.rest.get_array_serial(serial_number)
|
||||||
|
self.version_dict['vmax_firmware_version'] = vmax_info_dict['ucode']
|
||||||
|
self.version_dict['vmax_model'] = vmax_info_dict['model']
|
||||||
|
self.version_dict['vmax_driver_version'] = self.vmax_driver_version
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def gather_version_info(self, serial_number):
|
||||||
|
"""Gather info on the array
|
||||||
|
|
||||||
|
:param serial_number: the serial number of the array
|
||||||
|
:returns: version_dict
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._update_openstack_info()
|
||||||
|
self._update_vmax_info(serial_number)
|
||||||
|
self.print_pretty_table(self.version_dict)
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Unable to gather version info. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
return self.version_dict
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def gather_volume_info(
|
||||||
|
self, volume_id, operation, append, **kwargs):
|
||||||
|
"""Gather volume information.
|
||||||
|
|
||||||
|
:param volume_id: the unique volume id key
|
||||||
|
:param operation: the operation e.g "create"
|
||||||
|
:param append: append flag
|
||||||
|
:param kwargs: variable length argument list
|
||||||
|
:returns: datadict
|
||||||
|
"""
|
||||||
|
volume_trace_dict = {}
|
||||||
|
volume_key_value = {}
|
||||||
|
datadict = {}
|
||||||
|
try:
|
||||||
|
volume_trace_dict = self._fill_volume_trace_dict(
|
||||||
|
volume_id, operation, append, **kwargs)
|
||||||
|
volume_trace_dict['volume_updated_time'] = (
|
||||||
|
datetime.datetime.fromtimestamp(
|
||||||
|
int(time.time())).strftime('%Y-%m-%d %H:%M:%S'))
|
||||||
|
volume_key_value[volume_id] = volume_trace_dict
|
||||||
|
if not self.volume_trace_list:
|
||||||
|
self.volume_trace_list.append(volume_key_value.copy())
|
||||||
|
else:
|
||||||
|
self._consolidate_volume_trace_list(
|
||||||
|
volume_id, volume_trace_dict, volume_key_value)
|
||||||
|
for datadict in list(self.volume_trace_list):
|
||||||
|
if volume_id in datadict:
|
||||||
|
if not append:
|
||||||
|
self.volume_trace_list.remove(datadict)
|
||||||
|
return datadict
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning("Exception in gather volume metadata. "
|
||||||
|
"Exception is %s.", ex)
|
||||||
|
return datadict
|
||||||
|
|
||||||
|
def _fill_volume_trace_dict(
|
||||||
|
self, volume_id, operation, append, **kwargs):
|
||||||
|
"""Populates a dictionary with key value pairs
|
||||||
|
|
||||||
|
:param volume_id: the unique volume id key
|
||||||
|
:param operation: the operation e.g "create"
|
||||||
|
:param append: append flag
|
||||||
|
:param kwargs: variable length argument list
|
||||||
|
:returns: my_volume_trace_dict
|
||||||
|
"""
|
||||||
|
param_dict = locals()
|
||||||
|
my_volume_trace_dict = {}
|
||||||
|
for k, v in param_dict.items():
|
||||||
|
if self._param_condition(k, v):
|
||||||
|
my_volume_trace_dict[k] = v
|
||||||
|
if k == 'kwargs':
|
||||||
|
for k2, v2 in v.items():
|
||||||
|
if self._param_condition(k2, v2):
|
||||||
|
my_volume_trace_dict[k2] = v2
|
||||||
|
elif k2 == 'mv_list' and v2:
|
||||||
|
for i, item in enumerate(v2, 1):
|
||||||
|
my_volume_trace_dict["masking_view_%d" % i] = item
|
||||||
|
elif k2 == 'sg_list' and v2:
|
||||||
|
for i, item in enumerate(v2, 1):
|
||||||
|
my_volume_trace_dict["storage_group_%d" % i] = item
|
||||||
|
|
||||||
|
return my_volume_trace_dict
|
||||||
|
|
||||||
|
def _param_condition(self, key, value):
|
||||||
|
"""Determines condition for inclusion.
|
||||||
|
|
||||||
|
:param key: the key
|
||||||
|
:param value: the value
|
||||||
|
|
||||||
|
:returns: True or False
|
||||||
|
"""
|
||||||
|
exclude_list = ('self', 'append', 'mv_list', 'sg_list')
|
||||||
|
return (value is not None and key not in exclude_list and
|
||||||
|
not isinstance(value, (dict,
|
||||||
|
types.FunctionType,
|
||||||
|
type)))
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def print_pretty_table(self, datadict):
|
||||||
|
"""Prints the data in the dict.
|
||||||
|
|
||||||
|
:param datadict: the data dictionary
|
||||||
|
"""
|
||||||
|
t = prettytable.PrettyTable(['Key', 'Value'])
|
||||||
|
for k, v in datadict.items():
|
||||||
|
if v is not None:
|
||||||
|
t.add_row([k, v])
|
||||||
|
|
||||||
|
LOG.debug('\n%(output)s\n', {'output': t})
|
||||||
|
|
||||||
|
def _consolidate_volume_trace_list(
|
||||||
|
self, volume_id, volume_trace_dict, volume_key_value):
|
||||||
|
"""Consolidate data into self.volume_trace_list
|
||||||
|
|
||||||
|
:param volume_id: the unique volume identifier
|
||||||
|
:param volume_trace_dict: the existing dict
|
||||||
|
:param volume_key_value: the volume id key and dict value
|
||||||
|
"""
|
||||||
|
is_merged = False
|
||||||
|
for datadict in list(self.volume_trace_list):
|
||||||
|
if volume_id in datadict:
|
||||||
|
for key, dict_value in datadict.items():
|
||||||
|
merged_dict = (
|
||||||
|
self.utils.merge_dicts(
|
||||||
|
volume_trace_dict, dict_value))
|
||||||
|
self.volume_trace_list.remove(datadict)
|
||||||
|
volume_key_value[volume_id] = merged_dict
|
||||||
|
self.volume_trace_list.append(volume_key_value.copy())
|
||||||
|
is_merged = True
|
||||||
|
if not is_merged:
|
||||||
|
self.volume_trace_list.append(volume_key_value.copy())
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def update_volume_info_metadata(self, datadict, version_dict):
|
||||||
|
"""Get update volume metadata with volume info
|
||||||
|
|
||||||
|
:param datadict: volume info key value pairs
|
||||||
|
:param version_dict: version dictionary
|
||||||
|
:returns: volume_metadata
|
||||||
|
"""
|
||||||
|
return self.utils.merge_dicts(
|
||||||
|
version_dict, *datadict.values())
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_attach_info(
|
||||||
|
self, volume, extra_specs, masking_view_dict, host,
|
||||||
|
is_multipath, is_multiattach):
|
||||||
|
"""Captures attach info in volume metadata
|
||||||
|
|
||||||
|
:param volume: the volume object
|
||||||
|
:param extra_specs: extra specifications
|
||||||
|
:param masking_view_dict: masking view dict
|
||||||
|
:param host: host
|
||||||
|
:param is_multipath: is mulitipath flag
|
||||||
|
:param is_multiattach: is multi attach
|
||||||
|
"""
|
||||||
|
mv_list, sg_list = [], []
|
||||||
|
child_storage_group, parent_storage_group = None, None
|
||||||
|
initiator_group, port_group = None, None
|
||||||
|
|
||||||
|
if is_multiattach:
|
||||||
|
operation = 'multi_attach'
|
||||||
|
mv_list = masking_view_dict['mv_list']
|
||||||
|
sg_list = masking_view_dict['sg_list']
|
||||||
|
else:
|
||||||
|
operation = 'attach'
|
||||||
|
child_storage_group = masking_view_dict[utils.SG_NAME]
|
||||||
|
parent_storage_group = masking_view_dict[utils.PARENT_SG_NAME]
|
||||||
|
initiator_group = masking_view_dict[utils.IG_NAME]
|
||||||
|
port_group = masking_view_dict[utils.PORTGROUPNAME]
|
||||||
|
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, operation, False,
|
||||||
|
serial_number=extra_specs[utils.ARRAY],
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD], srp=extra_specs[utils.SRP],
|
||||||
|
masking_view=masking_view_dict[utils.MV_NAME],
|
||||||
|
child_storage_group=child_storage_group,
|
||||||
|
parent_storage_group=parent_storage_group,
|
||||||
|
initiator_group=initiator_group,
|
||||||
|
port_group=port_group,
|
||||||
|
host=host, is_multipath=is_multipath,
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
mv_list=mv_list, sg_list=sg_list)
|
||||||
|
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_detach_info(
|
||||||
|
self, volume, extra_specs, device_id, mv_list, sg_list):
|
||||||
|
"""Captures detach info in volume metadata
|
||||||
|
|
||||||
|
:param volume: the volume object
|
||||||
|
:param extra_specs: extra specifications
|
||||||
|
:param device_id: masking view dict
|
||||||
|
:param mv_list: masking view list
|
||||||
|
:param sg_list: storage group list
|
||||||
|
"""
|
||||||
|
default_sg = self.utils.derive_default_sg_from_extra_specs(extra_specs)
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, 'detach', False, device_id=device_id,
|
||||||
|
serial_number=extra_specs[utils.ARRAY],
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD], srp=extra_specs[utils.SRP],
|
||||||
|
default_sg_name=default_sg,
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
mv_list=mv_list, sg_list=sg_list
|
||||||
|
)
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_extend_info(
|
||||||
|
self, volume, new_size, device_id, extra_specs, array):
|
||||||
|
"""Capture extend info in volume metadata
|
||||||
|
|
||||||
|
:param volume: the volume object
|
||||||
|
:param new_size: new size
|
||||||
|
:param device_id: device id
|
||||||
|
:param extra_specs: extra specifications
|
||||||
|
:param array: array serial number
|
||||||
|
"""
|
||||||
|
default_sg = self.utils.derive_default_sg_from_extra_specs(extra_specs)
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, 'extend', False, volume_size=new_size,
|
||||||
|
device_id=device_id,
|
||||||
|
default_sg_name=default_sg, serial_number=array,
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD],
|
||||||
|
srp=extra_specs[utils.SRP],
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
is_compression_disabled=self.utils.is_compression_disabled(
|
||||||
|
extra_specs))
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_snapshot_info(
|
||||||
|
self, source, extra_specs, operation, last_ss_name):
|
||||||
|
"""Captures snapshot info in volume metadata
|
||||||
|
|
||||||
|
:param source: the source volume object
|
||||||
|
:param extra_specs: extra specifications
|
||||||
|
:param operation: snapshot operation
|
||||||
|
:param last_ss_name: the last snapshot name
|
||||||
|
"""
|
||||||
|
if isinstance(source, volume.Volume):
|
||||||
|
if 'create' in operation:
|
||||||
|
snapshot_count = six.text_type(len(source.snapshots))
|
||||||
|
else:
|
||||||
|
snapshot_count = six.text_type(len(source.snapshots) - 1)
|
||||||
|
default_sg = (
|
||||||
|
self.utils.derive_default_sg_from_extra_specs(extra_specs))
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
source.id, operation, False,
|
||||||
|
volume_size=source.size,
|
||||||
|
default_sg_name=default_sg,
|
||||||
|
serial_number=extra_specs[utils.ARRAY],
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD],
|
||||||
|
srp=extra_specs[utils.SRP],
|
||||||
|
identifier_name=(
|
||||||
|
self.utils.get_volume_element_name(source.id)),
|
||||||
|
snapshot_count=snapshot_count,
|
||||||
|
last_ss_name=last_ss_name)
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_modify_group(
|
||||||
|
self, group_name, group_id, add_vols, remove_volumes, array):
|
||||||
|
"""Captures group info after a modify operation
|
||||||
|
|
||||||
|
:param group_name: group name
|
||||||
|
:param group_id: group id
|
||||||
|
:param add_vols: add volume list
|
||||||
|
:param remove_volumes: remove volume list
|
||||||
|
:param array: array serial number
|
||||||
|
"""
|
||||||
|
if not self.version_dict:
|
||||||
|
self.version_dict = self.gather_version_info(array)
|
||||||
|
for add_vol in add_vols:
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
add_vol.id, 'addToGroup', True,
|
||||||
|
group_name=group_name, group_id=group_id)
|
||||||
|
add_volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(add_volume_metadata)
|
||||||
|
|
||||||
|
for remove_volume in remove_volumes:
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
remove_volume.id, 'removeFromGroup', True,
|
||||||
|
group_name='Removed from %s' % group_name,
|
||||||
|
group_id='Removed from %s' % group_id)
|
||||||
|
remove_volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(remove_volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_create_volume(
|
||||||
|
self, device_id, volume, group_name, group_id, extra_specs,
|
||||||
|
rep_info_dict, operation, source_snapshot_id):
|
||||||
|
"""Captures create volume info in volume metadata
|
||||||
|
|
||||||
|
:param device_id: device id
|
||||||
|
:param volume: volume object
|
||||||
|
:param group_name: group name
|
||||||
|
:param group_id: group id
|
||||||
|
:param extra_specs: additional info
|
||||||
|
:param rep_info_dict: information gathered from replication
|
||||||
|
:param operation: the type of create operation
|
||||||
|
:param source_snapshot_id: the source snapshot id
|
||||||
|
:returns: volume_metadata dict
|
||||||
|
"""
|
||||||
|
rdf_group_no, target_name, remote_array, target_device_id = (
|
||||||
|
None, None, None, None)
|
||||||
|
rep_mode, replication_status, rdf_group_label, use_bias = (
|
||||||
|
None, None, None, None)
|
||||||
|
if rep_info_dict:
|
||||||
|
rdf_group_no = rep_info_dict['rdf_group_no']
|
||||||
|
target_name = rep_info_dict['target_name']
|
||||||
|
remote_array = rep_info_dict['remote_array']
|
||||||
|
target_device_id = rep_info_dict['target_device_id']
|
||||||
|
rep_mode = rep_info_dict['rep_mode']
|
||||||
|
replication_status = rep_info_dict['replication_status']
|
||||||
|
rdf_group_label = rep_info_dict['rdf_group_label']
|
||||||
|
if utils.METROBIAS in extra_specs:
|
||||||
|
use_bias = extra_specs[utils.METROBIAS]
|
||||||
|
|
||||||
|
default_sg = self.utils.derive_default_sg_from_extra_specs(
|
||||||
|
extra_specs, rep_mode)
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, operation, True, volume_size=volume.size,
|
||||||
|
device_id=device_id,
|
||||||
|
default_sg_name=default_sg,
|
||||||
|
serial_number=extra_specs[utils.ARRAY],
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD],
|
||||||
|
srp=extra_specs[utils.SRP],
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
source_volid=volume.source_volid,
|
||||||
|
group_name=group_name, group_id=group_id,
|
||||||
|
rdf_group_no=rdf_group_no,
|
||||||
|
target_name=target_name, remote_array=remote_array,
|
||||||
|
target_device_id=target_device_id,
|
||||||
|
source_snapshot_id=source_snapshot_id,
|
||||||
|
rep_mode=rep_mode, replication_status=replication_status,
|
||||||
|
rdf_group_label=rdf_group_label, use_bias=use_bias,
|
||||||
|
is_compression_disabled = (
|
||||||
|
'yes' if self.utils.is_compression_disabled(
|
||||||
|
extra_specs) else 'no')
|
||||||
|
)
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def gather_replication_info(
|
||||||
|
self, rdf_group_no=None,
|
||||||
|
target_name=None, remote_array=None,
|
||||||
|
target_device_id=None, replication_status=None,
|
||||||
|
rep_mode=None, rdf_group_label=None):
|
||||||
|
"""Gathers replication information
|
||||||
|
|
||||||
|
:param rdf_group_no: RDF group number
|
||||||
|
:param target_name: target volume name
|
||||||
|
:param remote_array: remote array serialnumber
|
||||||
|
:param target_device_id: target device id
|
||||||
|
:param replication_status: replication status
|
||||||
|
:param rep_mode: replication mode, sync, async, metro
|
||||||
|
:param rdf_group_label: rdf group label
|
||||||
|
:returns: rep_dict
|
||||||
|
"""
|
||||||
|
param_dict = locals()
|
||||||
|
return self._fill_volume_trace_dict(param_dict)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_failover_volume(
|
||||||
|
self, volume, target_device, remote_array, rdf_group, device_id,
|
||||||
|
array, extra_specs, failover, vol_grp_name,
|
||||||
|
replication_status, rep_mode):
|
||||||
|
"""Captures failover info in volume metadata
|
||||||
|
|
||||||
|
:param volume: volume object
|
||||||
|
:param target_device: the device to failover to
|
||||||
|
:param remote_array: the array to failover to
|
||||||
|
:param rdf_group: the rdf group
|
||||||
|
:param device_id: the device to failover from
|
||||||
|
:param array: the array to failover from
|
||||||
|
:param extra_specs: additional info
|
||||||
|
:param failover: failover flag
|
||||||
|
:param vol_grp_name: async group name
|
||||||
|
:param replication_status: volume replication status
|
||||||
|
:param rep_mode: replication mode
|
||||||
|
"""
|
||||||
|
operation = "Failover" if failover else "Failback"
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, operation, True, volume_size=volume.size,
|
||||||
|
device_id=target_device,
|
||||||
|
serial_number=remote_array,
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD],
|
||||||
|
srp=extra_specs[utils.SRP],
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
source_volid=volume.source_volid,
|
||||||
|
rdf_group_no=rdf_group, remote_array=array,
|
||||||
|
target_device_id=device_id, vol_grp_name=vol_grp_name,
|
||||||
|
replication_status=replication_status, rep_mode=rep_mode
|
||||||
|
)
|
||||||
|
|
||||||
|
self.version_dict = (
|
||||||
|
self.gather_version_info(remote_array))
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_manage_existing(
|
||||||
|
self, volume, rep_info_dict, device_id, extra_specs):
|
||||||
|
"""Captures manage existing info in volume metadata
|
||||||
|
|
||||||
|
:param volume: volume object
|
||||||
|
:param rep_info_dict: information gathered from replication
|
||||||
|
:param device_id: the VMAX device id
|
||||||
|
:param extra_specs: the extra specs
|
||||||
|
"""
|
||||||
|
operation = "manage_existing_volume"
|
||||||
|
rdf_group_no, target_name, remote_array, target_device_id = (
|
||||||
|
None, None, None, None)
|
||||||
|
rep_mode, replication_status, rdf_group_label = (
|
||||||
|
None, None, None)
|
||||||
|
if rep_info_dict:
|
||||||
|
rdf_group_no = rep_info_dict['rdf_group_no']
|
||||||
|
target_name = rep_info_dict['target_name']
|
||||||
|
remote_array = rep_info_dict['remote_array']
|
||||||
|
target_device_id = rep_info_dict['target_device_id']
|
||||||
|
rep_mode = rep_info_dict['rep_mode']
|
||||||
|
replication_status = rep_info_dict['replication_status']
|
||||||
|
rdf_group_label = rep_info_dict['rdf_group_label']
|
||||||
|
|
||||||
|
default_sg = self.utils.derive_default_sg_from_extra_specs(
|
||||||
|
extra_specs, rep_mode)
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume.id, operation, True, volume_size=volume.size,
|
||||||
|
device_id=device_id,
|
||||||
|
default_sg_name=default_sg,
|
||||||
|
serial_number=extra_specs[utils.ARRAY],
|
||||||
|
service_level=extra_specs[utils.SLO],
|
||||||
|
workload=extra_specs[utils.WORKLOAD],
|
||||||
|
srp=extra_specs[utils.SRP],
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume.id),
|
||||||
|
source_volid=volume.source_volid,
|
||||||
|
rdf_group_no=rdf_group_no,
|
||||||
|
target_name=target_name, remote_array=remote_array,
|
||||||
|
target_device_id=target_device_id,
|
||||||
|
rep_mode=rep_mode, replication_status=replication_status,
|
||||||
|
rdf_group_label=rdf_group_label
|
||||||
|
)
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
||||||
|
@debug_required
|
||||||
|
def capture_retype_info(
|
||||||
|
self, volume_id, volume_size, device_id, array, srp, target_slo,
|
||||||
|
target_workload, target_sg_name, is_rep_enabled, rep_mode,
|
||||||
|
is_compression_disabled):
|
||||||
|
"""Captures manage existing info in volume metadata
|
||||||
|
|
||||||
|
:param volume_id: volume identifier
|
||||||
|
:param volume_size: volume size
|
||||||
|
:param device_id: the VMAX device id
|
||||||
|
:param array: the VMAX serialnumber
|
||||||
|
:param srp: VMAX SRP
|
||||||
|
:param target_slo: volume name
|
||||||
|
:param target_workload: the VMAX device id
|
||||||
|
:param is_rep_enabled: replication enabled flag
|
||||||
|
:param rep_mode: replication mode
|
||||||
|
:param is_compression_disabled: compression disabled flag
|
||||||
|
"""
|
||||||
|
operation = "retype"
|
||||||
|
datadict = self.gather_volume_info(
|
||||||
|
volume_id, operation, False, volume_size=volume_size,
|
||||||
|
device_id=device_id,
|
||||||
|
target_sg_name=target_sg_name,
|
||||||
|
serial_number=array,
|
||||||
|
target_service_level=target_slo,
|
||||||
|
target_workload=target_workload,
|
||||||
|
srp=srp,
|
||||||
|
identifier_name=self.utils.get_volume_element_name(volume_id),
|
||||||
|
is_rep_enabled=('yes' if is_rep_enabled else 'no'),
|
||||||
|
rep_mode=rep_mode, is_compression_disabled = (
|
||||||
|
'yes' if is_compression_disabled else 'no')
|
||||||
|
)
|
||||||
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
|
datadict, self.version_dict)
|
||||||
|
self.print_pretty_table(volume_metadata)
|
@ -430,14 +430,24 @@ class VMAXRest(object):
|
|||||||
:return: version and major_version(e.g. ("V8.4.0.16", "84"))
|
:return: version and major_version(e.g. ("V8.4.0.16", "84"))
|
||||||
"""
|
"""
|
||||||
version, major_version = None, None
|
version, major_version = None, None
|
||||||
target_uri = "/%s/system/version" % U4V_VERSION
|
response = self.get_unisphere_version()
|
||||||
response = self._get_request(target_uri, 'version')
|
|
||||||
if response and response.get('version'):
|
if response and response.get('version'):
|
||||||
version = response['version']
|
version = response['version']
|
||||||
version_list = version.split('.')
|
version_list = version.split('.')
|
||||||
major_version = version_list[0][1] + version_list[1]
|
major_version = version_list[0][1] + version_list[1]
|
||||||
return version, major_version
|
return version, major_version
|
||||||
|
|
||||||
|
def get_unisphere_version(self):
|
||||||
|
"""Get the unisphere version from the server.
|
||||||
|
|
||||||
|
:returns: version dict
|
||||||
|
"""
|
||||||
|
version_url = "/%s/system/version" % U4V_VERSION
|
||||||
|
version_dict = self._get_request(version_url, 'version')
|
||||||
|
if not version_dict:
|
||||||
|
LOG.error("Unisphere version info not found.")
|
||||||
|
return version_dict
|
||||||
|
|
||||||
def get_srp_by_name(self, array, srp=None):
|
def get_srp_by_name(self, array, srp=None):
|
||||||
"""Returns the details of a storage pool.
|
"""Returns the details of a storage pool.
|
||||||
|
|
||||||
|
@ -690,6 +690,36 @@ class VMAXUtils(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def derive_default_sg_from_extra_specs(self, extra_specs, rep_mode=None):
|
||||||
|
"""Get the name of the default sg from the extra specs.
|
||||||
|
|
||||||
|
:param extra_specs: extra specs
|
||||||
|
:returns: default sg - string
|
||||||
|
"""
|
||||||
|
do_disable_compression = self.is_compression_disabled(
|
||||||
|
extra_specs)
|
||||||
|
rep_enabled = self.is_replication_enabled(extra_specs)
|
||||||
|
return self.get_default_storage_group_name(
|
||||||
|
extra_specs[SRP], extra_specs[SLO],
|
||||||
|
extra_specs[WORKLOAD],
|
||||||
|
is_compression_disabled=do_disable_compression,
|
||||||
|
is_re=rep_enabled, rep_mode=rep_mode)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def merge_dicts(d1, *args):
|
||||||
|
"""Merge dictionaries
|
||||||
|
|
||||||
|
:param d1: dict 1
|
||||||
|
:param *args: one or more dicts
|
||||||
|
:returns: merged dict
|
||||||
|
"""
|
||||||
|
d2 = {}
|
||||||
|
for d in args:
|
||||||
|
d2 = d.copy()
|
||||||
|
d2.update(d1)
|
||||||
|
d1 = d2
|
||||||
|
return d2
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_temp_failover_grp_name(rep_config):
|
def get_temp_failover_grp_name(rep_config):
|
||||||
"""Get the temporary group name used for failover.
|
"""Get the temporary group name used for failover.
|
||||||
|
4
releasenotes/notes/vmax-metadata-ac9bdd31e7e561c3.yaml
Normal file
4
releasenotes/notes/vmax-metadata-ac9bdd31e7e561c3.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Log VMAX specific metadata of a volume if debug is enabled.
|
Loading…
x
Reference in New Issue
Block a user