cinder/cinder/volume/drivers/fujitsu/eternus_dx_common.py

2163 lines
84 KiB
Python

# Copyright (c) 2015 FUJITSU LIMITED
# Copyright (c) 2012 EMC Corporation.
# Copyright (c) 2012 OpenStack Foundation
# 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.
#
"""
Cinder Volume driver for Fujitsu ETERNUS DX S3 series.
"""
import ast
import base64
import hashlib
import six
import time
from xml.etree.ElementTree import parse
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import units
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
try:
import pywbem
pywbemAvailable = True
except ImportError:
pywbemAvailable = False
VOL_PREFIX = "FJosv_"
RAIDGROUP = 2
TPPOOL = 5
SNAPOPC = 4
OPC = 5
RETURN_TO_RESOURCEPOOL = 19
DETACH = 8
INITIALIZED = 2
UNSYNCHRONIZED = 3
BROKEN = 5
PREPARED = 11
REPL = "FUJITSU_ReplicationService"
STOR_CONF = "FUJITSU_StorageConfigurationService"
CTRL_CONF = "FUJITSU_ControllerConfigurationService"
STOR_HWID = "FUJITSU_StorageHardwareIDManagementService"
UNDEF_MSG = 'Undefined Error!!'
JOB_RETRIES = 60
JOB_INTERVAL_SEC = 10
# Error code keyword.
VOLUMENAME_IN_USE = 32788
COPYSESSION_NOT_EXIST = 32793
LUNAME_IN_USE = 4102
LUNAME_NOT_EXIST = 4097 # Only for InvokeMethod(HidePaths).
EC_REC = 3
FJ_ETERNUS_DX_OPT_opts = [
cfg.StrOpt('cinder_eternus_config_file',
default='/etc/cinder/cinder_fujitsu_eternus_dx.xml',
help='config file for cinder eternus_dx volume driver'),
]
POOL_TYPE_dic = {
RAIDGROUP: 'RAID_GROUP',
TPPOOL: 'Thinporvisioning_POOL',
}
OPERATION_dic = {
SNAPOPC: RETURN_TO_RESOURCEPOOL,
OPC: DETACH,
EC_REC: DETACH,
}
RETCODE_dic = {
'0': 'Success',
'1': 'Method Not Supported',
'4': 'Failed',
'5': 'Invalid Parameter',
'4096': 'Method Parameters Checked - Job Started',
'4097': 'Size Not Supported',
'4101': 'Target/initiator combination already exposed',
'4102': 'Requested logical unit number in use',
'32769': 'Maximum number of Logical Volume in a RAID group '
'has been reached',
'32770': 'Maximum number of Logical Volume in the storage device '
'has been reached',
'32771': 'Maximum number of registered Host WWN '
'has been reached',
'32772': 'Maximum number of affinity group has been reached',
'32773': 'Maximum number of host affinity has been reached',
'32785': 'The RAID group is in busy state',
'32786': 'The Logical Volume is in busy state',
'32787': 'The device is in busy state',
'32788': 'Element Name is in use',
'32792': 'No Copy License',
'32793': 'Session is not exist',
'32796': 'Quick Format Error',
'32801': 'The CA port is in invalid setting',
'32802': 'The Logical Volume is Mainframe volume',
'32803': 'The RAID group is not operative',
'32804': 'The Logical Volume is not operative',
'32808': 'No Thin Provisioning License',
'32809': 'The Logical Element is ODX volume',
'32811': 'This operation cannot be performed to the NAS resources',
'32812': 'This operation cannot be performed to the Storage Cluster '
'resources',
'32816': 'Fatal error generic',
'35302': 'Invalid LogicalElement',
'35304': 'LogicalElement state error',
'35316': 'Multi-hop error',
'35318': 'Maximum number of multi-hop has been reached',
'35324': 'RAID is broken',
'35331': 'Maximum number of session has been reached(per device)',
'35333': 'Maximum number of session has been reached(per SourceElement)',
'35334': 'Maximum number of session has been reached(per TargetElement)',
'35335': 'Maximum number of Snapshot generation has been reached '
'(per SourceElement)',
'35346': 'Copy table size is not setup',
'35347': 'Copy table size is not enough',
}
CONF.register_opts(FJ_ETERNUS_DX_OPT_opts)
class FJDXCommon(object):
"""Common code that does not depend on protocol."""
VERSION = "1.3.0"
stats = {
'driver_version': VERSION,
'free_capacity_gb': 0,
'reserved_percentage': 0,
'storage_protocol': None,
'total_capacity_gb': 0,
'vendor_name': 'FUJITSU',
'QoS_support': False,
'volume_backend_name': None,
}
def __init__(self, prtcl, configuration=None):
if not pywbemAvailable:
LOG.error(_LE('import pywbem failed!! '
'pywbem is necessary for this volume driver.'))
self.protocol = prtcl
self.configuration = configuration
self.configuration.append_config_values(FJ_ETERNUS_DX_OPT_opts)
if prtcl == 'iSCSI':
# Get iSCSI ipaddress from driver configuration file.
self.configuration.iscsi_ip_address = (
self._get_drvcfg('EternusISCSIIP'))
def create_volume(self, volume):
"""Create volume on ETERNUS."""
LOG.debug('create_volume, '
'volume id: %(vid)s, volume size: %(vsize)s.',
{'vid': volume['id'], 'vsize': volume['size']})
self.conn = self._get_eternus_connection()
volumesize = int(volume['size']) * units.Gi
volumename = self._create_volume_name(volume['id'])
LOG.debug('create_volume, volumename: %(volumename)s, '
'volumesize: %(volumesize)u.',
{'volumename': volumename,
'volumesize': volumesize})
# get poolname from driver configuration file
eternus_pool = self._get_drvcfg('EternusPool')
# Existence check the pool
pool = self._find_pool(eternus_pool)
if 'RSP' in pool['InstanceID']:
pooltype = RAIDGROUP
else:
pooltype = TPPOOL
configservice = self._find_eternus_service(STOR_CONF)
if configservice is None:
msg = (_('create_volume, volume: %(volume)s, '
'volumename: %(volumename)s, '
'eternus_pool: %(eternus_pool)s, '
'Storage Configuration Service not found.')
% {'volume': volume,
'volumename': volumename,
'eternus_pool': eternus_pool})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('create_volume, '
'CreateOrModifyElementFromStoragePool, '
'ConfigService: %(service)s, '
'ElementName: %(volumename)s, '
'InPool: %(eternus_pool)s, '
'ElementType: %(pooltype)u, '
'Size: %(volumesize)u.',
{'service': configservice,
'volumename': volumename,
'eternus_pool': eternus_pool,
'pooltype': pooltype,
'volumesize': volumesize})
# Invoke method for create volume
rc, errordesc, job = self._exec_eternus_service(
'CreateOrModifyElementFromStoragePool',
configservice,
ElementName=volumename,
InPool=pool,
ElementType=self._pywbem_uint(pooltype, '16'),
Size=self._pywbem_uint(volumesize, '64'))
if rc == VOLUMENAME_IN_USE: # Element Name is in use
LOG.warning(_LW('create_volume, '
'volumename: %(volumename)s, '
'Element Name is in use.'),
{'volumename': volumename})
vol_instance = self._find_lun(volume)
element = vol_instance
elif rc != 0:
msg = (_('create_volume, '
'volumename: %(volumename)s, '
'poolname: %(eternus_pool)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'volumename': volumename,
'eternus_pool': eternus_pool,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
element = job['TheElement']
# Get eternus model name
try:
systemnamelist = (
self._enum_eternus_instances('FUJITSU_StorageProduct'))
except Exception:
msg = (_('create_volume, '
'volume: %(volume)s, '
'EnumerateInstances, '
'cannot connect to ETERNUS.')
% {'volume': volume})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('create_volume, '
'volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s, '
'Backend: %(backend)s, '
'Pool Name: %(eternus_pool)s, '
'Pool Type: %(pooltype)s.',
{'volumename': volumename,
'rc': rc,
'errordesc': errordesc,
'backend': systemnamelist[0]['IdentifyingNumber'],
'eternus_pool': eternus_pool,
'pooltype': POOL_TYPE_dic[pooltype]})
# Create return value.
element_path = {
'classname': element.classname,
'keybindings': {
'CreationClassName': element['CreationClassName'],
'SystemName': element['SystemName'],
'DeviceID': element['DeviceID'],
'SystemCreationClassName': element['SystemCreationClassName']
}
}
volume_no = "0x" + element['DeviceID'][24:28]
metadata = {'FJ_Backend': systemnamelist[0]['IdentifyingNumber'],
'FJ_Volume_Name': volumename,
'FJ_Volume_No': volume_no,
'FJ_Pool_Name': eternus_pool,
'FJ_Pool_Type': POOL_TYPE_dic[pooltype]}
return (element_path, metadata)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
LOG.debug('create_volume_from_snapshot, '
'volume id: %(vid)s, volume size: %(vsize)s, '
'snapshot id: %(sid)s.',
{'vid': volume['id'], 'vsize': volume['size'],
'sid': snapshot['id']})
self.conn = self._get_eternus_connection()
source_volume_instance = self._find_lun(snapshot)
# Check the existence of source volume.
if source_volume_instance is None:
msg = _('create_volume_from_snapshot, '
'Source Volume does not exist in ETERNUS.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Create volume for the target volume.
(element_path, metadata) = self.create_volume(volume)
target_volume_instancename = self._create_eternus_instance_name(
element_path['classname'], element_path['keybindings'])
try:
target_volume_instance = (
self._get_eternus_instance(target_volume_instancename))
except Exception:
msg = (_('create_volume_from_snapshot, '
'target volume instancename: %(volume_instancename)s, '
'Get Instance Failed.')
% {'volume_instancename': target_volume_instancename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self._create_local_cloned_volume(target_volume_instance,
source_volume_instance)
return (element_path, metadata)
def create_cloned_volume(self, volume, src_vref):
"""Create clone of the specified volume."""
LOG.debug('create_cloned_volume, '
'tgt: (%(tid)s, %(tsize)s), src: (%(sid)s, %(ssize)s).',
{'tid': volume['id'], 'tsize': volume['size'],
'sid': src_vref['id'], 'ssize': src_vref['size']})
self.conn = self._get_eternus_connection()
source_volume_instance = self._find_lun(src_vref)
if source_volume_instance is None:
msg = _('create_cloned_volume, '
'Source Volume does not exist in ETERNUS.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
(element_path, metadata) = self.create_volume(volume)
target_volume_instancename = self._create_eternus_instance_name(
element_path['classname'], element_path['keybindings'])
try:
target_volume_instance = (
self._get_eternus_instance(target_volume_instancename))
except Exception:
msg = (_('create_cloned_volume, '
'target volume instancename: %(volume_instancename)s, '
'Get Instance Failed.')
% {'volume_instancename': target_volume_instancename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self._create_local_cloned_volume(target_volume_instance,
source_volume_instance)
return (element_path, metadata)
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def _create_local_cloned_volume(self, tgt_vol_instance, src_vol_instance):
"""Create local clone of the specified volume."""
s_volumename = src_vol_instance['ElementName']
t_volumename = tgt_vol_instance['ElementName']
LOG.debug('_create_local_cloned_volume, '
'tgt volume name: %(t_volumename)s, '
'src volume name: %(s_volumename)s, ',
{'t_volumename': t_volumename,
's_volumename': s_volumename})
# Get replicationservice for CreateElementReplica.
repservice = self._find_eternus_service(REPL)
if repservice is None:
msg = _('_create_local_cloned_volume, '
'Replication Service not found.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Invoke method for create cloned volume from volume.
rc, errordesc, job = self._exec_eternus_service(
'CreateElementReplica',
repservice,
SyncType=self._pywbem_uint(8, '16'),
SourceElement=src_vol_instance.path,
TargetElement=tgt_vol_instance.path)
if rc != 0:
msg = (_('_create_local_cloned_volume, '
'volumename: %(volumename)s, '
'sourcevolumename: %(sourcevolumename)s, '
'source volume instance: %(source_volume)s, '
'target volume instance: %(target_volume)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'volumename': t_volumename,
'sourcevolumename': s_volumename,
'source_volume': src_vol_instance.path,
'target_volume': tgt_vol_instance.path,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_create_local_cloned_volume, out: %(rc)s, %(job)s.',
{'rc': rc, 'job': job})
def delete_volume(self, volume):
"""Delete volume on ETERNUS."""
LOG.debug('delete_volume, volume id: %s.', volume['id'])
self.conn = self._get_eternus_connection()
vol_exist = self._delete_volume_setting(volume)
if not vol_exist:
LOG.debug('delete_volume, volume not found in 1st check.')
return False
# Check volume existence on ETERNUS again
# because volume is deleted when SnapOPC copysession is deleted.
vol_instance = self._find_lun(volume)
if vol_instance is None:
LOG.debug('delete_volume, volume not found in 2nd check, '
'but no problem.')
return True
self._delete_volume(vol_instance)
return True
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def _delete_volume_setting(self, volume):
"""Delete volume setting (HostAffinity, CopySession) on ETERNUS."""
LOG.debug('_delete_volume_setting, volume id: %s.', volume['id'])
# Check the existence of volume.
volumename = self._create_volume_name(volume['id'])
vol_instance = self._find_lun(volume)
if vol_instance is None:
LOG.info(_LI('_delete_volume_setting, volumename:%(volumename)s, '
'volume not found on ETERNUS. '),
{'volumename': volumename})
return False
# Delete host-affinity setting remained by unexpected error.
self._unmap_lun(volume, None, force=True)
# Check copy session relating to target volume.
cpsessionlist = self._find_copysession(vol_instance)
delete_copysession_list = []
wait_copysession_list = []
for cpsession in cpsessionlist:
LOG.debug('_delete_volume_setting, '
'volumename: %(volumename)s, '
'cpsession: %(cpsession)s.',
{'volumename': volumename,
'cpsession': cpsession})
if cpsession['SyncedElement'] == vol_instance.path:
# Copy target : other_volume --(copy)--> vol_instance
delete_copysession_list.append(cpsession)
elif cpsession['SystemElement'] == vol_instance.path:
# Copy source : vol_instance --(copy)--> other volume
wait_copysession_list.append(cpsession)
LOG.debug('_delete_volume_setting, '
'wait_cpsession: %(wait_cpsession)s, '
'delete_cpsession: %(delete_cpsession)s.',
{'wait_cpsession': wait_copysession_list,
'delete_cpsession': delete_copysession_list})
for cpsession in wait_copysession_list:
self._wait_for_copy_complete(cpsession)
for cpsession in delete_copysession_list:
self._delete_copysession(cpsession)
LOG.debug('_delete_volume_setting, '
'wait_cpsession: %(wait_cpsession)s, '
'delete_cpsession: %(delete_cpsession)s, complete.',
{'wait_cpsession': wait_copysession_list,
'delete_cpsession': delete_copysession_list})
return True
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def _delete_volume(self, vol_instance):
"""Delete volume on ETERNUS."""
LOG.debug('_delete_volume, volume name: %s.',
vol_instance['ElementName'])
volumename = vol_instance['ElementName']
configservice = self._find_eternus_service(STOR_CONF)
if configservice is None:
msg = (_('_delete_volume, volumename: %(volumename)s, '
'Storage Configuration Service not found.')
% {'volumename': volumename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_delete_volume, volumename: %(volumename)s, '
'vol_instance: %(vol_instance)s, '
'Method: ReturnToStoragePool.',
{'volumename': volumename,
'vol_instance': vol_instance.path})
# Invoke method for delete volume
rc, errordesc, job = self._exec_eternus_service(
'ReturnToStoragePool',
configservice,
TheElement=vol_instance.path)
if rc != 0:
msg = (_('_delete_volume, volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'volumename': volumename,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_delete_volume, volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.',
{'volumename': volumename,
'rc': rc,
'errordesc': errordesc})
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def create_snapshot(self, snapshot):
"""Create snapshot using SnapOPC."""
LOG.debug('create_snapshot, '
'snapshot id: %(sid)s, volume id: %(vid)s.',
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
self.conn = self._get_eternus_connection()
snapshotname = snapshot['name']
volumename = snapshot['volume_name']
vol_id = snapshot['volume_id']
volume = snapshot['volume']
d_volumename = self._create_volume_name(snapshot['id'])
s_volumename = self._create_volume_name(vol_id)
vol_instance = self._find_lun(volume)
repservice = self._find_eternus_service(REPL)
# Check the existence of volume.
if vol_instance is None:
# Volume not found on ETERNUS.
msg = (_('create_snapshot, '
'volumename: %(s_volumename)s, '
'source volume not found on ETERNUS.')
% {'s_volumename': s_volumename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if repservice is None:
msg = (_('create_snapshot, '
'volumename: %(volumename)s, '
'Replication Service not found.')
% {'volumename': volumename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Get poolname from driver configuration file.
eternus_pool = self._get_drvcfg('EternusSnapPool')
# Check the existence of pool
pool = self._find_pool(eternus_pool)
if pool is None:
msg = (_('create_snapshot, '
'eternus_pool: %(eternus_pool)s, '
'pool not found.')
% {'eternus_pool': eternus_pool})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('create_snapshot, '
'snapshotname: %(snapshotname)s, '
'source volume name: %(volumename)s, '
'vol_instance.path: %(vol_instance)s, '
'dest_volumename: %(d_volumename)s, '
'pool: %(pool)s, '
'Invoke CreateElementReplica.',
{'snapshotname': snapshotname,
'volumename': volumename,
'vol_instance': vol_instance.path,
'd_volumename': d_volumename,
'pool': pool})
# Invoke method for create snapshot
rc, errordesc, job = self._exec_eternus_service(
'CreateElementReplica',
repservice,
ElementName=d_volumename,
TargetPool=pool,
SyncType=self._pywbem_uint(7, '16'),
SourceElement=vol_instance.path)
if rc != 0:
msg = (_('create_snapshot, '
'snapshotname: %(snapshotname)s, '
'source volume name: %(volumename)s, '
'vol_instance.path: %(vol_instance)s, '
'dest volume name: %(d_volumename)s, '
'pool: %(pool)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'snapshotname': snapshotname,
'volumename': volumename,
'vol_instance': vol_instance.path,
'd_volumename': d_volumename,
'pool': pool,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
element = job['TargetElement']
LOG.debug('create_snapshot, '
'volumename:%(volumename)s, '
'Return code:%(rc)lu, '
'Error:%(errordesc)s.',
{'volumename': volumename,
'rc': rc,
'errordesc': errordesc})
# Create return value.
element_path = {
'classname': element.classname,
'keybindings': {
'CreationClassName': element['CreationClassName'],
'SystemName': element['SystemName'],
'DeviceID': element['DeviceID'],
'SystemCreationClassName': element['SystemCreationClassName']
}
}
sdv_no = "0x" + element['DeviceID'][24:28]
metadata = {'FJ_SDV_Name': d_volumename,
'FJ_SDV_No': sdv_no,
'FJ_Pool_Name': eternus_pool}
return (element_path, metadata)
def delete_snapshot(self, snapshot):
"""Delete snapshot."""
LOG.debug('delete_snapshot, '
'snapshot id: %(sid)s, volume id: %(vid)s.',
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
vol_exist = self.delete_volume(snapshot)
LOG.debug('delete_snapshot, vol_exist: %s.', vol_exist)
return vol_exist
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
LOG.debug('initialize_connection, '
'volume id: %(vid)s, protocol: %(prtcl)s.',
{'vid': volume['id'], 'prtcl': self.protocol})
self.conn = self._get_eternus_connection()
vol_instance = self._find_lun(volume)
# Check the existence of volume
if vol_instance is None:
# Volume not found
msg = (_('initialize_connection, '
'volume: %(volume)s, '
'Volume not found.')
% {'volume': volume['name']})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
target_portlist = self._get_target_port()
mapdata = self._get_mapdata(vol_instance, connector, target_portlist)
if mapdata:
# volume is already mapped
target_lun = mapdata.get('target_lun', None)
target_luns = mapdata.get('target_luns', None)
LOG.info(_LI('initialize_connection, '
'volume: %(volume)s, '
'target_lun: %(target_lun)s, '
'target_luns: %(target_luns)s, '
'Volume is already mapped.'),
{'volume': volume['name'],
'target_lun': target_lun,
'target_luns': target_luns})
else:
self._map_lun(vol_instance, connector, target_portlist)
mapdata = self._get_mapdata(vol_instance,
connector, target_portlist)
mapdata['target_discovered'] = True
mapdata['volume_id'] = volume['id']
if self.protocol == 'fc':
device_info = {'driver_volume_type': 'fibre_channel',
'data': mapdata}
elif self.protocol == 'iSCSI':
device_info = {'driver_volume_type': 'iscsi',
'data': mapdata}
LOG.debug('initialize_connection, '
'device_info:%(info)s.',
{'info': device_info})
return device_info
def terminate_connection(self, volume, connector, force=False, **kwargs):
"""Disallow connection from connector."""
LOG.debug('terminate_connection, '
'volume id: %(vid)s, protocol: %(prtcl)s, force: %(frc)s.',
{'vid': volume['id'], 'prtcl': self.protocol, 'frc': force})
self.conn = self._get_eternus_connection()
map_exist = self._unmap_lun(volume, connector)
LOG.debug('terminate_connection, map_exist: %s.', map_exist)
return map_exist
def build_fc_init_tgt_map(self, connector, target_wwn=None):
"""Build parameter for Zone Manager"""
LOG.debug('build_fc_init_tgt_map, target_wwn: %s.', target_wwn)
initiatorlist = self._find_initiator_names(connector)
if target_wwn is None:
target_wwn = []
target_portlist = self._get_target_port()
for target_port in target_portlist:
target_wwn.append(target_port['Name'])
init_tgt_map = {initiator: target_wwn for initiator in initiatorlist}
LOG.debug('build_fc_init_tgt_map, '
'initiator target mapping: %s.', init_tgt_map)
return init_tgt_map
def check_attached_volume_in_zone(self, connector):
"""Check Attached Volume in Same FC Zone or not"""
LOG.debug('check_attached_volume_in_zone, connector: %s.', connector)
aglist = self._find_affinity_group(connector)
if not aglist:
attached = False
else:
attached = True
LOG.debug('check_attached_volume_in_zone, attached: %s.', attached)
return attached
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
def extend_volume(self, volume, new_size):
"""Extend volume on ETERNUS."""
LOG.debug('extend_volume, volume id: %(vid)s, '
'size: %(size)s, new_size: %(nsize)s.',
{'vid': volume['id'],
'size': volume['size'], 'nsize': new_size})
self.conn = self._get_eternus_connection()
volumesize = new_size * units.Gi
volumename = self._create_volume_name(volume['id'])
# Get source volume instance.
vol_instance = self._find_lun(volume)
if vol_instance is None:
msg = (_('extend_volume, '
'volumename: %(volumename)s, '
'volume not found.')
% {'volumename': volumename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, volumename: %(volumename)s, '
'volumesize: %(volumesize)u, '
'volume instance: %(vol_instance)s.',
{'volumename': volumename,
'volumesize': volumesize,
'vol_instance': vol_instance.path})
# Get poolname from driver configuration file.
eternus_pool = self._get_drvcfg('EternusPool')
# Check the existence of volume.
pool = self._find_pool(eternus_pool)
if pool is None:
msg = (_('extend_volume, '
'eternus_pool: %(eternus_pool)s, '
'pool not found.')
% {'eternus_pool': eternus_pool})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Set pooltype.
if 'RSP' in pool['InstanceID']:
pooltype = RAIDGROUP
else:
pooltype = TPPOOL
configservice = self._find_eternus_service(STOR_CONF)
if configservice is None:
msg = (_('extend_volume, volume: %(volume)s, '
'volumename: %(volumename)s, '
'eternus_pool: %(eternus_pool)s, '
'Storage Configuration Service not found.')
% {'volume': volume,
'volumename': volumename,
'eternus_pool': eternus_pool})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, '
'CreateOrModifyElementFromStoragePool, '
'ConfigService: %(service)s, '
'ElementName: %(volumename)s, '
'InPool: %(eternus_pool)s, '
'ElementType: %(pooltype)u, '
'Size: %(volumesize)u, '
'TheElement: %(vol_instance)s.',
{'service': configservice,
'volumename': volumename,
'eternus_pool': eternus_pool,
'pooltype': pooltype,
'volumesize': volumesize,
'vol_instance': vol_instance.path})
# Invoke method for extend volume
rc, errordesc, job = self._exec_eternus_service(
'CreateOrModifyElementFromStoragePool',
configservice,
ElementName=volumename,
InPool=pool,
ElementType=self._pywbem_uint(pooltype, '16'),
Size=self._pywbem_uint(volumesize, '64'),
TheElement=vol_instance.path)
if rc != 0:
msg = (_('extend_volume, '
'volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s, '
'PoolType: %(pooltype)s.')
% {'volumename': volumename,
'rc': rc,
'errordesc': errordesc,
'pooltype': POOL_TYPE_dic[pooltype]})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('extend_volume, '
'volumename: %(volumename)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s, '
'Pool Name: %(eternus_pool)s, '
'Pool Type: %(pooltype)s.',
{'volumename': volumename,
'rc': rc,
'errordesc': errordesc,
'eternus_pool': eternus_pool,
'pooltype': POOL_TYPE_dic[pooltype]})
return eternus_pool
@lockutils.synchronized('ETERNUS-update', 'cinder-', True)
def update_volume_stats(self):
"""get pool capacity."""
self.conn = self._get_eternus_connection()
eternus_pool = self._get_drvcfg('EternusPool')
LOG.debug('update_volume_stats, pool name: %s.', eternus_pool)
pool = self._find_pool(eternus_pool, True)
if pool:
# pool is found
self.stats['total_capacity_gb'] = (
pool['TotalManagedSpace'] / units.Gi)
self.stats['free_capacity_gb'] = (
pool['RemainingManagedSpace'] / units.Gi)
else:
# if pool information is unknown, set 0 GB to capacity information
LOG.warning(_LW('update_volume_stats, '
'eternus_pool:%(eternus_pool)s, '
'specified pool is not found.'),
{'eternus_pool': eternus_pool})
self.stats['total_capacity_gb'] = 0
self.stats['free_capacity_gb'] = 0
self.stats['multiattach'] = True
LOG.debug('update_volume_stats, '
'eternus_pool:%(eternus_pool)s, '
'total capacity[%(total)s], '
'free capacity[%(free)s].',
{'eternus_pool': eternus_pool,
'total': self.stats['total_capacity_gb'],
'free': self.stats['free_capacity_gb']})
return (self.stats, eternus_pool)
def _get_mapdata(self, vol_instance, connector, target_portlist):
"""return mapping information."""
mapdata = None
multipath = connector.get('multipath', False)
LOG.debug('_get_mapdata, volume name: %(vname)s, '
'protocol: %(prtcl)s, multipath: %(mpath)s.',
{'vname': vol_instance['ElementName'],
'prtcl': self.protocol, 'mpath': multipath})
# find affinity group
# attach the connector and include the volume
aglist = self._find_affinity_group(connector, vol_instance)
if not aglist:
LOG.debug('_get_mapdata, ag_list:%s.', aglist)
else:
if self.protocol == 'fc':
mapdata = self._get_mapdata_fc(aglist, vol_instance,
target_portlist)
elif self.protocol == 'iSCSI':
mapdata = self._get_mapdata_iscsi(aglist, vol_instance,
multipath)
LOG.debug('_get_mapdata, mapdata: %s.', mapdata)
return mapdata
def _get_mapdata_fc(self, aglist, vol_instance, target_portlist):
"""_get_mapdata for FibreChannel."""
target_wwn = []
try:
ag_volmaplist = self._reference_eternus_names(
aglist[0],
ResultClass='CIM_ProtocolControllerForUnit')
vo_volmaplist = self._reference_eternus_names(
vol_instance.path,
ResultClass='CIM_ProtocolControllerForUnit')
except pywbem.CIM_Error:
msg = (_('_get_mapdata_fc, '
'getting host-affinity from aglist/vol_instance failed, '
'affinitygroup: %(ag)s, '
'ReferenceNames, '
'cannot connect to ETERNUS.')
% {'ag': aglist[0]})
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
volmap = None
for vo_volmap in vo_volmaplist:
if vo_volmap in ag_volmaplist:
volmap = vo_volmap
break
try:
volmapinstance = self._get_eternus_instance(
volmap,
LocalOnly=False)
except pywbem.CIM_Error:
msg = (_('_get_mapdata_fc, '
'getting host-affinity instance failed, '
'volmap: %(volmap)s, '
'GetInstance, '
'cannot connect to ETERNUS.')
% {'volmap': volmap})
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
target_lun = int(volmapinstance['DeviceNumber'], 16)
for target_port in target_portlist:
target_wwn.append(target_port['Name'])
mapdata = {'target_wwn': target_wwn,
'target_lun': target_lun}
LOG.debug('_get_mapdata_fc, mapdata: %s.', mapdata)
return mapdata
def _get_mapdata_iscsi(self, aglist, vol_instance, multipath):
"""_get_mapdata for iSCSI."""
target_portals = []
target_iqns = []
target_luns = []
try:
vo_volmaplist = self._reference_eternus_names(
vol_instance.path,
ResultClass='CIM_ProtocolControllerForUnit')
except Exception:
msg = (_('_get_mapdata_iscsi, '
'vol_instance: %(vol_instance)s, '
'ReferenceNames: CIM_ProtocolControllerForUnit, '
'cannot connect to ETERNUS.')
% {'vol_instance': vol_instance})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
target_properties_list = self._get_eternus_iscsi_properties()
target_list = [prop[0] for prop in target_properties_list]
properties_list = (
[(prop[1], prop[2]) for prop in target_properties_list])
for ag in aglist:
try:
iscsi_endpointlist = (
self._assoc_eternus_names(
ag,
AssocClass='FUJITSU_SAPAvailableForElement',
ResultClass='FUJITSU_iSCSIProtocolEndpoint'))
except Exception:
msg = (_('_get_mapdata_iscsi, '
'Associators: FUJITSU_SAPAvailableForElement, '
'cannot connect to ETERNUS.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
iscsi_endpoint = iscsi_endpointlist[0]
if iscsi_endpoint not in target_list:
continue
idx = target_list.index(iscsi_endpoint)
target_portal, target_iqn = properties_list[idx]
try:
ag_volmaplist = self._reference_eternus_names(
ag,
ResultClass='CIM_ProtocolControllerForUnit')
except Exception:
msg = (_('_get_mapdata_iscsi, '
'affinitygroup: %(ag)s, '
'ReferenceNames, '
'cannot connect to ETERNUS.')
% {'ag': ag})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
volmap = None
for vo_volmap in vo_volmaplist:
if vo_volmap in ag_volmaplist:
volmap = vo_volmap
break
if volmap is None:
continue
try:
volmapinstance = self._get_eternus_instance(
volmap,
LocalOnly=False)
except Exception:
msg = (_('_get_mapdata_iscsi, '
'volmap: %(volmap)s, '
'GetInstance, '
'cannot connect to ETERNUS.')
% {'volmap': volmap})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
target_lun = int(volmapinstance['DeviceNumber'], 16)
target_portals.append(target_portal)
target_iqns.append(target_iqn)
target_luns.append(target_lun)
if multipath:
mapdata = {'target_portals': target_portals,
'target_iqns': target_iqns,
'target_luns': target_luns}
else:
mapdata = {'target_portal': target_portals[0],
'target_iqn': target_iqns[0],
'target_lun': target_luns[0]}
LOG.debug('_get_mapdata_iscsi, mapdata: %s.', mapdata)
return mapdata
def _get_drvcfg(self, tagname, filename=None, multiple=False):
"""read from driver configuration file."""
if filename is None:
# set default configuration file name
filename = self.configuration.cinder_eternus_config_file
LOG.debug("_get_drvcfg, input[%(filename)s][%(tagname)s].",
{'filename': filename, 'tagname': tagname})
tree = parse(filename)
elem = tree.getroot()
ret = None
if not multiple:
ret = elem.findtext(".//" + tagname)
else:
ret = []
for e in elem.findall(".//" + tagname):
if (e.text is not None) and (e.text not in ret):
ret.append(e.text)
if not ret:
msg = (_('_get_drvcfg, '
'filename: %(filename)s, '
'tagname: %(tagname)s, '
'data is None!! '
'Please edit driver configuration file and correct.')
% {'filename': filename,
'tagname': tagname})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return ret
def _get_eternus_connection(self, filename=None):
"""return WBEM connection."""
LOG.debug('_get_eternus_connection, filename: %s.', filename)
ip = self._get_drvcfg('EternusIP', filename)
port = self._get_drvcfg('EternusPort', filename)
user = self._get_drvcfg('EternusUser', filename)
passwd = self._get_drvcfg('EternusPassword', filename)
url = 'http://' + ip + ':' + port
conn = pywbem.WBEMConnection(url, (user, passwd),
default_namespace='root/eternus')
if conn is None:
msg = (_('_get_eternus_connection, '
'filename: %(filename)s, '
'ip: %(ip)s, '
'port: %(port)s, '
'user: %(user)s, '
'passwd: ****, '
'url: %(url)s, '
'FAILED!!.')
% {'filename': filename,
'ip': ip,
'port': port,
'user': user,
'url': url})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_get_eternus_connection, conn: %s.', conn)
return conn
def _create_volume_name(self, id_code):
"""create volume_name on ETERNUS from id on OpenStack."""
LOG.debug('_create_volume_name, id_code: %s.', id_code)
if id_code is None:
msg = _('_create_volume_name, id_code is None.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
m = hashlib.md5()
m.update(id_code.encode('utf-8'))
# pylint: disable=E1121
volumename = base64.urlsafe_b64encode(m.digest()).decode()
ret = VOL_PREFIX + six.text_type(volumename)
LOG.debug('_create_volume_name, ret: %s', ret)
return ret
def _find_pool(self, eternus_pool, detail=False):
"""find Instance or InstanceName of pool by pool name on ETERNUS."""
LOG.debug('_find_pool, pool name: %s.', eternus_pool)
tppoollist = []
rgpoollist = []
# Get pools info form CIM instance(include info about instance path).
try:
tppoollist = self._enum_eternus_instances(
'FUJITSU_ThinProvisioningPool')
rgpoollist = self._enum_eternus_instances(
'FUJITSU_RAIDStoragePool')
except Exception:
msg = (_('_find_pool, '
'eternus_pool:%(eternus_pool)s, '
'EnumerateInstances, '
'cannot connect to ETERNUS.')
% {'eternus_pool': eternus_pool})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Make total pools list.
poollist = tppoollist + rgpoollist
# One eternus backend has only one special pool name
# so just use pool name can get the target pool.
for pool in poollist:
if pool['ElementName'] == eternus_pool:
poolinstance = pool
break
else:
poolinstance = None
if poolinstance is None:
ret = None
elif detail is True:
ret = poolinstance
else:
ret = poolinstance.path
LOG.debug('_find_pool, pool: %s.', ret)
return ret
def _find_eternus_service(self, classname):
"""find CIM instance about service information."""
LOG.debug('_find_eternus_service, '
'classname: %s.', classname)
try:
services = self._enum_eternus_instance_names(
six.text_type(classname))
except Exception:
msg = (_('_find_eternus_service, '
'classname: %(classname)s, '
'EnumerateInstanceNames, '
'cannot connect to ETERNUS.')
% {'classname': classname})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ret = services[0]
LOG.debug('_find_eternus_service, '
'classname: %(classname)s, '
'ret: %(ret)s.',
{'classname': classname, 'ret': ret})
return ret
@lockutils.synchronized('ETERNUS-SMIS-exec', 'cinder-', True)
def _exec_eternus_service(self, classname, instanceNameList, **param_dict):
"""Execute SMI-S Method."""
LOG.debug('_exec_eternus_service, '
'classname: %(a)s, '
'instanceNameList: %(b)s, '
'parameters: %(c)s.',
{'a': classname,
'b': instanceNameList,
'c': param_dict})
# Use InvokeMethod.
try:
rc, retdata = self.conn.InvokeMethod(
classname,
instanceNameList,
**param_dict)
except Exception:
if rc is None:
msg = (_('_exec_eternus_service, '
'classname: %(classname)s, '
'InvokeMethod, '
'cannot connect to ETERNUS.')
% {'classname': classname})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# If the result has job information, wait for job complete
if "Job" in retdata:
rc = self._wait_for_job_complete(self.conn, retdata)
errordesc = RETCODE_dic.get(six.text_type(rc), UNDEF_MSG)
ret = (rc, errordesc, retdata)
LOG.debug('_exec_eternus_service, '
'classname: %(a)s, '
'instanceNameList: %(b)s, '
'parameters: %(c)s, '
'Return code: %(rc)s, '
'Error: %(errordesc)s, '
'Retrun data: %(retdata)s.',
{'a': classname,
'b': instanceNameList,
'c': param_dict,
'rc': rc,
'errordesc': errordesc,
'retdata': retdata})
return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
def _enum_eternus_instances(self, classname):
"""Enumerate Instances."""
LOG.debug('_enum_eternus_instances, classname: %s.', classname)
ret = self.conn.EnumerateInstances(classname)
LOG.debug('_enum_eternus_instances, enum %d instances.', len(ret))
return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
def _enum_eternus_instance_names(self, classname):
"""Enumerate Instance Names."""
LOG.debug('_enum_eternus_instance_names, classname: %s.', classname)
ret = self.conn.EnumerateInstanceNames(classname)
LOG.debug('_enum_eternus_instance_names, enum %d names.', len(ret))
return ret
@lockutils.synchronized('ETERNUS-SMIS-getinstance', 'cinder-', True)
def _get_eternus_instance(self, classname, **param_dict):
"""Get Instance."""
LOG.debug('_get_eternus_instance, '
'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict})
ret = self.conn.GetInstance(classname, **param_dict)
LOG.debug('_get_eternus_instance, ret: %s.', ret)
return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
def _assoc_eternus(self, classname, **param_dict):
"""Associator."""
LOG.debug('_assoc_eternus, '
'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict})
ret = self.conn.Associators(classname, **param_dict)
LOG.debug('_assoc_eternus, enum %d instances.', len(ret))
return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
def _assoc_eternus_names(self, classname, **param_dict):
"""Associator Names."""
LOG.debug('_assoc_eternus_names, '
'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict})
ret = self.conn.AssociatorNames(classname, **param_dict)
LOG.debug('_assoc_eternus_names, enum %d names.', len(ret))
return ret
@lockutils.synchronized('ETERNUS-SMIS-other', 'cinder-', True)
def _reference_eternus_names(self, classname, **param_dict):
"""Refference Names."""
LOG.debug('_reference_eternus_names, '
'classname: %(cls)s, param: %(param)s.',
{'cls': classname, 'param': param_dict})
ret = self.conn.ReferenceNames(classname, **param_dict)
LOG.debug('_reference_eternus_names, enum %d names.', len(ret))
return ret
def _create_eternus_instance_name(self, classname, bindings):
"""create CIM InstanceName from classname and bindings."""
LOG.debug('_create_eternus_instance_name, '
'classname: %(cls)s, bindings: %(bind)s.',
{'cls': classname, 'bind': bindings})
instancename = None
try:
instancename = pywbem.CIMInstanceName(
classname,
namespace='root/eternus',
keybindings=bindings)
except NameError:
instancename = None
LOG.debug('_create_eternus_instance_name, ret: %s.', instancename)
return instancename
def _find_lun(self, volume):
"""find lun instance from volume class or volumename on ETERNUS."""
LOG.debug('_find_lun, volume id: %s.', volume['id'])
volumeinstance = None
volumename = self._create_volume_name(volume['id'])
try:
location = ast.literal_eval(volume['provider_location'])
classname = location['classname']
bindings = location['keybindings']
if classname and bindings:
LOG.debug('_find_lun, '
'classname: %(classname)s, '
'bindings: %(bindings)s.',
{'classname': classname,
'bindings': bindings})
volume_instance_name = (
self._create_eternus_instance_name(classname, bindings))
LOG.debug('_find_lun, '
'volume_insatnce_name: %(volume_instance_name)s.',
{'volume_instance_name': volume_instance_name})
vol_instance = (
self._get_eternus_instance(volume_instance_name))
if vol_instance['ElementName'] == volumename:
volumeinstance = vol_instance
except Exception:
volumeinstance = None
LOG.debug('_find_lun, '
'Cannot get volume instance from provider location, '
'Search all volume using EnumerateInstanceNames.')
if volumeinstance is None:
# for old version
LOG.debug('_find_lun, '
'volumename: %(volumename)s.',
{'volumename': volumename})
# get volume instance from volumename on ETERNUS
try:
namelist = self._enum_eternus_instance_names(
'FUJITSU_StorageVolume')
except Exception:
msg = (_('_find_lun, '
'volumename: %(volumename)s, '
'EnumerateInstanceNames, '
'cannot connect to ETERNUS.')
% {'volumename': volumename})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for name in namelist:
try:
vol_instance = self._get_eternus_instance(name)
if vol_instance['ElementName'] == volumename:
volumeinstance = vol_instance
path = volumeinstance.path
LOG.debug('_find_lun, '
'volumename: %(volumename)s, '
'vol_instance: %(vol_instance)s.',
{'volumename': volumename,
'vol_instance': path})
break
except Exception:
continue
else:
LOG.debug('_find_lun, '
'volumename: %(volumename)s, '
'volume not found on ETERNUS.',
{'volumename': volumename})
LOG.debug('_find_lun, ret: %s.', volumeinstance)
return volumeinstance
def _find_copysession(self, vol_instance):
"""find copysession from volumename on ETERNUS."""
LOG.debug('_find_copysession, volume name: %s.',
vol_instance['ElementName'])
try:
cpsessionlist = self.conn.ReferenceNames(
vol_instance.path,
ResultClass='FUJITSU_StorageSynchronized')
except Exception:
msg = (_('_find_copysession, '
'ReferenceNames, '
'vol_instance: %(vol_instance_path)s, '
'Cannot connect to ETERNUS.')
% {'vol_instance_path': vol_instance.path})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_find_copysession, '
'cpsessionlist: %(cpsessionlist)s.',
{'cpsessionlist': cpsessionlist})
LOG.debug('_find_copysession, ret: %s.', cpsessionlist)
return cpsessionlist
def _wait_for_copy_complete(self, cpsession):
"""Wait for the completion of copy."""
LOG.debug('_wait_for_copy_complete, cpsession: %s.', cpsession)
cpsession_instance = None
while True:
try:
cpsession_instance = self.conn.GetInstance(
cpsession,
LocalOnly=False)
except Exception:
cpsession_instance = None
# if copy session is none,
# it means copy session was finished,break and return
if cpsession_instance is None:
break
LOG.debug('_wait_for_copy_complete, '
'find target copysession, '
'wait for end of copysession.')
if cpsession_instance['CopyState'] == BROKEN:
msg = (_('_wait_for_copy_complete, '
'cpsession: %(cpsession)s, '
'copysession state is BROKEN.')
% {'cpsession': cpsession})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
time.sleep(10)
def _delete_copysession(self, cpsession):
"""delete copysession."""
LOG.debug('_delete_copysession: cpssession: %s.', cpsession)
try:
cpsession_instance = self._get_eternus_instance(
cpsession, LocalOnly=False)
except Exception:
LOG.info(_LI('_delete_copysession, '
'The copysession was already completed.'))
return
copytype = cpsession_instance['CopyType']
# set oparation code
# SnapOPC: 19 (Return To ResourcePool)
# OPC:8 (Detach)
# EC/REC:8 (Detach)
operation = OPERATION_dic.get(copytype, None)
if operation is None:
msg = (_('_delete_copysession, '
'copy session type is undefined! '
'copy session: %(cpsession)s, '
'copy type: %(copytype)s.')
% {'cpsession': cpsession,
'copytype': copytype})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
repservice = self._find_eternus_service(REPL)
if repservice is None:
msg = (_('_delete_copysession, '
'Cannot find Replication Service'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Invoke method for delete copysession
rc, errordesc, job = self._exec_eternus_service(
'ModifyReplicaSynchronization',
repservice,
Operation=self._pywbem_uint(operation, '16'),
Synchronization=cpsession,
Force=True,
WaitForCopyState=self._pywbem_uint(15, '16'))
LOG.debug('_delete_copysession, '
'copysession: %(cpsession)s, '
'operation: %(operation)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.',
{'cpsession': cpsession,
'operation': operation,
'rc': rc,
'errordesc': errordesc})
if rc == COPYSESSION_NOT_EXIST:
LOG.debug('_delete_copysession, '
'cpsession: %(cpsession)s, '
'copysession is not exist.',
{'cpsession': cpsession})
elif rc != 0:
msg = (_('_delete_copysession, '
'copysession: %(cpsession)s, '
'operation: %(operation)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'cpsession': cpsession,
'operation': operation,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _get_target_port(self):
"""return target portid."""
LOG.debug('_get_target_port, protocol: %s.', self.protocol)
target_portlist = []
if self.protocol == 'fc':
prtcl_endpoint = 'FUJITSU_SCSIProtocolEndpoint'
connection_type = 2
elif self.protocol == 'iSCSI':
prtcl_endpoint = 'FUJITSU_iSCSIProtocolEndpoint'
connection_type = 7
try:
tgtportlist = self._enum_eternus_instances(prtcl_endpoint)
except Exception:
msg = (_('_get_target_port, '
'EnumerateInstances, '
'cannot connect to ETERNUS.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for tgtport in tgtportlist:
# Check : protocol of tgtport
if tgtport['ConnectionType'] != connection_type:
continue
# Check : if port is for remote copy, continue
if (tgtport['RAMode'] & 0x7B) != 0x00:
continue
# Check : if port is for StorageCluster, continue
if 'SCGroupNo' in tgtport:
continue
target_portlist.append(tgtport)
LOG.debug('_get_target_port, '
'connection type: %(cont)s, '
'ramode: %(ramode)s.',
{'cont': tgtport['ConnectionType'],
'ramode': tgtport['RAMode']})
LOG.debug('_get_target_port, '
'target port: %(target_portid)s.',
{'target_portid': target_portlist})
if len(target_portlist) == 0:
msg = (_('_get_target_port, '
'protcol: %(protocol)s, '
'target_port not found.')
% {'protocol': self.protocol})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_get_target_port, ret: %s.', target_portlist)
return target_portlist
@lockutils.synchronized('ETERNUS-connect', 'cinder-', True)
def _map_lun(self, vol_instance, connector, targetlist=None):
"""map volume to host."""
volumename = vol_instance['ElementName']
LOG.debug('_map_lun, '
'volume name: %(vname)s, connector: %(connector)s.',
{'vname': volumename, 'connector': connector})
volume_uid = vol_instance['Name']
initiatorlist = self._find_initiator_names(connector)
aglist = self._find_affinity_group(connector)
configservice = self._find_eternus_service(CTRL_CONF)
if targetlist is None:
targetlist = self._get_target_port()
if configservice is None:
msg = (_('_map_lun, '
'vol_instance.path:%(vol)s, '
'volumename: %(volumename)s, '
'volume_uid: %(uid)s, '
'initiator: %(initiator)s, '
'target: %(tgt)s, '
'aglist: %(aglist)s, '
'Storage Configuration Service not found.')
% {'vol': vol_instance.path,
'volumename': volumename,
'uid': volume_uid,
'initiator': initiatorlist,
'tgt': targetlist,
'aglist': aglist})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_map_lun, '
'vol_instance.path: %(vol_instance)s, '
'volumename:%(volumename)s, '
'initiator:%(initiator)s, '
'target:%(tgt)s.',
{'vol_instance': vol_instance.path,
'volumename': [volumename],
'initiator': initiatorlist,
'tgt': targetlist})
if not aglist:
# Create affinity group and set host-affinity.
for target in targetlist:
LOG.debug('_map_lun, '
'lun_name: %(volume_uid)s, '
'Initiator: %(initiator)s, '
'target: %(target)s.',
{'volume_uid': [volume_uid],
'initiator': initiatorlist,
'target': target['Name']})
rc, errordesc, job = self._exec_eternus_service(
'ExposePaths',
configservice,
LUNames=[volume_uid],
InitiatorPortIDs=initiatorlist,
TargetPortIDs=[target['Name']],
DeviceAccesses=[self._pywbem_uint(2, '16')])
LOG.debug('_map_lun, '
'Error: %(errordesc)s, '
'Return code: %(rc)lu, '
'Create affinitygroup and set host-affinity.',
{'errordesc': errordesc,
'rc': rc})
if rc != 0 and rc != LUNAME_IN_USE:
LOG.warning(_LW('_map_lun, '
'lun_name: %(volume_uid)s, '
'Initiator: %(initiator)s, '
'target: %(target)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.'),
{'volume_uid': [volume_uid],
'initiator': initiatorlist,
'target': target['Name'],
'rc': rc,
'errordesc': errordesc})
else:
# Add lun to affinity group
for ag in aglist:
LOG.debug('_map_lun, '
'ag: %(ag)s, lun_name: %(volume_uid)s.',
{'ag': ag,
'volume_uid': volume_uid})
rc, errordesc, job = self._exec_eternus_service(
'ExposePaths',
configservice, LUNames=[volume_uid],
DeviceAccesses=[self._pywbem_uint(2, '16')],
ProtocolControllers=[ag])
LOG.debug('_map_lun, '
'Error: %(errordesc)s, '
'Return code: %(rc)lu, '
'Add lun to affinity group.',
{'errordesc': errordesc,
'rc': rc})
if rc != 0 and rc != LUNAME_IN_USE:
LOG.warning(_LW('_map_lun, '
'lun_name: %(volume_uid)s, '
'Initiator: %(initiator)s, '
'ag: %(ag)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.'),
{'volume_uid': [volume_uid],
'initiator': initiatorlist,
'ag': ag,
'rc': rc,
'errordesc': errordesc})
def _find_initiator_names(self, connector):
"""return initiator names."""
initiatornamelist = []
if self.protocol == 'fc' and connector['wwpns']:
LOG.debug('_find_initiator_names, wwpns: %s.',
connector['wwpns'])
initiatornamelist = connector['wwpns']
elif self.protocol == 'iSCSI' and connector['initiator']:
LOG.debug('_find_initiator_names, initiator: %s.',
connector['initiator'])
initiatornamelist.append(connector['initiator'])
if not initiatornamelist:
msg = (_('_find_initiator_names, '
'connector: %(connector)s, '
'initiator not found.')
% {'connector': connector})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_find_initiator_names, '
'initiator list: %(initiator)s.',
{'initiator': initiatornamelist})
return initiatornamelist
def _find_affinity_group(self, connector, vol_instance=None):
"""find affinity group from connector."""
LOG.debug('_find_affinity_group, vol_instance: %s.', vol_instance)
affinity_grouplist = []
initiatorlist = self._find_initiator_names(connector)
if vol_instance is None:
try:
aglist = self._enum_eternus_instance_names(
'FUJITSU_AffinityGroupController')
except Exception:
msg = (_('_find_affinity_group, '
'connector: %(connector)s, '
'EnumerateInstanceNames, '
'cannot connect to ETERNUS.')
% {'connector': connector})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_find_affinity_group,'
'affinity_groups:%s', aglist)
else:
try:
aglist = self._assoc_eternus_names(
vol_instance.path,
AssocClass='FUJITSU_ProtocolControllerForUnit',
ResultClass='FUJITSU_AffinityGroupController')
except Exception:
msg = (_('_find_affinity_group,'
'connector: %(connector)s,'
'AssocNames: FUJITSU_ProtocolControllerForUnit, '
'cannot connect to ETERNUS.')
% {'connector': connector})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_find_affinity_group, '
'vol_instance.path: %(volume)s, '
'affinity_groups: %(aglist)s.',
{'volume': vol_instance.path,
'aglist': aglist})
for ag in aglist:
try:
hostaglist = self._assoc_eternus(
ag,
AssocClass='FUJITSU_AuthorizedTarget',
ResultClass='FUJITSU_AuthorizedPrivilege')
except Exception:
msg = (_('_find_affinity_group, '
'connector: %(connector)s, '
'Associators: FUJITSU_AuthorizedTarget, '
'cannot connect to ETERNUS.')
% {'connector': connector})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for hostag in hostaglist:
for initiator in initiatorlist:
if initiator.lower() not in hostag['InstanceID'].lower():
continue
LOG.debug('_find_affinity_group, '
'AffinityGroup: %(ag)s.', {'ag': ag})
affinity_grouplist.append(ag)
break
break
LOG.debug('_find_affinity_group, '
'initiators: %(initiator)s, '
'affinity_group: %(affinity_group)s.',
{'initiator': initiatorlist,
'affinity_group': affinity_grouplist})
return affinity_grouplist
@lockutils.synchronized('ETERNUS-connect', 'cinder-', True)
def _unmap_lun(self, volume, connector, force=False):
"""unmap volume from host."""
LOG.debug('_map_lun, volume id: %(vid)s, '
'connector: %(connector)s, force: %(frc)s.',
{'vid': volume['id'],
'connector': connector, 'frc': force})
volumename = self._create_volume_name(volume['id'])
vol_instance = self._find_lun(volume)
if vol_instance is None:
LOG.info(_LI('_unmap_lun, '
'volumename:%(volumename)s, '
'volume not found.'),
{'volumename': volumename})
return False
volume_uid = vol_instance['Name']
if not force:
aglist = self._find_affinity_group(connector, vol_instance)
if not aglist:
LOG.info(_LI('_unmap_lun, '
'volumename: %(volumename)s, '
'volume is not mapped.'),
{'volumename': volumename})
return False
else:
try:
aglist = self._assoc_eternus_names(
vol_instance.path,
AssocClass='CIM_ProtocolControllerForUnit',
ResultClass='FUJITSU_AffinityGroupController')
except Exception:
msg = (_('_unmap_lun,'
'vol_instance.path: %(volume)s, '
'AssociatorNames: CIM_ProtocolControllerForUnit, '
'cannot connect to ETERNUS.')
% {'volume': vol_instance.path})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_unmap_lun, '
'vol_instance.path: %(volume)s, '
'affinity_groups: %(aglist)s.',
{'volume': vol_instance.path,
'aglist': aglist})
configservice = self._find_eternus_service(CTRL_CONF)
if configservice is None:
msg = (_('_unmap_lun, '
'vol_instance.path: %(volume)s, '
'volumename: %(volumename)s, '
'volume_uid: %(uid)s, '
'aglist: %(aglist)s, '
'Controller Configuration Service not found.')
% {'vol': vol_instance.path,
'volumename': [volumename],
'uid': [volume_uid],
'aglist': aglist})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for ag in aglist:
LOG.debug('_unmap_lun, '
'volumename: %(volumename)s, '
'volume_uid: %(volume_uid)s, '
'AffinityGroup: %(ag)s.',
{'volumename': volumename,
'volume_uid': volume_uid,
'ag': ag})
rc, errordesc, job = self._exec_eternus_service(
'HidePaths',
configservice,
LUNames=[volume_uid],
ProtocolControllers=[ag])
LOG.debug('_unmap_lun, '
'Error: %(errordesc)s, '
'Return code: %(rc)lu.',
{'errordesc': errordesc,
'rc': rc})
if rc == LUNAME_NOT_EXIST:
LOG.debug('_unmap_lun, '
'volumename: %(volumename)s, '
'Invalid LUNames.',
{'volumename': volumename})
elif rc != 0:
msg = (_('_unmap_lun, '
'volumename: %(volumename)s, '
'volume_uid: %(volume_uid)s, '
'AffinityGroup: %(ag)s, '
'Return code: %(rc)lu, '
'Error: %(errordesc)s.')
% {'volumename': volumename,
'volume_uid': volume_uid,
'ag': ag,
'rc': rc,
'errordesc': errordesc})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_unmap_lun, '
'volumename: %(volumename)s.',
{'volumename': volumename})
return True
def _get_eternus_iscsi_properties(self):
"""get target port iqns and target_portals."""
iscsi_properties_list = []
iscsiip_list = self._get_drvcfg('EternusISCSIIP', multiple=True)
iscsi_port = self.configuration.iscsi_port
LOG.debug('_get_eternus_iscsi_properties, iplist: %s.', iscsiip_list)
try:
ip_endpointlist = self._enum_eternus_instance_names(
'FUJITSU_IPProtocolEndpoint')
except Exception:
msg = (_('_get_eternus_iscsi_properties, '
'iscsiip: %(iscsiip)s, '
'EnumerateInstanceNames, '
'cannot connect to ETERNUS.')
% {'iscsiip': iscsiip_list})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for ip_endpoint in ip_endpointlist:
try:
ip_endpoint_instance = self._get_eternus_instance(
ip_endpoint)
ip_address = ip_endpoint_instance['IPv4Address']
LOG.debug('_get_eternus_iscsi_properties, '
'instanceip: %(ip)s, '
'iscsiip: %(iscsiip)s.',
{'ip': ip_address,
'iscsiip': iscsiip_list})
except Exception:
msg = (_('_get_eternus_iscsi_properties, '
'iscsiip: %(iscsiip)s, '
'GetInstance, '
'cannot connect to ETERNUS.')
% {'iscsiip': iscsiip_list})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if ip_address not in iscsiip_list:
continue
LOG.debug('_get_eternus_iscsi_properties, '
'find iscsiip: %(ip)s.', {'ip': ip_address})
try:
tcp_endpointlist = self._assoc_eternus_names(
ip_endpoint,
AssocClass='CIM_BindsTo',
ResultClass='FUJITSU_TCPProtocolEndpoint')
except Exception:
msg = (_('_get_eternus_iscsi_properties, '
'iscsiip: %(iscsiip)s, '
'AssociatorNames: CIM_BindsTo, '
'cannot connect to ETERNUS.')
% {'iscsiip': ip_address})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for tcp_endpoint in tcp_endpointlist:
try:
iscsi_endpointlist = (
self._assoc_eternus(tcp_endpoint,
AssocClass='CIM_BindsTo',
ResultClass='FUJITSU_iSCSI'
'ProtocolEndpoint'))
except Exception:
msg = (_('_get_eternus_iscsi_properties, '
'iscsiip: %(iscsiip)s, '
'AssociatorNames: CIM_BindsTo, '
'cannot connect to ETERNUS.')
% {'iscsiip': ip_address})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for iscsi_endpoint in iscsi_endpointlist:
target_portal = "%s:%s" % (ip_address, iscsi_port)
iqn = iscsi_endpoint['Name'].split(',')[0]
iscsi_properties_list.append((iscsi_endpoint.path,
target_portal,
iqn))
LOG.debug('_get_eternus_iscsi_properties, '
'target_portal: %(target_portal)s, '
'iqn: %(iqn)s.',
{'target_portal': target_portal,
'iqn': iqn})
if len(iscsi_properties_list) == 0:
msg = (_('_get_eternus_iscsi_properties, '
'iscsiip list: %(iscsiip_list)s, '
'iqn not found.')
% {'iscsiip_list': iscsiip_list})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
LOG.debug('_get_eternus_iscsi_properties, '
'iscsi_properties_list: %(iscsi_properties_list)s.',
{'iscsi_properties_list': iscsi_properties_list})
return iscsi_properties_list
def _wait_for_job_complete(self, conn, job):
"""Given the job wait for it to complete."""
self.retries = 0
self.wait_for_job_called = False
def _wait_for_job_complete():
"""Called at an interval until the job is finished."""
if self._is_job_finished(conn, job):
raise loopingcall.LoopingCallDone()
if self.retries > JOB_RETRIES:
LOG.error(_LE("_wait_for_job_complete, "
"failed after %(retries)d tries."),
{'retries': self.retries})
raise loopingcall.LoopingCallDone()
try:
self.retries += 1
if not self.wait_for_job_called:
if self._is_job_finished(conn, job):
self.wait_for_job_called = True
except Exception as e:
LOG.error(_LE("Exception: %s"), e)
exceptionMessage = _("Issue encountered waiting for job.")
LOG.error(exceptionMessage)
raise exception.VolumeBackendAPIException(exceptionMessage)
self.wait_for_job_called = False
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_job_complete)
timer.start(interval=JOB_INTERVAL_SEC).wait()
jobInstanceName = job['Job']
jobinstance = conn.GetInstance(jobInstanceName,
LocalOnly=False)
rc = jobinstance['ErrorCode']
LOG.debug('_wait_for_job_complete, rc: %s.', rc)
return rc
def _is_job_finished(self, conn, job):
"""Check if the job is finished."""
jobInstanceName = job['Job']
jobinstance = conn.GetInstance(jobInstanceName,
LocalOnly=False)
jobstate = jobinstance['JobState']
LOG.debug('_is_job_finished,'
'state: %(state)s', {'state': jobstate})
# From ValueMap of JobState in CIM_ConcreteJob
# 2=New, 3=Starting, 4=Running, 32767=Queue Pending
# ValueMap("2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13..32767,
# 32768..65535"),
# Values("New, Starting, Running, Suspended, Shutting Down,
# Completed, Terminated, Killed, Exception, Service,
# Query Pending, DMTF Reserved, Vendor Reserved")]
# NOTE(deva): string matching based on
# http://ipmitool.cvs.sourceforge.net/
# viewvc/ipmitool/ipmitool/lib/ipmi_chassis.c
if jobstate in [2, 3, 4]:
job_finished = False
else:
job_finished = True
LOG.debug('_is_job_finished, finish: %s.', job_finished)
return job_finished
def _pywbem_uint(self, num, datatype):
try:
result = {
'8': pywbem.Uint8(num),
'16': pywbem.Uint16(num),
'32': pywbem.Uint32(num),
'64': pywbem.Uint64(num)
}
result = result.get(datatype, num)
except NameError:
result = num
return result