2161 lines
87 KiB
Python
2161 lines
87 KiB
Python
# Copyright (c) 2014 FUJITSU LIMITED
|
|
# Copyright (c) 2012 - 2014 EMC Corporation.
|
|
# 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.
|
|
"""
|
|
Common class for SMI-S based FUJITSU ETERNUS DX volume drivers.
|
|
|
|
This common class is for FUJITSU ETERNUS DX volume drivers based on SMI-S.
|
|
|
|
"""
|
|
|
|
import base64
|
|
import hashlib
|
|
import time
|
|
from xml.dom.minidom import parseString
|
|
|
|
from oslo.config import cfg
|
|
from oslo.utils import units
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LE, _LI, _LW
|
|
from cinder.openstack.common import log as logging
|
|
from cinder.openstack.common import loopingcall
|
|
from cinder.volume import volume_types
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
CONF = cfg.CONF
|
|
|
|
try:
|
|
import pywbem
|
|
except ImportError:
|
|
pass
|
|
|
|
CINDER_CONFIG_FILE = '/etc/cinder/cinder_fujitsu_eternus_dx.xml'
|
|
SMIS_ROOT = 'root/eternus'
|
|
PROVISIONING = 'storagetype:provisioning'
|
|
POOL = 'storagetype:pool'
|
|
STOR_CONF_SVC = 'FUJITSU_StorageConfigurationService'
|
|
SCSI_PROT_CTR = 'FUJITSU_AffinityGroupController'
|
|
STOR_HWID = 'FUJITSU_StorageHardwareID'
|
|
CTRL_CONF_SVC = 'FUJITSU_ControllerConfigurationService'
|
|
STOR_HWID_MNG_SVC = 'FUJITSU_StorageHardwareIDManagementService'
|
|
STOR_VOL = 'FUJITSU_StorageVolume'
|
|
REPL_SVC = 'FUJITSU_ReplicationService'
|
|
STOR_POOLS = ['FUJITSU_ThinProvisioningPool', 'FUJITSU_RAIDStoragePool']
|
|
AUTH_PRIV = 'FUJITSU_AuthorizedPrivilege'
|
|
STOR_SYNC = 'FUJITSU_StorageSynchronized'
|
|
VOL_PREFIX = 'FJosv_'
|
|
|
|
drv_opts = [
|
|
cfg.StrOpt('cinder_smis_config_file',
|
|
default=CINDER_CONFIG_FILE,
|
|
help='The configuration file for the Cinder '
|
|
'SMI-S driver'), ]
|
|
|
|
CONF.register_opts(drv_opts)
|
|
|
|
BROKEN = 5
|
|
SNAPOPC = 4
|
|
OPC = 5
|
|
RETURN_TO_RESOURCEPOOL = 19
|
|
DETACH = 8
|
|
JOB_RETRIES = 60
|
|
INTERVAL_10_SEC = 10
|
|
|
|
OPERATION_dic = {SNAPOPC: RETURN_TO_RESOURCEPOOL,
|
|
OPC: DETACH
|
|
}
|
|
|
|
RETCODE_dic = {'0': 'Success',
|
|
'1': 'Method Not Supported',
|
|
'4': 'Failed',
|
|
'5': 'Invalid Parameter',
|
|
'4097': 'Size Not Supported',
|
|
'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',
|
|
'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'
|
|
}
|
|
|
|
|
|
class FJDXCommon(object):
|
|
"""Common code that can be used by ISCSI and FC drivers."""
|
|
|
|
stats = {'driver_version': '1.2',
|
|
'free_capacity_gb': 0,
|
|
'reserved_percentage': 0,
|
|
'storage_protocol': None,
|
|
'total_capacity_gb': 0,
|
|
'vendor_name': 'FUJITSU',
|
|
'volume_backend_name': None}
|
|
|
|
def __init__(self, prtcl, configuration=None):
|
|
|
|
self.protocol = prtcl
|
|
self.configuration = configuration
|
|
self.configuration.append_config_values(drv_opts)
|
|
|
|
ip, port = self._get_ecom_server()
|
|
self.user, self.passwd = self._get_ecom_cred()
|
|
self.url = 'http://' + ip + ':' + port
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
def create_volume(self, volume):
|
|
"""Creates a volume."""
|
|
LOG.debug('Entering create_volume.')
|
|
volumesize = int(volume['size']) * units.Gi
|
|
volumename = self._create_volume_name(volume['id'])
|
|
|
|
LOG.info(_LI('Create Volume: %(volume)s Size: %(size)lu')
|
|
% {'volume': volumename,
|
|
'size': volumesize})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
storage_type = self._get_storage_type(volume)
|
|
|
|
LOG.debug('Create Volume: %(volume)s '
|
|
'Storage type: %(storage_type)s'
|
|
% {'volume': volumename,
|
|
'storage_type': storage_type})
|
|
|
|
pool, storage_system = self._find_pool(storage_type[POOL])
|
|
|
|
LOG.debug('Create Volume: %(volume)s Pool: %(pool)s '
|
|
'Storage System: %(storage_system)s'
|
|
% {'volume': volumename,
|
|
'pool': pool,
|
|
'storage_system': storage_system})
|
|
|
|
configservice = self._find_storage_configuration_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Error Create Volume: %(volumename)s. "
|
|
"Storage Configuration Service not found "
|
|
"for pool %(storage_type)s.")
|
|
% {'volumename': volumename,
|
|
'storage_type': storage_type})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
provisioning = self._get_provisioning(storage_type)
|
|
|
|
LOG.debug('Create Volume: %(name)s Method: '
|
|
'CreateOrModifyElementFromStoragePool ConfigServicie: '
|
|
'%(service)s ElementName: %(name)s InPool: %(pool)s '
|
|
'ElementType: %(provisioning)s Size: %(size)lu'
|
|
% {'service': configservice,
|
|
'name': volumename,
|
|
'pool': pool,
|
|
'provisioning': provisioning,
|
|
'size': volumesize})
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateOrModifyElementFromStoragePool',
|
|
configservice, ElementName=volumename, InPool=pool,
|
|
ElementType=self._getnum(provisioning, '16'),
|
|
Size=self._getnum(volumesize, '64'))
|
|
|
|
LOG.debug('Create Volume: %(volumename)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'rc': rc})
|
|
|
|
if rc == 5L:
|
|
# for DX S2
|
|
# retry with 16 digit of volume name
|
|
volumename = volumename[:16]
|
|
LOG.debug('Retry with 16 digit of volume name.'
|
|
'Create Volume: %(name)s Method: '
|
|
'CreateOrModifyElementFromStoragePool'
|
|
' ConfigServicie: %(service)s'
|
|
' ElementName: %(name)s InPool: %(pool)s '
|
|
'ElementType: %(provisioning)s Size: %(size)lu'
|
|
% {'service': configservice,
|
|
'name': volumename,
|
|
'pool': pool,
|
|
'provisioning': provisioning,
|
|
'size': volumesize})
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateOrModifyElementFromStoragePool',
|
|
configservice, ElementName=volumename, InPool=pool,
|
|
ElementType=self._getnum(provisioning, '16'),
|
|
Size=self._getnum(volumesize, '64'))
|
|
|
|
LOG.debug('Create Volume: %(volumename)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'rc': rc})
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
LOG.error(_LE('Error Create Volume: %(volumename)s. '
|
|
'Return code: %(rc)lu. Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
raise exception.VolumeBackendAPIException(data=errordesc)
|
|
|
|
# Find the newly created volume
|
|
if "job" in job:
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass=STOR_VOL)
|
|
volpath = associators[0].path
|
|
else: # for ETERNUS DX
|
|
volpath = job['TheElement']
|
|
|
|
name = {}
|
|
name['classname'] = volpath.classname
|
|
keys = {}
|
|
keys['CreationClassName'] = volpath['CreationClassName']
|
|
keys['SystemName'] = volpath['SystemName']
|
|
keys['DeviceID'] = volpath['DeviceID']
|
|
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
|
name['keybindings'] = keys
|
|
|
|
LOG.debug('Leaving create_volume: %(volumename)s '
|
|
'Return code: %(rc)lu '
|
|
'volume instance: %(name)s'
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'name': name})
|
|
|
|
return name
|
|
|
|
def create_volume_from_snapshot(self, volume, snapshot):
|
|
"""Creates a volume from a snapshot."""
|
|
|
|
LOG.debug('Entering create_volume_from_snapshot.')
|
|
|
|
snapshotname = snapshot['name']
|
|
volumename = self._create_volume_name(volume['id'])
|
|
vol_instance = None
|
|
|
|
LOG.info(_LI('Create Volume from Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s')
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
snapshot_instance = self._find_lun(snapshot)
|
|
storage_system = snapshot_instance['SystemName']
|
|
|
|
LOG.debug('Create Volume from Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s Snapshot Instance: '
|
|
'%(snapshotinstance)s Storage System: %(storage_system)s.'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'snapshotinstance': snapshot_instance.path,
|
|
'storage_system': storage_system})
|
|
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
exception_message = (_('Error Create Volume from Snapshot: '
|
|
'Volume: %(volumename)s Snapshot: '
|
|
'%(snapshotname)s. Cannot find Replication '
|
|
'Service to create volume from snapshot.')
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
LOG.debug('Create Volume from Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s Method: CreateElementReplica '
|
|
'ReplicationService: %(service)s ElementName: '
|
|
'%(elementname)s SyncType: 8 SourceElement: '
|
|
'%(sourceelement)s'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'service': repservice,
|
|
'elementname': volumename,
|
|
'sourceelement': snapshot_instance.path})
|
|
|
|
# Create a Clone from snapshot
|
|
name = self.create_volume(volume)
|
|
instancename = self._getinstancename(name['classname'],
|
|
name['keybindings'])
|
|
vol_instance = self.conn.GetInstance(instancename)
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateElementReplica', repservice,
|
|
ElementName=volumename,
|
|
SyncType=self._getnum(8, '16'),
|
|
SourceElement=snapshot_instance.path,
|
|
TargetElement=vol_instance.path)
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
exception_message = (_('Error Create Volume from Snapshot: '
|
|
'Volume: %(volumename)s Snapshot:'
|
|
'%(snapshotname)s. '
|
|
'Return code: %(rc)lu.'
|
|
'Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exception_message)
|
|
volume['provider_location'] = six.text_type(name)
|
|
self.delete_volume(volume)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Find the newly created volume
|
|
if "job" in job:
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass=STOR_VOL)
|
|
volpath = associators[0].path
|
|
else: # for ETERNUS DX
|
|
volpath = job['TargetElement']
|
|
|
|
name = {}
|
|
name['classname'] = volpath.classname
|
|
keys = {}
|
|
keys['CreationClassName'] = volpath['CreationClassName']
|
|
keys['SystemName'] = volpath['SystemName']
|
|
keys['DeviceID'] = volpath['DeviceID']
|
|
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
|
name['keybindings'] = keys
|
|
|
|
LOG.debug('Leaving create_volume_from_snapshot: Volume: '
|
|
'%(volumename)s Snapshot: %(snapshotname)s '
|
|
'Return code: %(rc)lu.'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc})
|
|
|
|
return name
|
|
|
|
def create_cloned_volume(self, volume, src_vref):
|
|
"""Creates a clone of the specified volume."""
|
|
LOG.debug('Entering create_cloned_volume.')
|
|
|
|
srcname = self._create_volume_name(src_vref['id'])
|
|
volumename = self._create_volume_name(volume['id'])
|
|
|
|
LOG.info(_LI('Create a Clone from Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s')
|
|
% {'volumename': volumename,
|
|
'srcname': srcname})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
src_instance = self._find_lun(src_vref)
|
|
storage_system = src_instance['SystemName']
|
|
|
|
LOG.debug('Create Cloned Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s Source Instance: '
|
|
'%(src_instance)s Storage System: %(storage_system)s.'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'src_instance': src_instance.path,
|
|
'storage_system': storage_system})
|
|
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
exception_message = (_('Error Create Cloned Volume: '
|
|
'Volume: %(volumename)s Source Volume: '
|
|
'%(srcname)s. Cannot find Replication '
|
|
'Service to create cloned volume.')
|
|
% {'volumename': volumename,
|
|
'srcname': srcname})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
LOG.debug('Create Cloned Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s Method: CreateElementReplica '
|
|
'ReplicationService: %(service)s ElementName: '
|
|
'%(elementname)s SyncType: 8 SourceElement: '
|
|
'%(sourceelement)s'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'service': repservice,
|
|
'elementname': volumename,
|
|
'sourceelement': src_instance.path})
|
|
|
|
# Create a Clone from source volume
|
|
name = self.create_volume(volume)
|
|
instancename = self._getinstancename(name['classname'],
|
|
name['keybindings'])
|
|
vol_instance = self.conn.GetInstance(instancename)
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateElementReplica', repservice,
|
|
ElementName=volumename,
|
|
SyncType=self._getnum(8, '16'),
|
|
SourceElement=src_instance.path,
|
|
TargetElement=vol_instance.path)
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
exception_message = (_('Error Create Cloned Volume: '
|
|
'Volume: %(volumename)s Source Volume:'
|
|
'%(srcname)s. Return code: %(rc)lu.'
|
|
'Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exception_message)
|
|
volume['provider_location'] = six.text_type(name)
|
|
self.delete_volume(volume)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Find the newly created volume
|
|
if "job" in job:
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass=STOR_VOL)
|
|
volpath = associators[0].path
|
|
else: # for ETERNUS DX
|
|
volpath = job['TargetElement']
|
|
name = {}
|
|
name['classname'] = volpath.classname
|
|
keys = {}
|
|
keys['CreationClassName'] = volpath['CreationClassName']
|
|
keys['SystemName'] = volpath['SystemName']
|
|
keys['DeviceID'] = volpath['DeviceID']
|
|
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
|
name['keybindings'] = keys
|
|
|
|
LOG.debug('Leaving create_cloned_volume: Volume: '
|
|
'%(volumename)s Source Volume: %(srcname)s '
|
|
'Return code: %(rc)lu.'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'rc': rc})
|
|
|
|
return name
|
|
|
|
def delete_volume(self, volume):
|
|
"""Deletes an volume."""
|
|
LOG.debug('Entering delete_volume.')
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.info(_LI('Delete Volume: %(volume)s')
|
|
% {'volume': volumename})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
cpsession, storage_system = self._find_copysession(volume)
|
|
if cpsession is not None:
|
|
LOG.debug('delete_volume,volumename:%(volumename)s,'
|
|
'volume is using by copysession[%(cpsession)s].'
|
|
'delete copysession.'
|
|
% {'volumename': volumename,
|
|
'cpsession': cpsession})
|
|
self._delete_copysession(storage_system, cpsession)
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
if vol_instance is None:
|
|
LOG.error(_LE('Volume %(name)s not found on the array. '
|
|
'No volume to delete.')
|
|
% {'name': volumename})
|
|
return
|
|
|
|
configservice =\
|
|
self._find_storage_configuration_service(storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Error Delete Volume: %(volumename)s. "
|
|
"Storage Configuration Service not found.")
|
|
% {'volumename': volumename})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
device_id = vol_instance['DeviceID']
|
|
|
|
LOG.debug('Delete Volume: %(name)s DeviceID: %(deviceid)s'
|
|
% {'name': volumename,
|
|
'deviceid': device_id})
|
|
|
|
LOG.debug('Delete Volume: %(name)s Method: ReturnToStoragePool '
|
|
'ConfigServic: %(service)s TheElement: %(vol_instance)s'
|
|
% {'service': configservice,
|
|
'name': volumename,
|
|
'vol_instance': vol_instance.path})
|
|
|
|
rc, job =\
|
|
self.conn.InvokeMethod('ReturnToStoragePool',
|
|
configservice,
|
|
TheElement=vol_instance.path)
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
if rc != 0L:
|
|
exception_message = (_('Error Delete Volume: %(volumename)s. '
|
|
'Return code: %(rc)lu. '
|
|
'Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
LOG.debug('Leaving delete_volume: %(volumename)s Return code: '
|
|
'%(rc)lu'
|
|
% {'volumename': volumename,
|
|
'rc': rc})
|
|
|
|
def create_snapshot(self, snapshot, volume):
|
|
"""Creates a snapshot."""
|
|
LOG.debug('Entering create_snapshot.')
|
|
|
|
snapshotname = self._create_volume_name(snapshot['id'])
|
|
volumename = snapshot['volume_name']
|
|
LOG.info(_LI('Create snapshot: %(snapshot)s: volume: %(volume)s')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
|
|
device_id = vol_instance['DeviceID']
|
|
snappool = self._get_snappool_conffile()
|
|
pool, storage_system = self._find_pool(snappool)
|
|
|
|
LOG.debug('Device ID: %(deviceid)s: Storage System: '
|
|
'%(storagesystem)s'
|
|
% {'deviceid': device_id,
|
|
'storagesystem': storage_system})
|
|
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
LOG.error(_LE("Cannot find Replication Service to create snapshot "
|
|
"for volume %s.") % volumename)
|
|
exception_message = (_("Cannot find Replication Service to "
|
|
"create snapshot for volume %s.")
|
|
% volumename)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
LOG.debug("Create Snapshot: Method: CreateElementReplica: "
|
|
"Target: %(snapshot)s Source: %(volume)s Replication "
|
|
"Service: %(service)s ElementName: %(elementname)s Sync "
|
|
"Type: 7 SourceElement: %(sourceelement)s."
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename,
|
|
'service': repservice,
|
|
'elementname': snapshotname,
|
|
'sourceelement': vol_instance.path})
|
|
|
|
rc, job =\
|
|
self.conn.InvokeMethod('CreateElementReplica', repservice,
|
|
ElementName=snapshotname,
|
|
SyncType=self._getnum(7, '16'),
|
|
TargetPool=pool,
|
|
SourceElement=vol_instance.path)
|
|
|
|
LOG.debug('Create Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc})
|
|
|
|
if rc == 5L:
|
|
# for DX S2
|
|
# retry by CreateReplica
|
|
snapshotname = snapshotname[:16]
|
|
LOG.debug('retry Create Snapshot: '
|
|
'snapshotname:%(snapshotname)s,'
|
|
'source volume name:%(volumename)s,'
|
|
'vol_instance.path:%(vol_instance)s,'
|
|
'Invoke CreateReplica'
|
|
% {'snapshotname': snapshotname,
|
|
'volumename': volumename,
|
|
'vol_instance': six.text_type(vol_instance.path)})
|
|
|
|
configservice = self._find_storage_configuration_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Create Snapshot: %(snapshotname)s. "
|
|
"Storage Configuration Service "
|
|
"not found")
|
|
% {'snapshotname': snapshotname})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Invoke method for create snapshot
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateReplica',
|
|
configservice,
|
|
ElementName=snapshotname,
|
|
TargetPool=pool,
|
|
CopyType=self._getnum(4, '16'),
|
|
SourceElement=vol_instance.path)
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
if rc != 0L:
|
|
exception_message = (_('Error Create Snapshot: %(snapshot)s '
|
|
'Volume: %(volume)s '
|
|
'Error: %(errordesc)s')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename,
|
|
'errordesc': errordesc})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Find the newly created volume
|
|
if "job" in job:
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass=STOR_VOL)
|
|
volpath = associators[0].path
|
|
else: # for ETERNUS DX
|
|
volpath = job['TargetElement']
|
|
|
|
name = {}
|
|
name['classname'] = volpath.classname
|
|
keys = {}
|
|
keys['CreationClassName'] = volpath['CreationClassName']
|
|
keys['SystemName'] = volpath['SystemName']
|
|
keys['DeviceID'] = volpath['DeviceID']
|
|
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
|
name['keybindings'] = keys
|
|
|
|
LOG.debug('Leaving create_snapshot: Snapshot: %(snapshot)s '
|
|
'Volume: %(volume)s Return code: %(rc)lu.' %
|
|
{'snapshot': snapshotname, 'volume': volumename, 'rc': rc})
|
|
|
|
return name
|
|
|
|
def delete_snapshot(self, snapshot, volume):
|
|
"""Deletes a snapshot."""
|
|
LOG.debug('Entering delete_snapshot.')
|
|
|
|
snapshotname = snapshot['name']
|
|
volumename = snapshot['volume_name']
|
|
LOG.info(_LI('Delete Snapshot: %(snapshot)s: volume: %(volume)s')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
LOG.debug('Delete Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Finding StorageSychronization_SV_SV.'
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
|
|
sync_name, storage_system =\
|
|
self._find_storage_sync_sv_sv(snapshot, volume, False)
|
|
if sync_name is None:
|
|
LOG.error(_LE('Snapshot: %(snapshot)s: volume: %(volume)s '
|
|
'not found on the array. No snapshot to delete.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
return
|
|
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
exception_message = (_("Cannot find Replication Service to "
|
|
"create snapshot for volume %s.")
|
|
% volumename)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
# Delete snapshot - deletes both the target element
|
|
# and the snap session
|
|
LOG.debug("Delete Snapshot: Target: %(snapshot)s "
|
|
"Source: %(volume)s. Method: "
|
|
"ModifyReplicaSynchronization: "
|
|
"Replication Service: %(service)s Operation: 19 "
|
|
"Synchronization: %(sync_name)s."
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename,
|
|
'service': repservice,
|
|
'sync_name': sync_name})
|
|
|
|
rc, job =\
|
|
self.conn.InvokeMethod('ModifyReplicaSynchronization',
|
|
repservice,
|
|
Operation=self._getnum(19, '16'),
|
|
Synchronization=sync_name)
|
|
|
|
LOG.debug('Delete Snapshot: Volume: %(volumename)s Snapshot: '
|
|
'%(snapshotname)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc})
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
if rc != 0L:
|
|
exception_message = (_('Error Delete Snapshot: Volume: '
|
|
'%(volumename)s Snapshot: '
|
|
'%(snapshotname)s. '
|
|
'Return code: %(rc)lu.'
|
|
' Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# It takes a while for the relationship between the snapshot
|
|
# and the source volume gets cleaned up. Needs to wait until
|
|
# it is cleaned up. Otherwise, the source volume can't be
|
|
# deleted immediately after the snapshot deletion because it
|
|
# still has snapshot.
|
|
wait_timeout = int(self._get_timeout())
|
|
wait_interval = 10
|
|
start = int(time.time())
|
|
|
|
def _wait_for_job():
|
|
try:
|
|
sync_name, storage_system =\
|
|
self._find_storage_sync_sv_sv(snapshot, volume, False)
|
|
if sync_name is None:
|
|
LOG.info(_LI('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot is deleted.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
raise loopingcall.LoopingCallDone()
|
|
if int(time.time()) - start >= wait_timeout:
|
|
LOG.warn(_LW('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot deleted but cleanup timed out.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
raise loopingcall.LoopingCallDone()
|
|
except Exception as ex:
|
|
if ex.args[0] == 6:
|
|
# 6 means object not found, so snapshot is deleted cleanly
|
|
LOG.info(_LI('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot is deleted.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
else:
|
|
LOG.warn(_LW('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot deleted but error during cleanup. '
|
|
'Error: %(error)s')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename,
|
|
'error': six.text_type(ex.args)})
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
timer = loopingcall.FixedIntervalLoopingCall(
|
|
_wait_for_job)
|
|
timer.start(interval=wait_interval)
|
|
|
|
LOG.debug('Leaving delete_snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s Return code: %(rc)lu.'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc})
|
|
|
|
# Mapping method
|
|
def _expose_paths(self, configservice, vol_instance,
|
|
connector):
|
|
"""This method maps a volume to a host.
|
|
|
|
It adds a volume and initiator to a Storage Group
|
|
and therefore maps the volume to the host.
|
|
"""
|
|
results = []
|
|
volumename = vol_instance['ElementName']
|
|
lun_name = vol_instance['Name']
|
|
initiators = self._find_initiator_names(connector)
|
|
storage_system = vol_instance['SystemName']
|
|
lunmask_ctrls = self._find_lunmasking_scsi_protocol_controller(
|
|
storage_system, connector)
|
|
targets = self.get_target_portid(connector)
|
|
|
|
if len(lunmask_ctrls) == 0:
|
|
# create new lunmasking
|
|
for target in targets:
|
|
LOG.debug('ExposePaths: %(vol)s ConfigServicie: %(service)s '
|
|
'LUNames: %(lun_name)s '
|
|
'InitiatorPortIDs: %(initiator)s '
|
|
'TargetPortIDs: %(target)s DeviceAccesses: 2'
|
|
% {'vol': vol_instance.path,
|
|
'service': configservice,
|
|
'lun_name': lun_name,
|
|
'initiator': initiators,
|
|
'target': target})
|
|
|
|
rc, controller =\
|
|
self.conn.InvokeMethod('ExposePaths',
|
|
configservice, LUNames=[lun_name],
|
|
InitiatorPortIDs=initiators,
|
|
TargetPortIDs=[target],
|
|
DeviceAccesses=[self._getnum(2, '16'
|
|
)])
|
|
results.append(rc)
|
|
if rc != 0L:
|
|
msg = (_('Error mapping volume %(volumename)s.rc:%(rc)lu')
|
|
% {'volumename': volumename, 'rc': rc})
|
|
LOG.warn(msg)
|
|
|
|
else:
|
|
# add lun to lunmasking
|
|
for lunmask_ctrl in lunmask_ctrls:
|
|
LOG.debug('ExposePaths parameter '
|
|
'LunMaskingSCSIProtocolController: '
|
|
'%(lunmasking)s'
|
|
% {'lunmasking': lunmask_ctrl})
|
|
rc, controller =\
|
|
self.conn.InvokeMethod('ExposePaths',
|
|
configservice, LUNames=[lun_name],
|
|
DeviceAccesses=[
|
|
self._getnum(2, '16')],
|
|
ProtocolControllers=[lunmask_ctrl])
|
|
results.append(rc)
|
|
if rc != 0L:
|
|
msg = (_('Error mapping volume %(volumename)s.rc:%(rc)lu')
|
|
% {'volumename': volumename, 'rc': rc})
|
|
LOG.warn(msg)
|
|
|
|
if 0L not in results:
|
|
msg = (_('Error mapping volume %(volumename)s:%(results)s.')
|
|
% {'volumename': volumename, 'results': results})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('ExposePaths for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
# Unmapping method
|
|
def _hide_paths(self, configservice, vol_instance,
|
|
connector):
|
|
"""This method unmaps a volume from the host.
|
|
|
|
Removes a volume from the Storage Group
|
|
and therefore unmaps the volume from the host.
|
|
"""
|
|
volumename = vol_instance['ElementName']
|
|
lun_name = vol_instance['Name']
|
|
lunmask_ctrls =\
|
|
self._find_lunmasking_scsi_protocol_controller_for_vol(
|
|
vol_instance, connector)
|
|
|
|
for lunmask_ctrl in lunmask_ctrls:
|
|
LOG.debug('HidePaths: %(vol)s ConfigServicie: %(service)s '
|
|
'LUNames: %(lun_name)s'
|
|
' LunMaskingSCSIProtocolController: '
|
|
'%(lunmasking)s'
|
|
% {'vol': vol_instance.path,
|
|
'service': configservice,
|
|
'lun_name': lun_name,
|
|
'lunmasking': lunmask_ctrl})
|
|
|
|
rc, controller = self.conn.InvokeMethod(
|
|
'HidePaths', configservice,
|
|
LUNames=[lun_name], ProtocolControllers=[lunmask_ctrl])
|
|
|
|
if rc != 0L:
|
|
msg = (_('Error unmapping volume %(volumename)s.rc:%(rc)lu')
|
|
% {'volumename': volumename, 'rc': rc})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('HidePaths for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
def _map_lun(self, volume, connector):
|
|
"""Maps a volume to the host."""
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.info(_LI('Map volume: %(volume)s')
|
|
% {'volume': volumename})
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
storage_system = vol_instance['SystemName']
|
|
|
|
configservice = self._find_controller_configuration_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Cannot find Controller Configuration "
|
|
"Service for storage system %s")
|
|
% storage_system)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
self._expose_paths(configservice, vol_instance, connector)
|
|
|
|
def _unmap_lun(self, volume, connector):
|
|
"""Unmaps a volume from the host."""
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.info(_LI('Unmap volume: %(volume)s')
|
|
% {'volume': volumename})
|
|
|
|
device_info = self.find_device_number(volume, connector)
|
|
device_number = device_info['hostlunid']
|
|
if device_number is None:
|
|
LOG.info(_LI("Volume %s is not mapped. No volume to unmap.")
|
|
% (volumename))
|
|
return
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
storage_system = vol_instance['SystemName']
|
|
|
|
configservice = self._find_controller_configuration_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Cannot find Controller Configuration "
|
|
"Service for storage system %s")
|
|
% storage_system)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
self._hide_paths(configservice, vol_instance, connector)
|
|
|
|
def initialize_connection(self, volume, connector):
|
|
"""Initializes the connection and returns connection info."""
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.info(_LI('Initialize connection: %(volume)s')
|
|
% {'volume': volumename})
|
|
self.conn = self._get_ecom_connection()
|
|
device_info = self.find_device_number(volume, connector)
|
|
device_number = device_info['hostlunid']
|
|
if device_number is not None:
|
|
LOG.info(_LI("Volume %s is already mapped.")
|
|
% (volumename))
|
|
else:
|
|
self._map_lun(volume, connector)
|
|
# Find host lun id again after the volume is exported to the host
|
|
device_info = self.find_device_number(volume, connector)
|
|
|
|
return device_info
|
|
|
|
def terminate_connection(self, volume, connector):
|
|
"""Disallow connection from connector."""
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.info(_LI('Terminate connection: %(volume)s')
|
|
% {'volume': volumename})
|
|
self.conn = self._get_ecom_connection()
|
|
self._unmap_lun(volume, connector)
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
storage_system = vol_instance['SystemName']
|
|
ctrl = self._find_lunmasking_scsi_protocol_controller(
|
|
storage_system, connector)
|
|
return ctrl
|
|
|
|
def extend_volume(self, volume, new_size):
|
|
"""Extends an existing volume."""
|
|
LOG.debug('Entering extend_volume.')
|
|
volumesize = int(new_size) * units.Gi
|
|
volumename = self._create_volume_name(volume['id'])
|
|
|
|
LOG.info(_LI('Extend Volume: %(volume)s New size: %(size)lu')
|
|
% {'volume': volumename,
|
|
'size': volumesize})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
storage_type = self._get_storage_type(volume)
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
|
|
device_id = vol_instance['DeviceID']
|
|
storage_system = vol_instance['SystemName']
|
|
LOG.debug('Device ID: %(deviceid)s: Storage System: '
|
|
'%(storagesystem)s'
|
|
% {'deviceid': device_id,
|
|
'storagesystem': storage_system})
|
|
|
|
configservice = self._find_storage_configuration_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_message = (_("Error Extend Volume: %(volumename)s. "
|
|
"Storage Configuration Service not found.")
|
|
% {'volumename': volumename})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
provisioning = self._get_provisioning(storage_type)
|
|
|
|
LOG.debug('Extend Volume: %(name)s Method: '
|
|
'CreateOrModifyElementFromStoragePool ConfigServicie: '
|
|
'%(service)s ElementType: %(provisioning)s Size: %(size)lu'
|
|
'Volume path: %(volumepath)s'
|
|
% {'service': configservice,
|
|
'name': volumename,
|
|
'provisioning': provisioning,
|
|
'size': volumesize,
|
|
'volumepath': vol_instance.path})
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateOrModifyElementFromStoragePool',
|
|
configservice,
|
|
ElementType=self._getnum(provisioning, '16'),
|
|
Size=self._getnum(volumesize, '64'),
|
|
TheElement=vol_instance.path)
|
|
|
|
LOG.debug('Extend Volume: %(volumename)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'rc': rc})
|
|
|
|
if rc != 0L:
|
|
if "job" in job:
|
|
rc, errordesc = self._wait_for_job_complete(self.conn, job)
|
|
else:
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
exception_message = (_('Error Extend Volume: %(volumename)s. '
|
|
'Return code: %(rc)lu. '
|
|
'Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
LOG.debug('Leaving extend_volume: %(volumename)s '
|
|
'Return code: %(rc)lu '
|
|
% {'volumename': volumename,
|
|
'rc': rc})
|
|
|
|
def update_volume_stats(self):
|
|
"""Retrieve stats info."""
|
|
LOG.debug("Updating volume stats")
|
|
self.stats['total_capacity_gb'] = 'unknown'
|
|
self.stats['free_capacity_gb'] = 'unknown'
|
|
|
|
return self.stats
|
|
|
|
def _get_storage_type(self, volume, filename=None):
|
|
"""Get storage type.
|
|
|
|
Look for user input volume type first.
|
|
If not available, fall back to finding it in conf file.
|
|
"""
|
|
specs = self._get_volumetype_extraspecs(volume)
|
|
if not specs:
|
|
specs = self._get_storage_type_conffile()
|
|
LOG.debug("Storage Type: %s" % (specs))
|
|
return specs
|
|
|
|
def _get_storage_type_conffile(self, filename=None):
|
|
"""Get the storage type from the config file."""
|
|
if filename is None:
|
|
filename = self.configuration.cinder_smis_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
storageTypes = dom.getElementsByTagName('StorageType')
|
|
if storageTypes is not None and len(storageTypes) > 0:
|
|
storageType = storageTypes[0].toxml()
|
|
storageType = storageType.replace('<StorageType>', '')
|
|
storageType = storageType.replace('</StorageType>', '')
|
|
LOG.debug("Found Storage Type in config file: %s"
|
|
% (storageType))
|
|
specs = {}
|
|
specs[POOL] = storageType
|
|
return specs
|
|
else:
|
|
exception_message = (_("Storage type not found."))
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
def _get_snappool_conffile(self, filename=None):
|
|
"""Get the snap pool from the config file."""
|
|
snappool = None
|
|
if filename is None:
|
|
filename = self.configuration.cinder_smis_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
snappools = dom.getElementsByTagName('SnapPool')
|
|
if snappools is not None and len(snappools) > 0:
|
|
snappool = snappools[0].toxml()
|
|
snappool = snappool.replace('<SnapPool>', '')
|
|
snappool = snappool.replace('</SnapPool>', '')
|
|
LOG.debug("Found Snap Pool in config file: [%s]"
|
|
% (snappool))
|
|
return snappool
|
|
else:
|
|
exception_message = (_("Snap pool not found."))
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
def _get_timeout(self, filename=None):
|
|
if filename is None:
|
|
filename = self.configuration.cinder_smis_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
timeouts = dom.getElementsByTagName('Timeout')
|
|
if timeouts is not None and len(timeouts) > 0:
|
|
timeout = timeouts[0].toxml().replace('<Timeout>', '')
|
|
timeout = timeout.replace('</Timeout>', '')
|
|
LOG.debug("Found Timeout: %s" % (timeout))
|
|
return timeout
|
|
else:
|
|
LOG.debug("Timeout not specified.")
|
|
return 10
|
|
|
|
def _get_ecom_cred(self, filename=None):
|
|
if filename is None:
|
|
filename = self.configuration.cinder_smis_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
ecomUsers = dom.getElementsByTagName('EcomUserName')
|
|
if ecomUsers is not None and len(ecomUsers) > 0:
|
|
ecomUser = ecomUsers[0].toxml().replace('<EcomUserName>', '')
|
|
ecomUser = ecomUser.replace('</EcomUserName>', '')
|
|
ecomPasswds = dom.getElementsByTagName('EcomPassword')
|
|
if ecomPasswds is not None and len(ecomPasswds) > 0:
|
|
ecomPasswd = ecomPasswds[0].toxml().replace('<EcomPassword>', '')
|
|
ecomPasswd = ecomPasswd.replace('</EcomPassword>', '')
|
|
if ecomUser is not None and ecomPasswd is not None:
|
|
return ecomUser, ecomPasswd
|
|
else:
|
|
LOG.debug("Ecom user not found.")
|
|
return None
|
|
|
|
def _get_ecom_server(self, filename=None):
|
|
if filename is None:
|
|
filename = self.configuration.cinder_smis_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
ecomIps = dom.getElementsByTagName('EcomServerIp')
|
|
if ecomIps is not None and len(ecomIps) > 0:
|
|
ecomIp = ecomIps[0].toxml().replace('<EcomServerIp>', '')
|
|
ecomIp = ecomIp.replace('</EcomServerIp>', '')
|
|
ecomPorts = dom.getElementsByTagName('EcomServerPort')
|
|
if ecomPorts is not None and len(ecomPorts) > 0:
|
|
ecomPort = ecomPorts[0].toxml().replace('<EcomServerPort>', '')
|
|
ecomPort = ecomPort.replace('</EcomServerPort>', '')
|
|
if ecomIp is not None and ecomPort is not None:
|
|
LOG.debug("Ecom IP: %(ecomIp)s Port: %(ecomPort)s",
|
|
{'ecomIp': ecomIp, 'ecomPort': ecomPort})
|
|
return ecomIp, ecomPort
|
|
else:
|
|
LOG.debug("Ecom server not found.")
|
|
return None
|
|
|
|
def _get_ecom_connection(self, filename=None):
|
|
conn = pywbem.WBEMConnection(self.url, (self.user, self.passwd),
|
|
default_namespace=SMIS_ROOT)
|
|
if conn is None:
|
|
exception_message = (_("Cannot connect to ECOM server"))
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
return conn
|
|
|
|
def _find_replication_service(self, storage_system):
|
|
foundRepService = None
|
|
repservices = self.conn.EnumerateInstanceNames(
|
|
REPL_SVC)
|
|
for repservice in repservices:
|
|
if storage_system == repservice['SystemName']:
|
|
foundRepService = repservice
|
|
LOG.debug("Found Replication Service: %s"
|
|
% (repservice))
|
|
break
|
|
|
|
return foundRepService
|
|
|
|
def _find_storage_configuration_service(self, storage_system):
|
|
foundConfigService = None
|
|
configservices = self.conn.EnumerateInstanceNames(
|
|
STOR_CONF_SVC)
|
|
for configservice in configservices:
|
|
if storage_system == configservice['SystemName']:
|
|
foundConfigService = configservice
|
|
LOG.debug("Found Storage Configuration Service: %s"
|
|
% (configservice))
|
|
break
|
|
|
|
return foundConfigService
|
|
|
|
def _find_controller_configuration_service(self, storage_system):
|
|
foundConfigService = None
|
|
configservices = self.conn.EnumerateInstanceNames(
|
|
CTRL_CONF_SVC)
|
|
for configservice in configservices:
|
|
if storage_system == configservice['SystemName']:
|
|
foundConfigService = configservice
|
|
LOG.debug("Found Controller Configuration Service: %s"
|
|
% (configservice))
|
|
break
|
|
|
|
return foundConfigService
|
|
|
|
def _find_storage_hardwareid_service(self, storage_system):
|
|
foundConfigService = None
|
|
configservices = self.conn.EnumerateInstanceNames(
|
|
STOR_HWID_MNG_SVC)
|
|
for configservice in configservices:
|
|
if storage_system == configservice['SystemName']:
|
|
foundConfigService = configservice
|
|
LOG.debug("Found Storage Hardware ID Management Service: %s"
|
|
% (configservice))
|
|
break
|
|
|
|
return foundConfigService
|
|
|
|
# Find pool based on storage_type
|
|
def _find_pool(self, storage_type, details=False):
|
|
foundPool = None
|
|
systemname = None
|
|
poolinstanceid = None
|
|
# Only get instance names if details flag is False;
|
|
# Otherwise get the whole instances
|
|
|
|
systemname, port = self._get_ecom_server()
|
|
poolinstanceid = self._get_pool_instance_id(storage_type)
|
|
|
|
if details is False:
|
|
pools = self.conn.EnumerateInstanceNames(
|
|
'CIM_StoragePool')
|
|
else:
|
|
pools = self.conn.EnumerateInstances(
|
|
'CIM_StoragePool')
|
|
|
|
for pool in pools:
|
|
if six.text_type(pool['InstanceID']) == six.text_type(
|
|
poolinstanceid):
|
|
foundPool = pool
|
|
break
|
|
|
|
if foundPool is None:
|
|
exception_message = (_("Pool %(storage_type)s is not found.")
|
|
% {'storage_type': storage_type})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
if systemname is None:
|
|
exception_message = (_("Storage system not found for pool "
|
|
"%(storage_type)s.")
|
|
% {'storage_type': storage_type})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
LOG.debug("Pool: %(pool)s SystemName: %(systemname)s."
|
|
% {'pool': foundPool,
|
|
'systemname': systemname})
|
|
return foundPool, systemname
|
|
|
|
def _find_lun(self, volume):
|
|
foundinstance = None
|
|
|
|
volumename = self._create_volume_name(volume['id'])
|
|
loc = volume['provider_location']
|
|
try:
|
|
name = eval(loc)
|
|
instancename = self._getinstancename(name['classname'],
|
|
name['keybindings'])
|
|
foundinstance = self.conn.GetInstance(instancename)
|
|
except Exception:
|
|
foundinstance = None
|
|
|
|
if foundinstance is None:
|
|
LOG.debug("Volume %(volumename)s not found on the array."
|
|
"volume instance is None."
|
|
% {'volumename': volumename})
|
|
else:
|
|
LOG.debug("Volume name: %(volumename)s Volume instance: "
|
|
"%(vol_instance)s."
|
|
% {'volumename': volumename,
|
|
'vol_instance': foundinstance.path})
|
|
|
|
return foundinstance
|
|
|
|
def _find_storage_sync_sv_sv(self, snapshot, volume,
|
|
waitforsync=True):
|
|
foundsyncname = None
|
|
storage_system = None
|
|
|
|
snapshotname = self._create_volume_name(snapshot['id'])
|
|
volumename = self._create_volume_name(volume['id'])
|
|
LOG.debug("Source: %(volumename)s Target: %(snapshotname)s."
|
|
% {'volumename': volumename, 'snapshotname': snapshotname})
|
|
|
|
snapshot_instance = self._find_lun(snapshot)
|
|
volume_instance = self._find_lun(volume)
|
|
if snapshot_instance is None or volume_instance is None:
|
|
LOG.info(_LI('Snapshot Volume %(snapshotname)s, '
|
|
'Source Volume %(volumename)s not '
|
|
'found on the array.')
|
|
% {'snapshotname': snapshotname,
|
|
'volumename': volumename})
|
|
return None, None
|
|
|
|
storage_system = volume_instance['SystemName']
|
|
classname = STOR_SYNC
|
|
bindings = {'SyncedElement': snapshot_instance.path,
|
|
'SystemElement': volume_instance.path}
|
|
foundsyncname = self._getinstancename(classname, bindings)
|
|
|
|
if foundsyncname is None:
|
|
LOG.debug("Source: %(volumename)s Target: %(snapshotname)s. "
|
|
"Storage Synchronized not found. "
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname})
|
|
else:
|
|
LOG.debug("Storage system: %(storage_system)s "
|
|
"Storage Synchronized instance: %(sync)s."
|
|
% {'storage_system': storage_system,
|
|
'sync': foundsyncname})
|
|
# Wait for SE_StorageSynchronized_SV_SV to be fully synced
|
|
if waitforsync:
|
|
self.wait_for_sync(self.conn, foundsyncname)
|
|
|
|
return foundsyncname, storage_system
|
|
|
|
def _find_initiator_names(self, connector):
|
|
foundinitiatornames = []
|
|
iscsi = 'iscsi'
|
|
fc = 'fc'
|
|
name = 'initiator name'
|
|
if self.protocol.lower() == iscsi and connector['initiator']:
|
|
foundinitiatornames.append(connector['initiator'])
|
|
elif self.protocol.lower() == fc and connector['wwpns']:
|
|
for wwn in connector['wwpns']:
|
|
foundinitiatornames.append(wwn)
|
|
name = 'world wide port names'
|
|
|
|
if foundinitiatornames is None or len(foundinitiatornames) == 0:
|
|
msg = (_('Error finding %s.') % name)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug("Found %(name)s: %(initiator)s."
|
|
% {'name': name,
|
|
'initiator': foundinitiatornames})
|
|
return foundinitiatornames
|
|
|
|
def _wait_for_job_complete(self, conn, job):
|
|
"""Given the job wait for it to complete.
|
|
|
|
:param conn: connection the ecom server
|
|
:param job: the job dict
|
|
"""
|
|
|
|
def _wait_for_job_complete():
|
|
"""Called at an interval until the job is finished"""
|
|
if self._is_job_finished(conn, job):
|
|
raise loopingcall.LoopingCallDone()
|
|
if self.retries > JOB_RETRIES:
|
|
LOG.error(_LE("_wait_for_job_complete failed after %(retries)d"
|
|
" tries") % {'retries': self.retries})
|
|
raise loopingcall.LoopingCallDone()
|
|
try:
|
|
self.retries += 1
|
|
if not self.wait_for_job_called:
|
|
if self._is_job_finished(conn, job):
|
|
self.wait_for_job_called = True
|
|
except Exception as e:
|
|
LOG.error(_LE("Exception: %s") % six.text_type(e))
|
|
exceptionMessage = (_("Issue encountered waiting for job."))
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(exceptionMessage)
|
|
|
|
self.retries = 0
|
|
self.wait_for_job_called = False
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_job_complete)
|
|
timer.start(interval=INTERVAL_10_SEC).wait()
|
|
|
|
jobInstanceName = job['Job']
|
|
jobinstance = conn.GetInstance(jobInstanceName,
|
|
LocalOnly=False)
|
|
rc = jobinstance['ErrorCode']
|
|
errordesc = jobinstance['ErrorDescription']
|
|
|
|
return rc, errordesc
|
|
|
|
def _is_job_finished(self, conn, job):
|
|
"""Check if the job is finished.
|
|
:param conn: connection the ecom server
|
|
:param job: the job dict
|
|
|
|
:returns: True if finished; False if not finished;
|
|
"""
|
|
jobInstanceName = job['Job']
|
|
jobinstance = conn.GetInstance(jobInstanceName,
|
|
LocalOnly=False)
|
|
jobstate = jobinstance['JobState']
|
|
# From ValueMap of JobState in CIM_ConcreteJob
|
|
# 2L=New, 3L=Starting, 4L=Running, 32767L=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 [2L, 3L, 4L, 32767L]:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def wait_for_sync(self, conn, syncName):
|
|
"""Given the sync name wait for it to fully synchronize.
|
|
:param conn: connection the ecom server
|
|
:param syncName: the syncName
|
|
"""
|
|
|
|
def _wait_for_sync():
|
|
"""Called at an interval until the synchronization is finished"""
|
|
if self._is_sync_complete(conn, syncName):
|
|
raise loopingcall.LoopingCallDone()
|
|
if self.retries > JOB_RETRIES:
|
|
LOG.error(_LE("_wait_for_sync failed after %(retries)d tries")
|
|
% {'retries': self.retries})
|
|
raise loopingcall.LoopingCallDone()
|
|
try:
|
|
self.retries += 1
|
|
if not self.wait_for_sync_called:
|
|
if self._is_sync_complete(conn, syncName):
|
|
self.wait_for_sync_called = True
|
|
except Exception as e:
|
|
LOG.error(_LE("Exception: %s") % six.text_type(e))
|
|
exceptionMessage = (_("Issue encountered waiting for "
|
|
"synchronization."))
|
|
LOG.error(exceptionMessage)
|
|
raise exception.VolumeBackendAPIException(exceptionMessage)
|
|
|
|
self.retries = 0
|
|
self.wait_for_sync_called = False
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_sync)
|
|
timer.start(interval=INTERVAL_10_SEC).wait()
|
|
|
|
def _is_sync_complete(self, conn, syncName):
|
|
"""Check if the job is finished.
|
|
:param conn: connection the ecom server
|
|
:param syncName: the sync name
|
|
|
|
:returns: True if fully synchronized; False if not;
|
|
"""
|
|
syncInstance = conn.GetInstance(syncName,
|
|
LocalOnly=False)
|
|
percentSynced = syncInstance['PercentSynced']
|
|
|
|
if percentSynced < 100:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
# Find LunMaskingSCSIProtocolController for the local host on the
|
|
# specified storage system
|
|
def _find_lunmasking_scsi_protocol_controller(self, storage_system,
|
|
connector):
|
|
foundCtrls = []
|
|
initiators = self._find_initiator_names(connector)
|
|
controllers = self.conn.EnumerateInstanceNames(
|
|
SCSI_PROT_CTR)
|
|
for ctrl in controllers:
|
|
if storage_system != ctrl['SystemName']:
|
|
continue
|
|
associators =\
|
|
self.conn.Associators(ctrl,
|
|
ResultClass=AUTH_PRIV)
|
|
for assoc in associators:
|
|
for initiator in initiators:
|
|
if initiator.lower() not in assoc['InstanceID'].lower():
|
|
continue
|
|
|
|
LOG.debug('_find_lunmasking_scsi_protocol_controller,'
|
|
'AffinityGroup:%(ag)s'
|
|
% {'ag': ctrl})
|
|
foundCtrls.append(ctrl)
|
|
break
|
|
break
|
|
|
|
LOG.debug("LunMaskingSCSIProtocolController for storage system "
|
|
"%(storage_system)s and initiator %(initiator)s is "
|
|
"%(ctrl)s."
|
|
% {'storage_system': storage_system,
|
|
'initiator': initiators,
|
|
'ctrl': foundCtrls})
|
|
return foundCtrls
|
|
|
|
# Find LunMaskingSCSIProtocolController for the local host and the
|
|
# specified storage volume
|
|
def _find_lunmasking_scsi_protocol_controller_for_vol(self, vol_instance,
|
|
connector):
|
|
foundCtrls = []
|
|
initiators = self._find_initiator_names(connector)
|
|
controllers =\
|
|
self.conn.AssociatorNames(
|
|
vol_instance.path,
|
|
ResultClass=SCSI_PROT_CTR)
|
|
LOG.debug('_find_lunmasking_scsi_protocol_controller_for_vol:'
|
|
'controllers:%(controllers)s'
|
|
% {'controllers': controllers})
|
|
for ctrl in controllers:
|
|
associators =\
|
|
self.conn.Associators(
|
|
ctrl,
|
|
ResultClass=AUTH_PRIV)
|
|
foundCtrl = None
|
|
for assoc in associators:
|
|
for initiator in initiators:
|
|
if initiator.lower() not in assoc['InstanceID'].lower():
|
|
continue
|
|
|
|
LOG.debug('_find_lunmasking_scsi_protocol_controller'
|
|
'_for_vol,'
|
|
'AffinityGroup:%(ag)s'
|
|
% {'ag': ctrl})
|
|
foundCtrl = ctrl
|
|
foundCtrls.append(foundCtrl)
|
|
break
|
|
if foundCtrl is not None:
|
|
break
|
|
|
|
LOG.debug("LunMaskingSCSIProtocolController for storage volume "
|
|
"%(vol)s and initiator %(initiator)s is %(ctrl)s."
|
|
% {'vol': vol_instance.path,
|
|
'initiator': initiators,
|
|
'ctrl': foundCtrls})
|
|
return foundCtrls
|
|
|
|
# Find out how many volumes are mapped to a host
|
|
# assoociated to the LunMaskingSCSIProtocolController
|
|
def get_num_volumes_mapped(self, volume, connector):
|
|
numVolumesMapped = 0
|
|
volumename = volume['name']
|
|
vol_instance = self._find_lun(volume)
|
|
if vol_instance is None:
|
|
msg = (_('Volume %(name)s not found on the array. '
|
|
'Cannot determine if there are volumes mapped.')
|
|
% {'name': volumename})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
storage_system = vol_instance['SystemName']
|
|
|
|
ctrl = self._find_lunmasking_scsi_protocol_controller(
|
|
storage_system,
|
|
connector)
|
|
|
|
LOG.debug("LunMaskingSCSIProtocolController for storage system "
|
|
"%(storage)s and %(connector)s is %(ctrl)s."
|
|
% {'storage': storage_system,
|
|
'connector': connector,
|
|
'ctrl': ctrl})
|
|
|
|
associators = self.conn.Associators(
|
|
ctrl,
|
|
resultClass=STOR_VOL)
|
|
|
|
numVolumesMapped = len(associators)
|
|
|
|
LOG.debug("Found %(numVolumesMapped)d volumes on storage system "
|
|
"%(storage)s mapped to %(connector)s."
|
|
% {'numVolumesMapped': numVolumesMapped,
|
|
'storage': storage_system,
|
|
'connector': connector})
|
|
return numVolumesMapped
|
|
|
|
# Find a device number that a host can see for a volume
|
|
def find_device_number(self, volume, connector):
|
|
out_num_device_number = None
|
|
|
|
volumename = self._create_volume_name(volume['id'])
|
|
vol_instance = self._find_lun(volume)
|
|
storage_system = vol_instance['SystemName']
|
|
sp = None
|
|
|
|
ctrls = self._find_lunmasking_scsi_protocol_controller_for_vol(
|
|
vol_instance,
|
|
connector)
|
|
|
|
LOG.debug("LunMaskingSCSIProtocolController for "
|
|
"volume %(vol)s and connector %(connector)s "
|
|
"is %(ctrl)s."
|
|
% {'vol': vol_instance.path,
|
|
'connector': connector,
|
|
'ctrl': ctrls})
|
|
|
|
if len(ctrls) != 0:
|
|
unitnames = self.conn.ReferenceNames(
|
|
vol_instance.path,
|
|
ResultClass='CIM_ProtocolControllerForUnit')
|
|
|
|
for unitname in unitnames:
|
|
controller = unitname['Antecedent']
|
|
classname = controller['CreationClassName']
|
|
index = classname.find(SCSI_PROT_CTR)
|
|
if index > -1:
|
|
if ctrls[0]['DeviceID'] != controller['DeviceID']:
|
|
continue
|
|
# Get an instance of CIM_ProtocolControllerForUnit
|
|
unitinstance = self.conn.GetInstance(unitname,
|
|
LocalOnly=False)
|
|
numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
|
|
out_num_device_number = numDeviceNumber
|
|
break
|
|
|
|
if out_num_device_number is None:
|
|
LOG.info(_LI("Device number not found for volume "
|
|
"%(volumename)s %(vol_instance)s.")
|
|
% {'volumename': volumename,
|
|
'vol_instance': vol_instance.path})
|
|
else:
|
|
LOG.debug("Found device number %(device)d for volume "
|
|
"%(volumename)s %(vol_instance)s." %
|
|
{'device': out_num_device_number,
|
|
'volumename': volumename,
|
|
'vol_instance': vol_instance.path})
|
|
|
|
data = {'hostlunid': out_num_device_number,
|
|
'storagesystem': storage_system,
|
|
'owningsp': sp}
|
|
|
|
LOG.debug("Device info: %(data)s." % {'data': data})
|
|
|
|
return data
|
|
|
|
def _getnum(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
|
|
|
|
def _getinstancename(self, classname, bindings):
|
|
instancename = None
|
|
try:
|
|
instancename = pywbem.CIMInstanceName(
|
|
classname,
|
|
namespace=SMIS_ROOT,
|
|
keybindings=bindings)
|
|
except NameError:
|
|
instancename = None
|
|
|
|
return instancename
|
|
|
|
# Find Storage Hardware IDs
|
|
def _find_storage_hardwareids(self, connector):
|
|
foundInstances = []
|
|
wwpns = self._find_initiator_names(connector)
|
|
hardwareids = self.conn.EnumerateInstances(
|
|
STOR_HWID)
|
|
for hardwareid in hardwareids:
|
|
storid = hardwareid['StorageID']
|
|
for wwpn in wwpns:
|
|
if wwpn.lower() == storid.lower():
|
|
foundInstances.append(hardwareid.path)
|
|
|
|
LOG.debug("Storage Hardware IDs for %(wwpns)s is "
|
|
"%(foundInstances)s."
|
|
% {'wwpns': wwpns,
|
|
'foundInstances': foundInstances})
|
|
|
|
return foundInstances
|
|
|
|
def _get_volumetype_extraspecs(self, volume):
|
|
specs = {}
|
|
type_id = volume['volume_type_id']
|
|
if type_id is not None:
|
|
specs = volume_types.get_volume_type_extra_specs(type_id)
|
|
# If specs['storagetype:pool'] not defined,
|
|
# set specs to {} so we can ready from config file later
|
|
if POOL not in specs:
|
|
specs = {}
|
|
|
|
return specs
|
|
|
|
def _get_provisioning(self, storage_type):
|
|
# provisioning is thin (5) by default
|
|
provisioning = 5
|
|
thick_str = 'thick'
|
|
try:
|
|
type_prov = storage_type[PROVISIONING]
|
|
if type_prov.lower() == thick_str.lower():
|
|
provisioning = 2
|
|
except KeyError:
|
|
# Default to thin if not defined
|
|
pass
|
|
|
|
return provisioning
|
|
|
|
def _create_volume_name(self, id_code):
|
|
"""create volume_name on ETERNUS from id on OpenStack."""
|
|
|
|
LOG.debug('_create_volume_name [%s],Enter method.'
|
|
% id_code)
|
|
|
|
if id_code is None:
|
|
msg = (_('_create_volume_name,'
|
|
'id_code is None.'))
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# pylint: disable=E1101
|
|
m = hashlib.md5()
|
|
m.update(id_code)
|
|
# pylint: disable=E1121
|
|
volname = base64.urlsafe_b64encode((m.digest()))
|
|
ret = VOL_PREFIX + six.text_type(volname)
|
|
|
|
LOG.debug('_create_volume_name: '
|
|
' id:%(id)s'
|
|
' volumename:%(ret)s'
|
|
' Exit method.'
|
|
% {'id': id_code, 'ret': ret})
|
|
|
|
return ret
|
|
|
|
def _get_pool_instance_id(self, poolname):
|
|
"""get pool instacne_id from pool name"""
|
|
LOG.debug('_get_pool_instance_id,'
|
|
'Enter method,poolname:%s'
|
|
% (poolname))
|
|
|
|
poolinstanceid = None
|
|
pool = None
|
|
pools = []
|
|
msg = None
|
|
|
|
try:
|
|
pools = self.conn.EnumerateInstances(
|
|
'CIM_StoragePool')
|
|
except Exception:
|
|
msg = (_('_get_pool_instance_id,'
|
|
'poolname:%(poolname)s,'
|
|
'EnumerateInstances,'
|
|
'cannot connect to ETERNUS.')
|
|
% {'poolname': poolname})
|
|
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
for pool in pools:
|
|
pool_elementname = pool['ElementName']
|
|
poolclass = pool.path.classname
|
|
LOG.debug('poolname from file or VolumeType:%s'
|
|
' poolname from smis:%s'
|
|
' poolclass from smis:%s'
|
|
% (poolname, pool_elementname, poolclass))
|
|
|
|
if six.text_type(poolname) == six.text_type(pool_elementname):
|
|
if poolclass in STOR_POOLS:
|
|
poolinstanceid = pool['InstanceID']
|
|
break
|
|
|
|
if poolinstanceid is None:
|
|
msg = (_('_get_pool_instance_id,'
|
|
'poolname:%(poolname)s,'
|
|
'poolinstanceid is None.')
|
|
% {'poolname': poolname})
|
|
LOG.info(msg)
|
|
|
|
LOG.debug('_get_pool_instance_id,'
|
|
'Exit method,poolinstanceid:%s'
|
|
% (poolinstanceid))
|
|
|
|
return poolinstanceid
|
|
|
|
def get_target_portid(self, connector):
|
|
"""return target_portid"""
|
|
|
|
LOG.debug('get_target_portid,Enter method')
|
|
|
|
target_portidlist = []
|
|
tgtportlist = []
|
|
tgtport = None
|
|
conn_type = {'fc': 2, 'iscsi': 7}
|
|
|
|
try:
|
|
tgtportlist = self.conn.EnumerateInstances(
|
|
'CIM_SCSIProtocolEndpoint')
|
|
except Exception:
|
|
msg = (_('get_target_portid,'
|
|
'connector:%(connector)s,'
|
|
'EnumerateInstances,'
|
|
'cannot connect to ETERNUS.')
|
|
% {'connector': connector})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
for tgtport in tgtportlist:
|
|
if tgtport['ConnectionType'] == conn_type[self.protocol.lower()]:
|
|
target_portidlist.append(tgtport['Name'])
|
|
|
|
LOG.debug('get_target_portid,'
|
|
'portid:%(portid)s,'
|
|
'connection type:%(cont)s,'
|
|
% {'portid': tgtport['Name'],
|
|
'cont': tgtport['ConnectionType']})
|
|
|
|
LOG.debug('get_target_portid,'
|
|
'target portid: %(target_portid)s '
|
|
% {'target_portid': target_portidlist})
|
|
|
|
if len(target_portidlist) == 0:
|
|
msg = (_('get_target_portid,'
|
|
'protcol:%(protocol)s,'
|
|
'connector:%(connector)s,'
|
|
'target_portid does not found.')
|
|
% {'protocol': self.protocol,
|
|
'connector': connector})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('get_target_portid,Exit method')
|
|
|
|
return target_portidlist
|
|
|
|
def _find_copysession(self, volume):
|
|
"""find copysession from volumename on ETERNUS"""
|
|
LOG.debug('_find_copysession, Enter method')
|
|
|
|
cpsession = None
|
|
vol_instance = None
|
|
repservice = None
|
|
rc = 0
|
|
replicarellist = None
|
|
replicarel = None
|
|
snapshot_vol_instance = None
|
|
msg = None
|
|
errordesc = None
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
if vol_instance is None:
|
|
return None, None
|
|
|
|
volumename = vol_instance['ElementName']
|
|
storage_system = vol_instance['SystemName']
|
|
if vol_instance is not None:
|
|
# find target_volume
|
|
|
|
# get copysession list
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
msg = (_('_find_copysession,'
|
|
'Cannot find Replication Service to '
|
|
'find copysession'))
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
def _wait_for_job(repservice):
|
|
cpsession_instance = None
|
|
LOG.debug('_find_copysession,source_volume'
|
|
' while copysession')
|
|
cpsession = None
|
|
|
|
rc, replicarellist = self.conn.InvokeMethod(
|
|
'GetReplicationRelationships',
|
|
repservice,
|
|
Type=self._getnum(2, '16'),
|
|
Mode=self._getnum(2, '16'),
|
|
Locality=self._getnum(2, '16'))
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
msg = (_('_find_copysession,'
|
|
'source_volumename:%(volumename)s,'
|
|
'Return code:%(rc)lu,'
|
|
'Error:%(errordesc)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'errordesc': errordesc})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
for replicarel in replicarellist['Synchronizations']:
|
|
LOG.debug('_find_copysession,'
|
|
'source_volume,'
|
|
'replicarel:%(replicarel)s'
|
|
% {'replicarel': replicarel})
|
|
try:
|
|
snapshot_vol_instance = self.conn.GetInstance(
|
|
replicarel['SystemElement'],
|
|
LocalOnly=False)
|
|
except Exception:
|
|
msg = (_('_find_copysession,'
|
|
'source_volumename:%(volumename)s,'
|
|
'GetInstance,'
|
|
'cannot connect to ETERNUS.')
|
|
% {'volumename': volumename})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('_find_copysession,'
|
|
'snapshot ElementName:%(elementname)s,'
|
|
'source_volumename:%(volumename)s'
|
|
% {'elementname':
|
|
snapshot_vol_instance['ElementName'],
|
|
'volumename': volumename})
|
|
|
|
if volumename == snapshot_vol_instance['ElementName']:
|
|
# find copysession
|
|
cpsession = replicarel
|
|
LOG.debug('_find_copysession,'
|
|
'volumename:%(volumename)s,'
|
|
'Storage Synchronized instance:%(sync)s'
|
|
% {'volumename': volumename,
|
|
'sync': six.text_type(cpsession)})
|
|
msg = (_('_find_copy_session,'
|
|
'source_volumename:%(volumename)s,'
|
|
'wait for end of copysession')
|
|
% {'volumename': volumename})
|
|
LOG.info(msg)
|
|
|
|
try:
|
|
cpsession_instance = self.conn.GetInstance(
|
|
replicarel)
|
|
except Exception:
|
|
break
|
|
|
|
LOG.debug('_find_copysession,'
|
|
'status:%(status)s'
|
|
% {'status':
|
|
cpsession_instance['CopyState']})
|
|
if cpsession_instance['CopyState'] == BROKEN:
|
|
msg = (_('_find_copysession,'
|
|
'source_volumename:%(volumename)s,'
|
|
'copysession state is BROKEN')
|
|
% {'volumename': volumename})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
break
|
|
else:
|
|
LOG.debug('_find_copysession,'
|
|
'volumename:%(volumename)s,'
|
|
'Storage Synchronized not found.'
|
|
% {'volumename': volumename})
|
|
|
|
if cpsession is None:
|
|
raise loopingcall.LoopingCallDone()
|
|
|
|
timer = loopingcall.FixedIntervalLoopingCall(
|
|
_wait_for_job, repservice)
|
|
timer.start(interval=10).wait()
|
|
|
|
rc, replicarellist = self.conn.InvokeMethod(
|
|
'GetReplicationRelationships',
|
|
repservice,
|
|
Type=self._getnum(2, '16'),
|
|
Mode=self._getnum(2, '16'),
|
|
Locality=self._getnum(2, '16'))
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
if rc != 0L:
|
|
msg = (_('_find_copysession,'
|
|
'source_volumename:%(volumename)s,'
|
|
'Return code:%(rc)lu,'
|
|
'Error:%(errordesc)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'errordesc': errordesc})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# find copysession for target_volume
|
|
for replicarel in replicarellist['Synchronizations']:
|
|
LOG.debug('_find_copysession,'
|
|
'replicarel:%(replicarel)s'
|
|
% {'replicarel': replicarel})
|
|
|
|
# target volume
|
|
try:
|
|
snapshot_vol_instance = self.conn.GetInstance(
|
|
replicarel['SyncedElement'],
|
|
LocalOnly=False)
|
|
except Exception:
|
|
msg = (_('_find_copysession,'
|
|
'target_volumename:%(volumename)s,'
|
|
'GetInstance,'
|
|
'cannot connect to ETERNUS.')
|
|
% {'volumename': volumename})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
LOG.debug('_find_copysession,'
|
|
'snapshot ElementName:%(elementname)s,'
|
|
'volumename:%(volumename)s'
|
|
% {'elementname':
|
|
snapshot_vol_instance['ElementName'],
|
|
'volumename': volumename})
|
|
|
|
if volumename == snapshot_vol_instance['ElementName']:
|
|
# find copysession
|
|
cpsession = replicarel
|
|
LOG.debug('_find_copysession,'
|
|
'volumename:%(volumename)s,'
|
|
'Storage Synchronized instance:%(sync)s'
|
|
% {'volumename': volumename,
|
|
'sync': six.text_type(cpsession)})
|
|
break
|
|
|
|
else:
|
|
LOG.debug('_find_copysession,'
|
|
'volumename:%(volumename)s,'
|
|
'Storage Synchronized not found.'
|
|
% {'volumename': volumename})
|
|
|
|
else:
|
|
# does not find target_volume of copysession
|
|
msg = (_('_find_copysession,'
|
|
'volumename:%(volumename)s,'
|
|
'not found.')
|
|
% {'volumename': volumename})
|
|
LOG.info(msg)
|
|
|
|
LOG.debug('_find_copysession,Exit method')
|
|
|
|
return cpsession, storage_system
|
|
|
|
def _delete_copysession(self, storage_system, cpsession):
|
|
"""delete copysession"""
|
|
LOG.debug('_delete_copysession,Entering')
|
|
LOG.debug('_delete_copysession,[%s]' % cpsession)
|
|
|
|
snapshot_instance = None
|
|
msg = None
|
|
errordesc = None
|
|
|
|
try:
|
|
snapshot_instance = self.conn.GetInstance(
|
|
cpsession,
|
|
LocalOnly=False)
|
|
except Exception:
|
|
msg = (_('_delete_copysession, '
|
|
'copysession:%(cpsession)s,'
|
|
'GetInstance,'
|
|
'cannot connect to ETERNUS.')
|
|
% {'cpsession': cpsession})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
copytype = snapshot_instance['CopyType']
|
|
|
|
# set oparation code
|
|
operation = OPERATION_dic[copytype]
|
|
|
|
repservice = self._find_replication_service(storage_system)
|
|
if repservice is None:
|
|
msg = (_('_delete_copysession,'
|
|
'Cannot find Replication Service to '
|
|
'delete copysession'))
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
# Invoke method for delete copysession
|
|
rc, job = self.conn.InvokeMethod(
|
|
'ModifyReplicaSynchronization',
|
|
repservice,
|
|
Operation=self._getnum(operation, '16'),
|
|
Synchronization=cpsession,
|
|
Force=True,
|
|
WaitForCopyState=self._getnum(15, '16'))
|
|
|
|
errordesc = RETCODE_dic[six.text_type(rc)]
|
|
|
|
LOG.debug('_delete_copysession,'
|
|
'copysession:%(cpsession)s,'
|
|
'operation:%(operation)s,'
|
|
'Return code:%(rc)lu,'
|
|
'Error:%(errordesc)s,'
|
|
'Exit method'
|
|
% {'cpsession': cpsession,
|
|
'operation': operation,
|
|
'rc': rc,
|
|
'errordesc': errordesc})
|
|
|
|
if rc != 0L:
|
|
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)
|
|
|
|
return
|