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:
Helen Walsh 2018-03-28 18:08:00 +01:00
parent 512fd07124
commit 4662ead8c3
8 changed files with 1106 additions and 43 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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"

View File

@ -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"

View 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)

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,4 @@
---
features:
- |
Log VMAX specific metadata of a volume if debug is enabled.