2503 lines
98 KiB
Python
2503 lines
98 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 time
|
|
|
|
from lxml import etree 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.secretutils import md5
|
|
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
|
|
from cinder.volume.drivers.fujitsu.eternus_dx import constants as CONSTANTS
|
|
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_cli
|
|
from cinder.volume import volume_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
try:
|
|
import pywbem
|
|
pywbemAvailable = True
|
|
except ImportError:
|
|
pywbemAvailable = False
|
|
|
|
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.'),
|
|
]
|
|
|
|
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,
|
|
'storage_protocol': None,
|
|
'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'))
|
|
self.conn = None
|
|
self.fjdxcli = {}
|
|
self._check_user()
|
|
|
|
@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 = volume_utils.extract_host(volume['host'], 'pool')
|
|
# Existence check the pool
|
|
pool = self._find_pool(eternus_pool)
|
|
|
|
if 'RSP' in pool['InstanceID']:
|
|
pooltype = CONSTANTS.RAIDGROUP
|
|
else:
|
|
pooltype = CONSTANTS.TPPOOL
|
|
|
|
configservice = self._find_eternus_service(CONSTANTS.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 == CONSTANTS.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': CONSTANTS.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': CONSTANTS.POOL_TYPE_dic[pooltype]}
|
|
|
|
return (element_path, metadata)
|
|
|
|
def create_pool_info(self, pool_instance, volume_count, pool_type):
|
|
"""Create pool information from pool instance."""
|
|
LOG.debug('create_pool_info, pool_instance: %(pool)s, '
|
|
'volume_count: %(volcount)s, pool_type: %(ptype)s.',
|
|
{'pool': pool_instance,
|
|
'volcount': volume_count, 'ptype': pool_type})
|
|
|
|
if pool_type not in CONSTANTS.POOL_TYPE_list:
|
|
msg = (_('Invalid pool type was specified : %s.') % pool_type)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
total_gb = pool_instance['TotalManagedSpace'] / units.Gi
|
|
free_gb = pool_instance['RemainingManagedSpace'] / units.Gi
|
|
|
|
if hasattr(pool_instance, 'provisioned_capacity_gb'):
|
|
prov_gb = pool_instance.provisioned_capacity_gb
|
|
else:
|
|
prov_gb = total_gb - free_gb
|
|
|
|
if pool_type == 'RAID':
|
|
useable_gb = free_gb
|
|
else:
|
|
# If the ratio is less than the value on ETERNUS,
|
|
# useable_gb may be negative. Avoid over-allocation.
|
|
max_capacity = total_gb * float(
|
|
self.configuration.max_over_subscription_ratio)
|
|
useable_gb = max_capacity - prov_gb
|
|
|
|
pool = {
|
|
'name': pool_instance['ElementName'],
|
|
'path': pool_instance.path,
|
|
'total_capacity_gb': total_gb,
|
|
'free_capacity_gb': free_gb,
|
|
'type': pool_type,
|
|
'volume_count': volume_count,
|
|
'provisioned_capacity_gb': prov_gb,
|
|
'useable_capacity_gb': useable_gb
|
|
}
|
|
|
|
LOG.debug('create_pool_info, pool: %s.', pool)
|
|
return pool
|
|
|
|
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 replication service for CreateElementReplica.
|
|
repservice = self._find_eternus_service(CONSTANTS.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(CONSTANTS.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(CONSTANTS.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.
|
|
pool_name, pool = self._find_pool_from_volume(vol_instance)
|
|
if pool is None:
|
|
msg = (_('extend_volume, '
|
|
'eternus_pool: %(eternus_pool)s, '
|
|
'pool not found.')
|
|
% {'eternus_pool': pool_name})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Set pooltype.
|
|
if 'RSP' in pool['InstanceID']:
|
|
pooltype = CONSTANTS.RAIDGROUP
|
|
else:
|
|
pooltype = CONSTANTS.TPPOOL
|
|
|
|
configservice = self._find_eternus_service(CONSTANTS.STOR_CONF)
|
|
if not configservice:
|
|
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': pool_name})
|
|
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': pool_name,
|
|
'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': CONSTANTS.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': pool_name,
|
|
'pooltype': CONSTANTS.POOL_TYPE_dic[pooltype]})
|
|
|
|
return pool_name
|
|
|
|
@lockutils.synchronized('ETERNUS-update', 'cinder-', True)
|
|
def update_volume_stats(self):
|
|
"""Get pool capacity."""
|
|
|
|
self.conn = self._get_eternus_connection()
|
|
|
|
poolname_list = self._get_drvcfg('EternusPool', multiple=True)
|
|
self._find_pools(poolname_list, self.conn)
|
|
|
|
return (self.stats, poolname_list)
|
|
|
|
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 not filename:
|
|
# 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()
|
|
|
|
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 = md5(usedforsecurity=False)
|
|
m.update(id_code.encode('utf-8'))
|
|
|
|
# pylint: disable=E1121
|
|
volumename = base64.urlsafe_b64encode(m.digest()).decode()
|
|
ret = CONSTANTS.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_pools(self, poolname_list, conn):
|
|
"""Find Instance or InstanceName of pool by pool name on ETERNUS."""
|
|
LOG.debug('_find_pool, pool name: %s.', poolname_list)
|
|
|
|
target_poolname = list(poolname_list)
|
|
pools = []
|
|
|
|
# Get pools info from CIM instance(include info about instance path).
|
|
try:
|
|
tppoollist = self._enum_eternus_instances(
|
|
'FUJITSU_ThinProvisioningPool', conn=conn)
|
|
rgpoollist = self._enum_eternus_instances(
|
|
'FUJITSU_RAIDStoragePool', conn=conn)
|
|
except Exception:
|
|
msg = (_('_find_pool, '
|
|
'eternus_pool:%(eternus_pool)s, '
|
|
'EnumerateInstances, '
|
|
'cannot connect to ETERNUS.')
|
|
% {'eternus_pool': target_poolname})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Make total pools list.
|
|
tppools = [(tppool, 'TPP') for tppool in tppoollist]
|
|
rgpools = [(rgpool, 'RAID') for rgpool in rgpoollist]
|
|
poollist = tppools + rgpools
|
|
|
|
# One eternus backend has only one special pool name
|
|
# so just use pool name can get the target pool.
|
|
for pool, ptype in poollist:
|
|
poolname = pool['ElementName']
|
|
|
|
LOG.debug('_find_pools, '
|
|
'pool: %(pool)s, ptype: %(ptype)s.',
|
|
{'pool': poolname, 'ptype': ptype})
|
|
if poolname in target_poolname:
|
|
try:
|
|
volume_list = self._assoc_eternus_names(
|
|
pool.path,
|
|
conn=conn,
|
|
AssocClass='FUJITSU_AllocatedFromStoragePool',
|
|
ResultClass='FUJITSU_StorageVolume')
|
|
|
|
volume_count = len(volume_list)
|
|
except Exception:
|
|
msg = (_('_find_pools, '
|
|
'poolname: %(poolname)s, '
|
|
'pooltype: %(ptype)s, '
|
|
'Associator Names, '
|
|
'cannot connect to ETERNUS.')
|
|
% {'ptype': ptype,
|
|
'poolname': poolname})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
if ptype == 'TPP':
|
|
param_dict = {
|
|
'pool-name': poolname
|
|
}
|
|
rc, errordesc, data = self._exec_eternus_cli(
|
|
'show_pool_provision', **param_dict)
|
|
|
|
if rc != 0:
|
|
msg = (_('_find_pools, show_pool_provision, '
|
|
'pool name: %(pool_name)s, '
|
|
'Return code: %(rc)lu, '
|
|
'Error: %(errordesc)s, '
|
|
'Message: %(job)s.')
|
|
% {'pool_name': poolname,
|
|
'rc': rc,
|
|
'errordesc': errordesc,
|
|
'job': data})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
pool.provisioned_capacity_gb = data
|
|
|
|
poolinfo = self.create_pool_info(pool, volume_count, ptype)
|
|
|
|
target_poolname.remove(poolname)
|
|
pools.append((poolinfo, poolname))
|
|
|
|
if not target_poolname:
|
|
break
|
|
|
|
if not pools:
|
|
LOG.warning('_find_pools, all the EternusPools in driver '
|
|
'configuration file do not exist. '
|
|
'Please edit the driver configuration file '
|
|
'to include EternusPool names.')
|
|
|
|
# Sort pools in the order defined in driver configuration file.
|
|
sorted_pools = (
|
|
[pool for name in poolname_list for pool, pname in pools
|
|
if name == pname])
|
|
|
|
LOG.debug('_find_pools, '
|
|
'pools: %(pools)s, '
|
|
'notfound_pools: %(notfound_pools)s.',
|
|
{'pools': pools,
|
|
'notfound_pools': target_poolname})
|
|
pools_stats = {'pools': []}
|
|
for pool in sorted_pools:
|
|
single_pool = {}
|
|
if pool['type'] == 'TPP':
|
|
thin_enabled = True
|
|
max_ratio = self.configuration.max_over_subscription_ratio
|
|
else:
|
|
thin_enabled = False
|
|
max_ratio = 1
|
|
|
|
single_pool.update(dict(
|
|
path=pool['path'],
|
|
pool_name=pool['name'],
|
|
total_capacity_gb=pool['total_capacity_gb'],
|
|
total_volumes=pool['volume_count'],
|
|
free_capacity_gb=pool['free_capacity_gb'],
|
|
provisioned_capacity_gb=pool['provisioned_capacity_gb'],
|
|
useable_capacity_gb=pool['useable_capacity_gb'],
|
|
thin_provisioning_support=thin_enabled,
|
|
thick_provisioning_support=not thin_enabled,
|
|
max_over_subscription_ratio=max_ratio,
|
|
))
|
|
single_pool['multiattach'] = False
|
|
pools_stats['pools'].append(single_pool)
|
|
|
|
self.stats['shared_targets'] = True
|
|
self.stats['backend_state'] = 'up'
|
|
self.stats['pools'] = pools_stats['pools']
|
|
|
|
return self.stats, target_poolname
|
|
|
|
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})
|
|
rc = None
|
|
retdata = None
|
|
# 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 == CONSTANTS.DEVICE_IS_BUSY:
|
|
msg = _('Device is in Busy state')
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
errordesc = CONSTANTS.RETCODE_dic.get(six.text_type(rc),
|
|
CONSTANTS.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, conn=None, **param_dict):
|
|
"""Enumerate Instances."""
|
|
LOG.debug('_enum_eternus_instances, classname: %s.', classname)
|
|
|
|
if not conn:
|
|
conn = self.conn
|
|
|
|
ret = conn.EnumerateInstances(classname, **param_dict)
|
|
|
|
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, conn=None, **param_dict):
|
|
"""Associator."""
|
|
LOG.debug('_assoc_eternus, '
|
|
'classname: %(cls)s, param: %(param)s.',
|
|
{'cls': classname, 'param': param_dict})
|
|
|
|
if not conn:
|
|
conn = self.conn
|
|
|
|
ret = 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, conn=None, **param_dict):
|
|
"""Associator Names."""
|
|
LOG.debug('_assoc_eternus_names, '
|
|
'classname: %(cls)s, param: %(param)s.',
|
|
{'cls': classname, 'param': param_dict})
|
|
|
|
if not conn:
|
|
conn = self.conn
|
|
|
|
ret = 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'] == CONSTANTS.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 = CONSTANTS.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(CONSTANTS.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 == CONSTANTS.COPYSESSION_NOT_EXIST:
|
|
LOG.debug('_delete_copysession, '
|
|
'cpsession: %(cpsession)s, '
|
|
'copysession is not exist.',
|
|
{'cpsession': cpsession})
|
|
elif rc == CONSTANTS.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(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(CONSTANTS.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 != CONSTANTS.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 != CONSTANTS.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(CONSTANTS.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 == CONSTANTS.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 > CONSTANTS.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=CONSTANTS.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
|
|
|
|
@staticmethod
|
|
def _pywbem_uint(num, datatype):
|
|
try:
|
|
if datatype == '8':
|
|
result = pywbem.Uint8(num)
|
|
elif datatype == '16':
|
|
result = pywbem.Uint16(num)
|
|
elif datatype == '32':
|
|
result = pywbem.Uint32(num)
|
|
elif datatype == '64':
|
|
result = pywbem.Uint64(num)
|
|
except NameError:
|
|
result = num
|
|
|
|
return result
|
|
|
|
def _find_pool_from_volume(self, vol_instance, manage_type='volume'):
|
|
"""Find Instance or InstanceName of pool by volume instance."""
|
|
LOG.debug('_find_pool_from_volume, volume: %(volume)s.',
|
|
{'volume': vol_instance})
|
|
poolname = None
|
|
target_pool = None
|
|
filename = None
|
|
conn = self.conn
|
|
|
|
# Get poolname of volume on Eternus.
|
|
try:
|
|
pools = self._assoc_eternus(
|
|
vol_instance.path,
|
|
conn=conn,
|
|
AssocClass='FUJITSU_AllocatedFromStoragePool',
|
|
ResultClass='CIM_StoragePool')
|
|
except Exception:
|
|
msg = (_('_find_pool_from_volume, '
|
|
'vol_instance: %s, '
|
|
'Associators: FUJITSU_AllocatedFromStoragePool, '
|
|
'cannot connect to ETERNUS.')
|
|
% vol_instance.path)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
if not pools:
|
|
msg = (_('_find_pool_from_volume, '
|
|
'vol_instance: %s, '
|
|
'pool not found.')
|
|
% vol_instance.path)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Get poolname from driver configuration file.
|
|
if manage_type == 'volume':
|
|
cfgpool_list = list(self._get_drvcfg('EternusPool',
|
|
filename=filename,
|
|
multiple=True))
|
|
elif manage_type == 'snapshot':
|
|
cfgpool_list = list(self._get_drvcfg('EternusSnapPool',
|
|
filename=filename,
|
|
multiple=True))
|
|
LOG.debug('_find_pool_from_volume, cfgpool_list: %(cfgpool_list)s.',
|
|
{'cfgpool_list': cfgpool_list})
|
|
for pool in pools:
|
|
if pool['ElementName'] in cfgpool_list:
|
|
poolname = pool['ElementName']
|
|
target_pool = pool.path
|
|
break
|
|
|
|
if not target_pool:
|
|
msg = (_('_find_pool_from_volume, '
|
|
'vol_instance: %s, '
|
|
'the pool of volume not in driver configuration file.')
|
|
% vol_instance.path)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('_find_pool_from_volume, poolname: %(poolname)s, '
|
|
'target_pool: %(target_pool)s.',
|
|
{'poolname': poolname, 'target_pool': target_pool})
|
|
return poolname, target_pool
|
|
|
|
def _check_user(self):
|
|
"""Check whether user's role is accessible to ETERNUS and Software."""
|
|
ret = True
|
|
rc, errordesc, job = self._exec_eternus_cli('check_user_role')
|
|
if rc != 0:
|
|
msg = (_('_check_user, '
|
|
'Return code: %(rc)lu, '
|
|
'Error: %(errordesc)s, '
|
|
'Message: %(job)s.')
|
|
% {'rc': rc,
|
|
'errordesc': errordesc,
|
|
'job': job})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
if job != 'Software':
|
|
msg = (_('_check_user, '
|
|
'Specified user(%(user)s) does not have '
|
|
'Software role: %(role)s.')
|
|
% {'user': self._get_drvcfg('EternusUser'),
|
|
'role': job})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
return ret
|
|
|
|
def _exec_eternus_cli(self, command, retry=CONSTANTS.TIMES_MIN,
|
|
retry_interval=CONSTANTS.RETRY_INTERVAL,
|
|
retry_code=[32787], filename=None, timeout=None,
|
|
**param_dict):
|
|
"""Execute ETERNUS CLI."""
|
|
LOG.debug('_exec_eternus_cli, '
|
|
'command: %(a)s, '
|
|
'filename: %(f)s, '
|
|
'timeout: %(t)s, '
|
|
'parameters: %(b)s.',
|
|
{'a': command,
|
|
'f': filename,
|
|
't': timeout,
|
|
'b': param_dict})
|
|
|
|
result = None
|
|
rc = None
|
|
retdata = None
|
|
errordesc = None
|
|
filename = self.configuration.cinder_eternus_config_file
|
|
storage_ip = self._get_drvcfg('EternusIP')
|
|
if not self.fjdxcli.get(filename):
|
|
user = self._get_drvcfg('EternusUser')
|
|
password = self._get_drvcfg('EternusPassword')
|
|
self.fjdxcli[filename] = (
|
|
eternus_dx_cli.FJDXCLI(user, storage_ip,
|
|
password=password))
|
|
|
|
for retry_num in range(retry):
|
|
# Execute ETERNUS CLI and get return value.
|
|
try:
|
|
out_dict = self.fjdxcli[filename].done(command, **param_dict)
|
|
result = out_dict.get('result')
|
|
rc_str = out_dict.get('rc')
|
|
retdata = out_dict.get('message')
|
|
except Exception as ex:
|
|
msg = (_('_exec_eternus_cli, '
|
|
'unexpected error: %(ex)s.')
|
|
% {'ex': ex})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Check ssh result.
|
|
if result == 255:
|
|
LOG.info('_exec_eternus_cli, retry, '
|
|
'command: %(command)s, '
|
|
'option: %(option)s, '
|
|
'ip: %(ip)s, '
|
|
'SSH Result: %(result)s, '
|
|
'retdata: %(retdata)s, '
|
|
'TryNum: %(rn)s.',
|
|
{'command': command,
|
|
'option': param_dict,
|
|
'ip': storage_ip,
|
|
'result': result,
|
|
'retdata': retdata,
|
|
'rn': (retry_num + 1)})
|
|
time.sleep(retry_interval)
|
|
continue
|
|
elif result != 0:
|
|
msg = (_('_exec_eternus_cli, '
|
|
'unexpected error, '
|
|
'command: %(command)s, '
|
|
'option: %(option)s, '
|
|
'ip: %(ip)s, '
|
|
'resuslt: %(result)s, '
|
|
'retdata: %(retdata)s.')
|
|
% {'command': command,
|
|
'option': param_dict,
|
|
'ip': storage_ip,
|
|
'result': result,
|
|
'retdata': retdata})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Check CLI return code.
|
|
if rc_str.isdigit():
|
|
# SMI-S style return code.
|
|
rc = int(rc_str)
|
|
|
|
try:
|
|
errordesc = CONSTANTS.RETCODE_dic[str(rc)]
|
|
except Exception:
|
|
errordesc = 'Undefined Error!!'
|
|
|
|
if rc in retry_code:
|
|
LOG.info('_exec_eternus_cli, retry, '
|
|
'ip: %(ip)s, '
|
|
'RetryCode: %(rc)s, '
|
|
'TryNum: %(rn)s.',
|
|
{'ip': storage_ip,
|
|
'rc': rc,
|
|
'rn': (retry_num + 1)})
|
|
time.sleep(retry_interval)
|
|
continue
|
|
if rc == 4:
|
|
if ('Authentication failed' in retdata and
|
|
retry_num + 1 < retry):
|
|
LOG.warning('_exec_eternus_cli, retry, ip: %(ip)s, '
|
|
'Message: %(message)s, '
|
|
'TryNum: %(rn)s.',
|
|
{'ip': storage_ip,
|
|
'message': retdata,
|
|
'rn': (retry_num + 1)})
|
|
time.sleep(1)
|
|
continue
|
|
|
|
break
|
|
else:
|
|
# CLI style return code.
|
|
LOG.warning('_exec_eternus_cli, '
|
|
'WARNING!! '
|
|
'ip: %(ip)s, '
|
|
'ReturnCode: %(rc_str)s, '
|
|
'ReturnData: %(retdata)s.',
|
|
{'ip': storage_ip,
|
|
'rc_str': rc_str,
|
|
'retdata': retdata})
|
|
|
|
errordesc = rc_str
|
|
rc = 4 # Failed.
|
|
break
|
|
else:
|
|
if 0 < result:
|
|
msg = (_('_exec_eternus_cli, '
|
|
'cannot connect to ETERNUS. '
|
|
'SSH Result: %(result)s, '
|
|
'retdata: %(retdata)s.')
|
|
% {'result': result,
|
|
'retdata': retdata})
|
|
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
LOG.warning('_exec_eternus_cli, Retry was exceeded.')
|
|
|
|
ret = (rc, errordesc, retdata)
|
|
|
|
LOG.debug('_exec_eternus_cli, '
|
|
'command: %(a)s, '
|
|
'parameters: %(b)s, '
|
|
'ip: %(ip)s, '
|
|
'Return code: %(rc)s, '
|
|
'Error: %(errordesc)s.',
|
|
{'a': command,
|
|
'b': param_dict,
|
|
'ip': storage_ip,
|
|
'rc': rc,
|
|
'errordesc': errordesc})
|
|
return ret
|