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
|
||||
from copy import deepcopy
|
||||
import datetime
|
||||
import platform
|
||||
import time
|
||||
|
||||
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_volume
|
||||
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 fc
|
||||
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 metadata
|
||||
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 utils
|
||||
@ -909,6 +912,42 @@ class VMAXCommonData(object):
|
||||
"getDynamicRDFCapability": "RDF1_Capable", "RDFA": 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):
|
||||
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(
|
||||
common.VMAXCommon, '_replicate_volume',
|
||||
return_value={
|
||||
return_value=({
|
||||
'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,
|
||||
mock_check, mock_get, mock_cg):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
@ -7219,7 +7258,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||
with mock.patch.object(self.common, '_replicate_volume',
|
||||
return_value={}) as mock_rep:
|
||||
return_value=({}, {})) as mock_rep:
|
||||
self.common.create_cloned_volume(
|
||||
self.data.test_clone_volume, self.data.test_volume)
|
||||
volume_dict = self.data.provider_location
|
||||
@ -7231,7 +7270,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||
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.data.test_clone_volume, self.data.test_snapshot)
|
||||
volume_dict = self.data.provider_location
|
||||
@ -7245,7 +7284,8 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
volume_dict = self.data.provider_location
|
||||
rs_enabled = fields.ReplicationStatus.ENABLED
|
||||
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.data.test_volume, "1", volume_dict, self.extra_specs)
|
||||
mock_setup.assert_called_once_with(
|
||||
@ -7396,7 +7436,7 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
|
||||
@mock.patch.object(common.VMAXCommon,
|
||||
'_replicate_volume',
|
||||
return_value={})
|
||||
return_value=({}, {}))
|
||||
def test_manage_existing_is_replicated(self, mock_rep):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
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')
|
||||
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.extra_specs)
|
||||
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(common.VMAXCommon, '_create_volume')
|
||||
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.extra_specs, self.data.device_id2)
|
||||
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):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
extra_specs['rep_mode'] = utils.REP_ASYNC
|
||||
rep_status, rep_data = (
|
||||
rep_status, rep_data, __ = (
|
||||
self.async_driver.common.setup_volume_replication(
|
||||
self.data.array, self.data.test_volume,
|
||||
self.data.device_id, extra_specs))
|
||||
@ -7936,7 +7976,8 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
@mock.patch.object(common.VMAXCommon, '_retype_remote_volume',
|
||||
return_value=True)
|
||||
@mock.patch.object(common.VMAXCommon, 'setup_volume_replication',
|
||||
return_value=VMAXCommonData.provider_location2)
|
||||
return_value=(
|
||||
'', VMAXCommonData.provider_location2, ''))
|
||||
@mock.patch.object(common.VMAXCommon,
|
||||
'_remove_vol_and_cleanup_replication')
|
||||
@mock.patch.object(utils.VMAXUtils, 'is_replication_enabled',
|
||||
@ -7970,3 +8011,230 @@ class VMAXCommonReplicationTest(test.TestCase):
|
||||
self.data.test_volume.name, utils.REP_SYNC,
|
||||
True, self.data.extra_specs)
|
||||
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.volume import configuration
|
||||
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 rest
|
||||
from cinder.volume.drivers.dell_emc.vmax import utils
|
||||
@ -118,6 +119,8 @@ class VMAXCommon(object):
|
||||
self.masking = masking.VMAXMasking(prtcl, self.rest)
|
||||
self.provision = provision.VMAXProvision(self.rest)
|
||||
self.version = version
|
||||
self.volume_metadata = volume_metadata.VMAXVolumeMetadata(
|
||||
self.rest, version, LOG.isEnabledFor(logging.DEBUG))
|
||||
# replication
|
||||
self.replication_enabled = False
|
||||
self.extend_replicated_vol = False
|
||||
@ -126,6 +129,7 @@ class VMAXCommon(object):
|
||||
self.failover = False
|
||||
self._get_replication_info()
|
||||
self._gather_info()
|
||||
self.version_dict = {}
|
||||
|
||||
def _gather_info(self):
|
||||
"""Gather the relevant information for update_volume_stats."""
|
||||
@ -249,8 +253,11 @@ class VMAXCommon(object):
|
||||
:returns: model_update - dict
|
||||
"""
|
||||
model_update = {}
|
||||
rep_info_dict = {}
|
||||
rep_driver_data = {}
|
||||
volume_id = volume.id
|
||||
group_name = None
|
||||
group_id = None
|
||||
extra_specs = self._initial_setup(volume)
|
||||
if 'qos' in extra_specs:
|
||||
del extra_specs['qos']
|
||||
@ -264,8 +271,8 @@ class VMAXCommon(object):
|
||||
|
||||
# Set-up volume replication, if enabled
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
rep_update = self._replicate_volume(volume, volume_name,
|
||||
volume_dict, extra_specs)
|
||||
rep_update, rep_info_dict = self._replicate_volume(
|
||||
volume, volume_name, volume_dict, extra_specs)
|
||||
rep_driver_data = rep_update['replication_driver_data']
|
||||
model_update.update(rep_update)
|
||||
|
||||
@ -273,16 +280,20 @@ class VMAXCommon(object):
|
||||
if volume.group_id is not None:
|
||||
if (volume_utils.is_group_a_cg_snapshot_type(volume.group)
|
||||
or volume.group.is_replicated):
|
||||
LOG.debug("Adding volume %(vol_id)s to group %(grp_id)s",
|
||||
{'vol_id': volume.id, 'grp_id': volume.group_id})
|
||||
self._add_new_volume_to_volume_group(
|
||||
group_id = volume.group_id
|
||||
group_name = self._add_new_volume_to_volume_group(
|
||||
volume, volume_dict['device_id'], volume_name,
|
||||
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.",
|
||||
{'name': volume_name, 'dict': volume_dict})
|
||||
model_update.update(
|
||||
{'provider_location': six.text_type(volume_dict)})
|
||||
|
||||
return model_update
|
||||
|
||||
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 extra_specs: the extra specifications
|
||||
:param rep_driver_data: the replication driver data, optional
|
||||
:returns: group_name string
|
||||
"""
|
||||
self.utils.check_replication_matched(volume, extra_specs)
|
||||
group_name = self.provision.get_or_create_volume_group(
|
||||
@ -306,6 +318,7 @@ class VMAXCommon(object):
|
||||
if volume.group.is_replicated:
|
||||
self.masking.add_remote_vols_to_volume_group(
|
||||
volume, volume.group, extra_specs, rep_driver_data)
|
||||
return group_name
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot.
|
||||
@ -316,7 +329,7 @@ class VMAXCommon(object):
|
||||
:raises: VolumeBackendAPIException:
|
||||
"""
|
||||
LOG.debug("Entering create_volume_from_snapshot.")
|
||||
model_update = {}
|
||||
model_update, rep_info_dict = {}, {}
|
||||
extra_specs = self._initial_setup(volume)
|
||||
|
||||
# Check if legacy snapshot
|
||||
@ -330,12 +343,19 @@ class VMAXCommon(object):
|
||||
|
||||
# Set-up volume replication, if enabled
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
rep_update = self._replicate_volume(volume, snapshot['name'],
|
||||
clone_dict, extra_specs)
|
||||
rep_update, rep_info_dict = (
|
||||
self._replicate_volume(
|
||||
volume, snapshot['name'], clone_dict, extra_specs))
|
||||
model_update.update(rep_update)
|
||||
|
||||
model_update.update(
|
||||
{'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
|
||||
|
||||
def create_cloned_volume(self, clone_volume, source_volume):
|
||||
@ -345,19 +365,23 @@ class VMAXCommon(object):
|
||||
:param source_volume: volume object
|
||||
:returns: model_update, dict
|
||||
"""
|
||||
model_update = {}
|
||||
model_update, rep_info_dict = {}, {}
|
||||
extra_specs = self._initial_setup(clone_volume)
|
||||
clone_dict = self._create_cloned_volume(clone_volume, source_volume,
|
||||
extra_specs)
|
||||
|
||||
# Set-up volume replication, if enabled
|
||||
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)
|
||||
model_update.update(rep_update)
|
||||
|
||||
model_update.update(
|
||||
{'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
|
||||
|
||||
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 delete_src: flag to indicate if source should be deleted on
|
||||
if replication fails
|
||||
:returns: replication model_update
|
||||
:returns: replication model_update, rep_info_dict
|
||||
"""
|
||||
array = volume_dict['array']
|
||||
try:
|
||||
device_id = volume_dict['device_id']
|
||||
replication_status, replication_driver_data = (
|
||||
replication_status, replication_driver_data, rep_info_dict = (
|
||||
self.setup_volume_replication(
|
||||
array, volume, device_id, extra_specs))
|
||||
except Exception:
|
||||
@ -385,7 +409,7 @@ class VMAXCommon(object):
|
||||
raise
|
||||
return ({'replication_status': replication_status,
|
||||
'replication_driver_data': six.text_type(
|
||||
replication_driver_data)})
|
||||
replication_driver_data)}, rep_info_dict)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a EMC(VMAX) volume.
|
||||
@ -408,6 +432,8 @@ class VMAXCommon(object):
|
||||
extra_specs = self._initial_setup(volume)
|
||||
snapshot_dict = self._create_cloned_volume(
|
||||
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)}
|
||||
return model_update
|
||||
|
||||
@ -443,6 +469,8 @@ class VMAXCommon(object):
|
||||
|
||||
LOG.info("Leaving delete_snapshot: %(ssname)s.",
|
||||
{'ssname': snap_name})
|
||||
self.volume_metadata.capture_snapshot_info(
|
||||
volume, extra_specs, 'deleteSnapshot', None)
|
||||
|
||||
def _remove_members(self, array, volume, device_id,
|
||||
extra_specs, connector, is_multiattach,
|
||||
@ -474,6 +502,7 @@ class VMAXCommon(object):
|
||||
:param volume: the volume Object
|
||||
:param connector: the connector Object
|
||||
"""
|
||||
mv_list, sg_list = None, None
|
||||
extra_specs = self._initial_setup(volume)
|
||||
if 'qos' in extra_specs:
|
||||
del extra_specs['qos']
|
||||
@ -535,6 +564,14 @@ class VMAXCommon(object):
|
||||
self.masking.attempt_ig_cleanup(
|
||||
connector, self.protocol, rep_extra_specs[utils.ARRAY],
|
||||
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):
|
||||
"""Initializes the connection and returns device and connection info.
|
||||
@ -640,6 +677,17 @@ class VMAXCommon(object):
|
||||
self._find_ip_and_iqns(
|
||||
rep_extra_specs[utils.ARRAY], remote_port_group))
|
||||
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
|
||||
|
||||
def _attach_metro_volume(self, volume, connector, is_multiattach,
|
||||
@ -772,6 +820,9 @@ class VMAXCommon(object):
|
||||
self.provision.extend_volume(
|
||||
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. ",
|
||||
{'volume_name': volume_name})
|
||||
|
||||
@ -1095,21 +1146,33 @@ class VMAXCommon(object):
|
||||
"""
|
||||
LOG.debug("Getting masking views from volume")
|
||||
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
|
||||
for sg in storage_group_list:
|
||||
mvs = self.rest.get_masking_views_from_storage_group(
|
||||
array, sg)
|
||||
for mv in mvs:
|
||||
all_masking_view_list.append(mv)
|
||||
if host_compare:
|
||||
if host.lower() in mv.lower():
|
||||
host_maskingview_list.append(mv)
|
||||
mvs, __ = self._get_mvs_and_sgs_from_volume(array, device_id)
|
||||
for mv in mvs:
|
||||
all_masking_view_list.append(mv)
|
||||
if host_compare:
|
||||
if host.lower() in mv.lower():
|
||||
host_maskingview_list.append(mv)
|
||||
maskingview_list = (host_maskingview_list if host_compare else
|
||||
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):
|
||||
"""Necessary setup to accumulate the relevant information.
|
||||
|
||||
@ -1372,7 +1435,6 @@ class VMAXCommon(object):
|
||||
:param volume_name: the volume name
|
||||
:param volume_size: the volume size
|
||||
:param extra_specs: extra specifications
|
||||
:returns: int -- return code
|
||||
:returns: dict -- volume_dict
|
||||
:raises: VolumeBackendAPIException:
|
||||
"""
|
||||
@ -1521,6 +1583,12 @@ class VMAXCommon(object):
|
||||
'array': extra_specs[utils.ARRAY],
|
||||
'slo': extra_specs[utils.SLO],
|
||||
'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
|
||||
|
||||
def _delete_from_srp(self, array, device_id, volume_name,
|
||||
@ -1803,6 +1871,7 @@ class VMAXCommon(object):
|
||||
:returns: dict -- model_update
|
||||
"""
|
||||
LOG.info("Beginning manage existing volume process")
|
||||
rep_info_dict = {}
|
||||
array, device_id = self.utils.get_array_and_device_id(
|
||||
volume, external_ref)
|
||||
volume_id = volume.id
|
||||
@ -1822,9 +1891,9 @@ class VMAXCommon(object):
|
||||
|
||||
# Set-up volume replication, if enabled
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
rep_update = self._replicate_volume(volume, volume_name,
|
||||
provider_location,
|
||||
extra_specs, delete_src=False)
|
||||
rep_update, rep_info_dict = self._replicate_volume(
|
||||
volume, volume_name, provider_location,
|
||||
extra_specs, delete_src=False)
|
||||
model_update.update(rep_update)
|
||||
|
||||
else:
|
||||
@ -1832,6 +1901,9 @@ class VMAXCommon(object):
|
||||
self.masking.add_volume_to_default_storage_group(
|
||||
array, device_id, volume_name, extra_specs)
|
||||
|
||||
self.volume_metadata.capture_manage_existing(
|
||||
volume, rep_info_dict, device_id, extra_specs)
|
||||
|
||||
return model_update
|
||||
|
||||
def _check_lun_valid_for_cinder_management(
|
||||
@ -2423,7 +2495,7 @@ class VMAXCommon(object):
|
||||
else:
|
||||
if is_rep_enabled:
|
||||
# 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)
|
||||
model_update = {
|
||||
'replication_status': rep_status,
|
||||
@ -2447,6 +2519,11 @@ class VMAXCommon(object):
|
||||
array, volume, device_id, volume_name,
|
||||
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
|
||||
|
||||
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 target_device_id: the target device id
|
||||
:returns: replication_status -- str, replication_driver_data -- dict
|
||||
rep_info_dict -- dict
|
||||
"""
|
||||
rep_extra_specs = {'rep_mode': None}
|
||||
source_name = volume.name
|
||||
LOG.debug('Starting replication setup '
|
||||
'for volume: %s.', source_name)
|
||||
@ -2686,8 +2765,15 @@ class VMAXCommon(object):
|
||||
target_name)
|
||||
replication_status = REPLICATION_ENABLED
|
||||
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(
|
||||
self, array, device_id, volume_name, remote_array,
|
||||
@ -3779,6 +3865,9 @@ class VMAXCommon(object):
|
||||
LOG.exception(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
|
||||
|
||||
def _remove_remote_vols_from_volume_group(
|
||||
@ -4211,6 +4300,13 @@ class VMAXCommon(object):
|
||||
if vol_rep_status != fields.ReplicationStatus.ERROR:
|
||||
loc = vol.replication_driver_data
|
||||
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,
|
||||
'replication_status': vol_rep_status,
|
||||
'provider_location': loc,
|
||||
|
@ -96,6 +96,7 @@ class VMAXFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
||||
- Support for list manageable volumes and snapshots
|
||||
(bp/vmax-list-manage-existing)
|
||||
- Fix for SSL verification/cert application (bug #1772924)
|
||||
- Log VMAX metadata of a volume (bp vmax-metadata)
|
||||
"""
|
||||
|
||||
VERSION = "3.2.0"
|
||||
|
@ -101,6 +101,7 @@ class VMAXISCSIDriver(san.SanISCSIDriver):
|
||||
- Support for list manageable volumes and snapshots
|
||||
(bp/vmax-list-manage-existing)
|
||||
- Fix for SSL verification/cert application (bug #1772924)
|
||||
- Log VMAX metadata of a volume (bp vmax-metadata)
|
||||
"""
|
||||
|
||||
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"))
|
||||
"""
|
||||
version, major_version = None, None
|
||||
target_uri = "/%s/system/version" % U4V_VERSION
|
||||
response = self._get_request(target_uri, 'version')
|
||||
response = self.get_unisphere_version()
|
||||
if response and response.get('version'):
|
||||
version = response['version']
|
||||
version_list = version.split('.')
|
||||
major_version = version_list[0][1] + version_list[1]
|
||||
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):
|
||||
"""Returns the details of a storage pool.
|
||||
|
||||
|
@ -690,6 +690,36 @@ class VMAXUtils(object):
|
||||
return True
|
||||
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
|
||||
def get_temp_failover_grp_name(rep_config):
|
||||
"""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…
Reference in New Issue
Block a user