1798 lines
73 KiB
Python
1798 lines
73 KiB
Python
# 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 EMC volume drivers.
|
|
|
|
This common class is for EMC volume drivers based on SMI-S.
|
|
It supports VNX and VMAX arrays.
|
|
|
|
"""
|
|
|
|
import time
|
|
|
|
from oslo.config import cfg
|
|
from xml.dom.minidom import parseString
|
|
|
|
from cinder import exception
|
|
from cinder.openstack.common import log as logging
|
|
from cinder import units
|
|
from cinder.volume import volume_types
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
CONF = cfg.CONF
|
|
|
|
try:
|
|
import pywbem
|
|
except ImportError:
|
|
LOG.info(_('Module PyWBEM not installed. '
|
|
'Install PyWBEM using the python-pywbem package.'))
|
|
|
|
CINDER_EMC_CONFIG_FILE = '/etc/cinder/cinder_emc_config.xml'
|
|
EMC_ROOT = 'root/emc'
|
|
PROVISIONING = 'storagetype:provisioning'
|
|
POOL = 'storagetype:pool'
|
|
|
|
emc_opts = [
|
|
cfg.StrOpt('cinder_emc_config_file',
|
|
default=CINDER_EMC_CONFIG_FILE,
|
|
help='The configuration file for the Cinder '
|
|
'EMC driver'), ]
|
|
|
|
|
|
CONF.register_opts(emc_opts)
|
|
|
|
|
|
class EMCSMISCommon():
|
|
"""Common code that can be used by ISCSI and FC drivers."""
|
|
|
|
stats = {'driver_version': '1.0',
|
|
'free_capacity_gb': 0,
|
|
'reserved_percentage': 0,
|
|
'storage_protocol': None,
|
|
'total_capacity_gb': 0,
|
|
'vendor_name': 'EMC',
|
|
'volume_backend_name': None}
|
|
|
|
def __init__(self, prtcl, configuration=None):
|
|
|
|
self.protocol = prtcl
|
|
self.configuration = configuration
|
|
self.configuration.append_config_values(emc_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 EMC(VMAX/VNX) volume."""
|
|
LOG.debug('Entering create_volume.')
|
|
volumesize = int(volume['size']) * units.GiB
|
|
volumename = volume['name']
|
|
|
|
LOG.info(_('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 != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
if rc != 0L:
|
|
LOG.error(_('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
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass='EMC_StorageVolume')
|
|
volpath = associators[0].path
|
|
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 = volume['name']
|
|
|
|
LOG.info(_('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})
|
|
|
|
isVMAX = storage_system.find('SYMMETRIX')
|
|
if isVMAX > -1:
|
|
exception_message = (_('Error Create Volume from Snapshot: '
|
|
'Volume: %(volumename)s Snapshot: '
|
|
'%(snapshotname)s. Create Volume '
|
|
'from Snapshot is NOT supported on VMAX.')
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname})
|
|
LOG.error(exception_message)
|
|
raise exception.VolumeBackendAPIException(data=exception_message)
|
|
|
|
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
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateElementReplica', repservice,
|
|
ElementName=volumename,
|
|
SyncType=self._getnum(8, '16'),
|
|
SourceElement=snapshot_instance.path)
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
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)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Find the newly created volume
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass='EMC_StorageVolume')
|
|
volpath = associators[0].path
|
|
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('Create Volume from Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s. Successfully clone volume '
|
|
'from snapshot. Finding the clone relationship.'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname})
|
|
|
|
volume['provider_location'] = str(name)
|
|
sync_name, storage_system = self._find_storage_sync_sv_sv(
|
|
volume, snapshot)
|
|
|
|
# Remove the Clone relationshop so it can be used as a regular lun
|
|
# 8 - Detach operation
|
|
LOG.debug('Create Volume from Snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s. Remove the clone '
|
|
'relationship. Method: ModifyReplicaSynchronization '
|
|
'ReplicationService: %(service)s Operation: 8 '
|
|
'Synchronization: %(sync_name)s'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'service': repservice,
|
|
'sync_name': sync_name})
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'ModifyReplicaSynchronization',
|
|
repservice,
|
|
Operation=self._getnum(8, '16'),
|
|
Synchronization=sync_name)
|
|
|
|
LOG.debug('Create Volume from 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(job)
|
|
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)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
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 = src_vref['name']
|
|
volumename = volume['name']
|
|
|
|
LOG.info(_('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
|
|
rc, job = self.conn.InvokeMethod(
|
|
'CreateElementReplica', repservice,
|
|
ElementName=volumename,
|
|
SyncType=self._getnum(8, '16'),
|
|
SourceElement=src_instance.path)
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
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)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
# Find the newly created volume
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass='EMC_StorageVolume')
|
|
volpath = associators[0].path
|
|
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('Create Cloned Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s. Successfully cloned volume '
|
|
'from source volume. Finding the clone relationship.'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname})
|
|
|
|
volume['provider_location'] = str(name)
|
|
sync_name, storage_system = self._find_storage_sync_sv_sv(
|
|
volume, src_vref)
|
|
|
|
# Remove the Clone relationshop so it can be used as a regular lun
|
|
# 8 - Detach operation
|
|
LOG.debug('Create Cloned Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s. Remove the clone '
|
|
'relationship. Method: ModifyReplicaSynchronization '
|
|
'ReplicationService: %(service)s Operation: 8 '
|
|
'Synchronization: %(sync_name)s'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'service': repservice,
|
|
'sync_name': sync_name})
|
|
|
|
rc, job = self.conn.InvokeMethod(
|
|
'ModifyReplicaSynchronization',
|
|
repservice,
|
|
Operation=self._getnum(8, '16'),
|
|
Synchronization=sync_name)
|
|
|
|
LOG.debug('Create Cloned Volume: Volume: %(volumename)s '
|
|
'Source Volume: %(srcname)s Return code: %(rc)lu'
|
|
% {'volumename': volumename,
|
|
'srcname': srcname,
|
|
'rc': rc})
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
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)
|
|
raise exception.VolumeBackendAPIException(
|
|
data=exception_message)
|
|
|
|
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 EMC volume."""
|
|
LOG.debug('Entering delete_volume.')
|
|
volumename = volume['name']
|
|
LOG.info(_('Delete Volume: %(volume)s')
|
|
% {'volume': volumename})
|
|
|
|
self.conn = self._get_ecom_connection()
|
|
|
|
vol_instance = self._find_lun(volume)
|
|
if vol_instance is None:
|
|
LOG.error(_('Volume %(name)s not found on the array. '
|
|
'No volume to delete.')
|
|
% {'name': volumename})
|
|
return
|
|
|
|
storage_system = vol_instance['SystemName']
|
|
|
|
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: EMCReturnToStoragePool '
|
|
'ConfigServic: %(service)s TheElement: %(vol_instance)s'
|
|
% {'service': configservice,
|
|
'name': volumename,
|
|
'vol_instance': vol_instance.path})
|
|
|
|
rc, job =\
|
|
self.conn.InvokeMethod('EMCReturnToStoragePool',
|
|
configservice,
|
|
TheElements=[vol_instance.path])
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
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 = snapshot['name']
|
|
volumename = snapshot['volume_name']
|
|
LOG.info(_('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']
|
|
storage_system = vol_instance['SystemName']
|
|
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(_("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'),
|
|
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 != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
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
|
|
associators = self.conn.Associators(
|
|
job['Job'],
|
|
resultClass='EMC_StorageVolume')
|
|
volpath = associators[0].path
|
|
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(_('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(_('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(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())
|
|
while True:
|
|
try:
|
|
sync_name, storage_system =\
|
|
self._find_storage_sync_sv_sv(snapshot, volume, False)
|
|
if sync_name is None:
|
|
LOG.info(_('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot is deleted.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
break
|
|
time.sleep(wait_interval)
|
|
if int(time.time()) - start >= wait_timeout:
|
|
LOG.warn(_('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot deleted but cleanup timed out.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
break
|
|
except Exception as ex:
|
|
if ex.args[0] == 6:
|
|
# 6 means object not found, so snapshot is deleted cleanly
|
|
LOG.info(_('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot is deleted.')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename})
|
|
else:
|
|
LOG.warn(_('Snapshot: %(snapshot)s: volume: %(volume)s. '
|
|
'Snapshot deleted but error during cleanup. '
|
|
'Error: %(error)s')
|
|
% {'snapshot': snapshotname,
|
|
'volume': volumename,
|
|
'error': str(ex.args)})
|
|
break
|
|
|
|
LOG.debug('Leaving delete_snapshot: Volume: %(volumename)s '
|
|
'Snapshot: %(snapshotname)s Return code: %(rc)lu.'
|
|
% {'volumename': volumename,
|
|
'snapshotname': snapshotname,
|
|
'rc': rc})
|
|
|
|
# Mapping method for VNX
|
|
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.
|
|
"""
|
|
volumename = vol_instance['ElementName']
|
|
lun_name = vol_instance['DeviceID']
|
|
initiators = self._find_initiator_names(connector)
|
|
storage_system = vol_instance['SystemName']
|
|
lunmask_ctrl = self._find_lunmasking_scsi_protocol_controller(
|
|
storage_system, connector)
|
|
|
|
LOG.debug('ExposePaths: %(vol)s ConfigServicie: %(service)s '
|
|
'LUNames: %(lun_name)s InitiatorPortIDs: %(initiator)s '
|
|
'DeviceAccesses: 2'
|
|
% {'vol': vol_instance.path,
|
|
'service': configservice,
|
|
'lun_name': lun_name,
|
|
'initiator': initiators})
|
|
|
|
if lunmask_ctrl is None:
|
|
rc, controller =\
|
|
self.conn.InvokeMethod('ExposePaths',
|
|
configservice, LUNames=[lun_name],
|
|
InitiatorPortIDs=initiators,
|
|
DeviceAccesses=[self._getnum(2, '16')])
|
|
else:
|
|
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])
|
|
|
|
if rc != 0L:
|
|
msg = (_('Error mapping volume %s.') % volumename)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('ExposePaths for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
# Unmapping method for VNX
|
|
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']
|
|
device_id = vol_instance['DeviceID']
|
|
lunmask_ctrl = self._find_lunmasking_scsi_protocol_controller_for_vol(
|
|
vol_instance, connector)
|
|
|
|
LOG.debug('HidePaths: %(vol)s ConfigServicie: %(service)s '
|
|
'LUNames: %(device_id)s LunMaskingSCSIProtocolController: '
|
|
'%(lunmasking)s'
|
|
% {'vol': vol_instance.path,
|
|
'service': configservice,
|
|
'device_id': device_id,
|
|
'lunmasking': lunmask_ctrl})
|
|
|
|
rc, controller = self.conn.InvokeMethod(
|
|
'HidePaths', configservice,
|
|
LUNames=[device_id], ProtocolControllers=[lunmask_ctrl])
|
|
|
|
if rc != 0L:
|
|
msg = (_('Error unmapping volume %s.') % volumename)
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('HidePaths for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
# Mapping method for VMAX
|
|
def _add_members(self, configservice, vol_instance):
|
|
"""This method maps a volume to a host.
|
|
|
|
Add volume to the Device Masking Group that belongs to
|
|
a Masking View.
|
|
"""
|
|
volumename = vol_instance['ElementName']
|
|
masking_group = self._find_device_masking_group()
|
|
|
|
LOG.debug('AddMembers: ConfigServicie: %(service)s MaskingGroup: '
|
|
'%(masking_group)s Members: %(vol)s'
|
|
% {'service': configservice,
|
|
'masking_group': masking_group,
|
|
'vol': vol_instance.path})
|
|
|
|
rc, job =\
|
|
self.conn.InvokeMethod('AddMembers',
|
|
configservice,
|
|
MaskingGroup=masking_group,
|
|
Members=[vol_instance.path])
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
if rc != 0L:
|
|
msg = (_('Error mapping volume %(vol)s. %(error)s') %
|
|
{'vol': volumename, 'error': errordesc})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('AddMembers for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
# Unmapping method for VMAX
|
|
def _remove_members(self, configservice, vol_instance):
|
|
"""This method unmaps a volume from a host.
|
|
|
|
Removes volume from the Device Masking Group that belongs to
|
|
a Masking View.
|
|
"""
|
|
volumename = vol_instance['ElementName']
|
|
masking_group = self._find_device_masking_group()
|
|
|
|
LOG.debug('RemoveMembers: ConfigServicie: %(service)s '
|
|
'MaskingGroup: %(masking_group)s Members: %(vol)s'
|
|
% {'service': configservice,
|
|
'masking_group': masking_group,
|
|
'vol': vol_instance.path})
|
|
|
|
rc, job = self.conn.InvokeMethod('RemoveMembers', configservice,
|
|
MaskingGroup=masking_group,
|
|
Members=[vol_instance.path])
|
|
|
|
if rc != 0L:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
if rc != 0L:
|
|
msg = (_('Error unmapping volume %(vol)s. %(error)s')
|
|
% {'vol': volumename, 'error': errordesc})
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
LOG.debug('RemoveMembers for volume %s completed successfully.'
|
|
% volumename)
|
|
|
|
def _map_lun(self, volume, connector):
|
|
"""Maps a volume to the host."""
|
|
volumename = volume['name']
|
|
LOG.info(_('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)
|
|
|
|
isVMAX = storage_system.find('SYMMETRIX')
|
|
if isVMAX > -1:
|
|
self._add_members(configservice, vol_instance)
|
|
else:
|
|
self._expose_paths(configservice, vol_instance, connector)
|
|
|
|
def _unmap_lun(self, volume, connector):
|
|
"""Unmaps a volume from the host."""
|
|
volumename = volume['name']
|
|
LOG.info(_('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(_("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)
|
|
|
|
isVMAX = storage_system.find('SYMMETRIX')
|
|
if isVMAX > -1:
|
|
self._remove_members(configservice, vol_instance)
|
|
else:
|
|
self._hide_paths(configservice, vol_instance, connector)
|
|
|
|
def initialize_connection(self, volume, connector):
|
|
"""Initializes the connection and returns connection info."""
|
|
volumename = volume['name']
|
|
LOG.info(_('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(_("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 = volume['name']
|
|
LOG.info(_('Terminate connection: %(volume)s')
|
|
% {'volume': volumename})
|
|
self.conn = self._get_ecom_connection()
|
|
self._unmap_lun(volume, connector)
|
|
|
|
def extend_volume(self, volume, new_size):
|
|
"""Extends an existing volume."""
|
|
LOG.debug('Entering extend_volume.')
|
|
volumesize = int(new_size) * units.GiB
|
|
volumename = volume['name']
|
|
|
|
LOG.info(_('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:
|
|
rc, errordesc = self._wait_for_job_complete(job)
|
|
if rc != 0L:
|
|
LOG.error(_('Error Extend Volume: %(volumename)s. '
|
|
'Return code: %(rc)lu. Error: %(error)s')
|
|
% {'volumename': volumename,
|
|
'rc': rc,
|
|
'error': errordesc})
|
|
raise exception.VolumeBackendAPIException(data=errordesc)
|
|
|
|
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 == None:
|
|
filename = self.configuration.cinder_emc_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_masking_view(self, filename=None):
|
|
if filename is None:
|
|
filename = self.configuration.cinder_emc_config_file
|
|
|
|
file = open(filename, 'r')
|
|
data = file.read()
|
|
file.close()
|
|
dom = parseString(data)
|
|
views = dom.getElementsByTagName('MaskingView')
|
|
if views is not None and len(views) > 0:
|
|
view = views[0].toxml().replace('<MaskingView>', '')
|
|
view = view.replace('</MaskingView>', '')
|
|
LOG.debug("Found Masking View: %s" % (view))
|
|
return view
|
|
else:
|
|
LOG.debug("Masking View not found.")
|
|
return None
|
|
|
|
def _get_timeout(self, filename=None):
|
|
if filename is None:
|
|
filename = self.configuration.cinder_emc_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_emc_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_emc_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='root/emc')
|
|
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(
|
|
'EMC_ReplicationService')
|
|
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(
|
|
'EMC_StorageConfigurationService')
|
|
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(
|
|
'EMC_ControllerConfigurationService')
|
|
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(
|
|
'EMC_StorageHardwareIDManagementService')
|
|
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
|
|
# Only get instance names if details flag is False;
|
|
# Otherwise get the whole instances
|
|
if details is False:
|
|
vpools = self.conn.EnumerateInstanceNames(
|
|
'EMC_VirtualProvisioningPool')
|
|
upools = self.conn.EnumerateInstanceNames(
|
|
'EMC_UnifiedStoragePool')
|
|
else:
|
|
vpools = self.conn.EnumerateInstances(
|
|
'EMC_VirtualProvisioningPool')
|
|
upools = self.conn.EnumerateInstances(
|
|
'EMC_UnifiedStoragePool')
|
|
|
|
for upool in upools:
|
|
poolinstance = upool['InstanceID']
|
|
# Example: CLARiiON+APM00115204878+U+Pool 0
|
|
poolname, systemname = self._parse_pool_instance_id(poolinstance)
|
|
if poolname is not None and systemname is not None:
|
|
if str(storage_type) == str(poolname):
|
|
foundPool = upool
|
|
break
|
|
|
|
if foundPool is None:
|
|
for vpool in vpools:
|
|
poolinstance = vpool['InstanceID']
|
|
# Example: SYMMETRIX+000195900551+TP+Sol_Innov
|
|
poolname, systemname = self._parse_pool_instance_id(
|
|
poolinstance)
|
|
if poolname is not None and systemname is not None:
|
|
if str(storage_type) == str(poolname):
|
|
foundPool = vpool
|
|
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 _parse_pool_instance_id(self, instanceid):
|
|
# Example of pool InstanceId: CLARiiON+APM00115204878+U+Pool 0
|
|
poolname = None
|
|
systemname = None
|
|
endp = instanceid.rfind('+')
|
|
if endp > -1:
|
|
poolname = instanceid[endp + 1:]
|
|
|
|
idarray = instanceid.split('+')
|
|
if len(idarray) > 2:
|
|
systemname = idarray[0] + '+' + idarray[1]
|
|
|
|
LOG.debug("Pool name: %(poolname)s System name: %(systemname)s."
|
|
% {'poolname': poolname, 'systemname': systemname})
|
|
return poolname, systemname
|
|
|
|
def _find_lun(self, volume):
|
|
foundinstance = None
|
|
|
|
volumename = volume['name']
|
|
loc = volume['provider_location']
|
|
name = eval(loc)
|
|
instancename = self._getinstancename(name['classname'],
|
|
name['keybindings'])
|
|
foundinstance = self.conn.GetInstance(instancename)
|
|
|
|
if foundinstance is None:
|
|
LOG.debug("Volume %(volumename)s not found on the array."
|
|
% {'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
|
|
percent_synced = 0
|
|
|
|
snapshotname = snapshot['name']
|
|
volumename = volume['name']
|
|
LOG.debug("Source: %(volumename)s Target: %(snapshotname)s."
|
|
% {'volumename': volumename, 'snapshotname': snapshotname})
|
|
|
|
snapshot_instance = self._find_lun(snapshot)
|
|
volume_instance = self._find_lun(volume)
|
|
storage_system = volume_instance['SystemName']
|
|
classname = 'SE_StorageSynchronized_SV_SV'
|
|
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
|
|
while waitforsync and percent_synced < 100:
|
|
time.sleep(10)
|
|
sync_instance = self.conn.GetInstance(foundsyncname,
|
|
LocalOnly=False)
|
|
percent_synced = sync_instance['PercentSynced']
|
|
|
|
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, job):
|
|
jobinstancename = job['Job']
|
|
|
|
while True:
|
|
jobinstance = self.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")]
|
|
if jobstate in [2L, 3L, 4L, 32767L]:
|
|
time.sleep(10)
|
|
else:
|
|
break
|
|
|
|
rc = jobinstance['ErrorCode']
|
|
errordesc = jobinstance['ErrorDescription']
|
|
|
|
return rc, errordesc
|
|
|
|
# Find LunMaskingSCSIProtocolController for the local host on the
|
|
# specified storage system
|
|
def _find_lunmasking_scsi_protocol_controller(self, storage_system,
|
|
connector):
|
|
foundCtrl = None
|
|
initiators = self._find_initiator_names(connector)
|
|
controllers = self.conn.EnumerateInstanceNames(
|
|
'EMC_LunMaskingSCSIProtocolController')
|
|
for ctrl in controllers:
|
|
if storage_system != ctrl['SystemName']:
|
|
continue
|
|
associators =\
|
|
self.conn.Associators(ctrl,
|
|
resultClass='EMC_StorageHardwareID')
|
|
for assoc in associators:
|
|
# if EMC_StorageHardwareID matches the initiator,
|
|
# we found the existing EMC_LunMaskingSCSIProtocolController
|
|
# (Storage Group for VNX)
|
|
# we can use for masking a new LUN
|
|
hardwareid = assoc['StorageID']
|
|
for initiator in initiators:
|
|
if hardwareid.lower() == initiator.lower():
|
|
foundCtrl = ctrl
|
|
break
|
|
|
|
if foundCtrl is not None:
|
|
break
|
|
|
|
if foundCtrl is not None:
|
|
break
|
|
|
|
LOG.debug("LunMaskingSCSIProtocolController for storage system "
|
|
"%(storage_system)s and initiator %(initiator)s is "
|
|
"%(ctrl)s."
|
|
% {'storage_system': storage_system,
|
|
'initiator': initiators,
|
|
'ctrl': foundCtrl})
|
|
return foundCtrl
|
|
|
|
# Find LunMaskingSCSIProtocolController for the local host and the
|
|
# specified storage volume
|
|
def _find_lunmasking_scsi_protocol_controller_for_vol(self, vol_instance,
|
|
connector):
|
|
foundCtrl = None
|
|
initiators = self._find_initiator_names(connector)
|
|
controllers =\
|
|
self.conn.AssociatorNames(
|
|
vol_instance.path,
|
|
resultClass='EMC_LunMaskingSCSIProtocolController')
|
|
|
|
for ctrl in controllers:
|
|
associators =\
|
|
self.conn.Associators(
|
|
ctrl,
|
|
resultClass='EMC_StorageHardwareID')
|
|
for assoc in associators:
|
|
# if EMC_StorageHardwareID matches the initiator,
|
|
# we found the existing EMC_LunMaskingSCSIProtocolController
|
|
# (Storage Group for VNX)
|
|
# we can use for masking a new LUN
|
|
hardwareid = assoc['StorageID']
|
|
for initiator in initiators:
|
|
if hardwareid.lower() == initiator.lower():
|
|
foundCtrl = ctrl
|
|
break
|
|
|
|
if foundCtrl is not None:
|
|
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': foundCtrl})
|
|
return foundCtrl
|
|
|
|
# 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='EMC_StorageVolume')
|
|
|
|
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 an available device number that a host can see
|
|
def _find_avail_device_number(self, storage_system):
|
|
out_device_number = '000000'
|
|
out_num_device_number = 0
|
|
numlist = []
|
|
myunitnames = []
|
|
|
|
unitnames = self.conn.EnumerateInstanceNames(
|
|
'CIM_ProtocolControllerForUnit')
|
|
for unitname in unitnames:
|
|
controller = unitname['Antecedent']
|
|
if storage_system != controller['SystemName']:
|
|
continue
|
|
classname = controller['CreationClassName']
|
|
index = classname.find('LunMaskingSCSIProtocolController')
|
|
if index > -1:
|
|
unitinstance = self.conn.GetInstance(unitname,
|
|
LocalOnly=False)
|
|
numDeviceNumber = int(unitinstance['DeviceNumber'])
|
|
numlist.append(numDeviceNumber)
|
|
myunitnames.append(unitname)
|
|
|
|
maxnum = max(numlist)
|
|
out_num_device_number = maxnum + 1
|
|
|
|
out_device_number = '%06d' % out_num_device_number
|
|
|
|
LOG.debug("Available device number on %(storage)s: %(device)s."
|
|
% {'storage': storage_system, 'device': out_device_number})
|
|
return out_device_number
|
|
|
|
# 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 = volume['name']
|
|
vol_instance = self._find_lun(volume)
|
|
storage_system = vol_instance['SystemName']
|
|
sp = None
|
|
try:
|
|
sp = vol_instance['EMCCurrentOwningStorageProcessor']
|
|
except KeyError:
|
|
# VMAX LUN doesn't have this property
|
|
pass
|
|
|
|
indexVMAX = storage_system.find('SYMMETRIX')
|
|
if indexVMAX == -1:
|
|
# find out whether the volume is already attached to the host
|
|
ctrl = 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': ctrl})
|
|
|
|
if indexVMAX > -1 or ctrl:
|
|
unitnames = self.conn.ReferenceNames(
|
|
vol_instance.path,
|
|
ResultClass='CIM_ProtocolControllerForUnit')
|
|
|
|
for unitname in unitnames:
|
|
controller = unitname['Antecedent']
|
|
classname = controller['CreationClassName']
|
|
index = classname.find('LunMaskingSCSIProtocolController')
|
|
if index > -1: # VNX
|
|
if ctrl['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
|
|
else:
|
|
index = classname.find('Symm_LunMaskingView')
|
|
if index > -1: # VMAX
|
|
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(_("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 _find_device_masking_group(self):
|
|
"""Finds the Device Masking Group in a masking view."""
|
|
foundMaskingGroup = None
|
|
maskingview_name = self._get_masking_view()
|
|
|
|
maskingviews = self.conn.EnumerateInstanceNames(
|
|
'EMC_LunMaskingSCSIProtocolController')
|
|
for view in maskingviews:
|
|
instance = self.conn.GetInstance(view, LocalOnly=False)
|
|
if maskingview_name == instance['ElementName']:
|
|
foundView = view
|
|
break
|
|
|
|
groups = self.conn.AssociatorNames(
|
|
foundView,
|
|
ResultClass='SE_DeviceMaskingGroup')
|
|
foundMaskingGroup = groups[0]
|
|
|
|
LOG.debug("Masking view: %(view)s DeviceMaskingGroup: %(masking)s."
|
|
% {'view': maskingview_name,
|
|
'masking': foundMaskingGroup})
|
|
|
|
return foundMaskingGroup
|
|
|
|
# Find a StorageProcessorSystem given sp and storage system
|
|
def _find_storage_processor_system(self, owningsp, storage_system):
|
|
foundSystem = None
|
|
systems = self.conn.EnumerateInstanceNames(
|
|
'EMC_StorageProcessorSystem')
|
|
for system in systems:
|
|
# Clar_StorageProcessorSystem.CreationClassName=
|
|
# "Clar_StorageProcessorSystem",Name="CLARiiON+APM00123907237+SP_A"
|
|
idarray = system['Name'].split('+')
|
|
if len(idarray) > 2:
|
|
storsystemname = idarray[0] + '+' + idarray[1]
|
|
sp = idarray[2]
|
|
|
|
if (storage_system == storsystemname and
|
|
owningsp == sp):
|
|
foundSystem = system
|
|
LOG.debug("Found Storage Processor System: %s"
|
|
% (system))
|
|
break
|
|
|
|
return foundSystem
|
|
|
|
# Find EMC_iSCSIProtocolEndpoint for the specified sp
|
|
def _find_iscsi_protocol_endpoints(self, owningsp, storage_system):
|
|
foundEndpoints = []
|
|
|
|
processor = self._find_storage_processor_system(
|
|
owningsp,
|
|
storage_system)
|
|
|
|
associators = self.conn.Associators(
|
|
processor,
|
|
resultClass='EMC_iSCSIProtocolEndpoint')
|
|
for assoc in associators:
|
|
# Name = iqn.1992-04.com.emc:cx.apm00123907237.a8,t,0x0001
|
|
# SystemName = CLARiiON+APM00123907237+SP_A+8
|
|
arr = assoc['SystemName'].split('+')
|
|
if len(arr) > 2:
|
|
processor_name = arr[0] + '+' + arr[1] + '+' + arr[2]
|
|
if processor_name == processor['Name']:
|
|
arr2 = assoc['Name'].split(',')
|
|
if len(arr2) > 1:
|
|
foundEndpoints.append(arr2[0])
|
|
|
|
LOG.debug("iSCSIProtocolEndpoint for storage system "
|
|
"%(storage_system)s and SP %(sp)s is "
|
|
"%(endpoint)s."
|
|
% {'storage_system': storage_system,
|
|
'sp': owningsp,
|
|
'endpoint': foundEndpoints})
|
|
return foundEndpoints
|
|
|
|
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=EMC_ROOT,
|
|
keybindings=bindings)
|
|
except NameError:
|
|
instancename = None
|
|
|
|
return instancename
|
|
|
|
# Find target WWNs
|
|
def get_target_wwns(self, storage_system, connector):
|
|
target_wwns = []
|
|
|
|
configservice = self._find_storage_hardwareid_service(
|
|
storage_system)
|
|
if configservice is None:
|
|
exception_msg = (_("Error finding Storage Hardware ID Service."))
|
|
LOG.error(exception_msg)
|
|
raise exception.VolumeBackendAPIException(data=exception_msg)
|
|
|
|
hardwareids = self._find_storage_hardwareids(connector)
|
|
|
|
LOG.debug('EMCGetTargetEndpoints: Service: %(service)s '
|
|
'Storage HardwareIDs: %(hardwareids)s.'
|
|
% {'service': configservice,
|
|
'hardwareids': hardwareids})
|
|
|
|
for hardwareid in hardwareids:
|
|
rc, targetendpoints = self.conn.InvokeMethod(
|
|
'EMCGetTargetEndpoints',
|
|
configservice,
|
|
HardwareId=hardwareid)
|
|
|
|
if rc != 0L:
|
|
msg = (_('Error finding Target WWNs.'))
|
|
LOG.error(msg)
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
endpoints = targetendpoints['TargetEndpoints']
|
|
for targetendpoint in endpoints:
|
|
wwn = targetendpoint['Name']
|
|
# Add target wwn to the list if it is not already there
|
|
if not any(d == wwn for d in target_wwns):
|
|
target_wwns.append(wwn)
|
|
LOG.debug('Add target WWN: %s.' % wwn)
|
|
|
|
LOG.debug('Target WWNs: %s.' % target_wwns)
|
|
|
|
return target_wwns
|
|
|
|
# Find Storage Hardware IDs
|
|
def _find_storage_hardwareids(self, connector):
|
|
foundInstances = []
|
|
wwpns = self._find_initiator_names(connector)
|
|
hardwareids = self.conn.EnumerateInstances(
|
|
'SE_StorageHardwareID')
|
|
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
|