
This patch adds a new static method to drivers to expose their configuration options. This also adds the same call to backup drivers as well as the zone manager drivers. Updated the generate_driver_list to expose the options as well. This patch also orders the driver list alphabetically to make it easier and consistent to find drivers. The driver list is now broken down into supported and unsupported drivers as well. Change-Id: I3e7db26ef3df24a12e3bfa219fe25bfb315335ec
2190 lines
85 KiB
Python
2190 lines
85 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 time
|
|
|
|
from defusedxml import ElementTree as ET
|
|
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
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder import utils
|
|
from cinder.volume import configuration as conf
|
|
|
|
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.
|
|
VOLUME_IS_BUSY = 32786
|
|
DEVICE_IS_BUSY = 32787
|
|
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, group=conf.SHARED_CONF_GROUP)
|
|
|
|
|
|
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):
|
|
|
|
self.pywbemAvailable = pywbemAvailable
|
|
|
|
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'))
|
|
|
|
@staticmethod
|
|
def get_driver_options():
|
|
return FJ_ETERNUS_DX_OPT_opts
|
|
|
|
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('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('_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('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()
|
|
force = True if not connector else force
|
|
map_exist = self._unmap_lun(volume, connector, force)
|
|
|
|
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('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'] = False
|
|
|
|
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 = ET.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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
|
|
if rc == DEVICE_IS_BUSY:
|
|
msg = _('Device is in Busy state')
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
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, '
|
|
'Return 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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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)
|
|
|
|
@utils.retry(exception.VolumeBackendAPIException)
|
|
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('_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 == VOLUME_IS_BUSY:
|
|
msg = (_('_delete_copysession, '
|
|
'copysession: %(cpsession)s, '
|
|
'operation: %(operation)s, '
|
|
'Error: Volume is in Busy state')
|
|
% {'cpsession': cpsession,
|
|
'operation': operation})
|
|
raise exception.VolumeIsBusy(data=msg)
|
|
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('_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('_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('_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('_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.target_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("_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:
|
|
exceptionMessage = _("Issue encountered waiting for job.")
|
|
LOG.exception(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
|