cinder/cinder/volume/drivers/dell_emc/powermax/metadata.py

676 lines
27 KiB
Python

# Copyright (c) 2017-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.powermax 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 PowerMaxVolumeMetadata(object):
"""Gathers PowerMax/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.PowerMaxUtils()
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 PowerMax/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),
openstack_name=volume.display_name,
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),
openstack_name=volume.display_name,
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),
openstack_name=volume.display_name,
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)),
openstack_name=source.display_name,
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),
openstack_name=volume.display_name,
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),
openstack_name=volume.display_name,
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 PowerMax/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),
openstack_name=volume.display_name,
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, 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 PowerMax/VMAX device id
:param array: the PowerMax/VMAX serialnumber
:param srp: PowerMax/VMAX SRP
:param target_slo: volume name
:param target_workload: the PowerMax/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),
openstack_name=volume.display_name,
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)
@debug_required
def capture_delete_info(self, volume):
"""Captures delete info in volume metadata
:param volume: the volume object
"""
datadict = self.gather_volume_info(
volume.id, 'delete', False,
identifier_name=self.utils.get_volume_element_name(volume.id),
openstack_name=volume.display_name)
volume_metadata = self.update_volume_info_metadata(
datadict, self.version_dict)
self.print_pretty_table(volume_metadata)