Refactor Huawei Volume driver
This patch attempts to refactor Huawei volume driver in liberty. We add a base driver to implement the basic functions. The sub-class will inherit from the base driver according to different storages. The following changes were made in this refactor: 1. Abstract a base class named HuaweiBaseDriver to make Huawei driver more universal. You can find it in the huawei_driver.py. 2. Put all static variables into the constants.py. 3. Rename rest_common.py to rest_client.py. rest_client.py stores the relevant methods implemented for Huawei driver. 4. Migrate some public methods from rest_client.py to huawei_utils.py, such as parse_xml_file(), _get_volume_type() and so on. 5. This refactor only involves structural adjustment and does not involve functional changes. Change-Id: I768889e2577a4d975397218eb31e89b42e08e04f Implements: blueprint refactor-huawei-volume-driver
This commit is contained in:
parent
a5c988f93e
commit
c9bb99b52f
File diff suppressed because it is too large
Load Diff
1363
cinder/tests/unit/test_huawei_drivers.py
Normal file
1363
cinder/tests/unit/test_huawei_drivers.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,9 @@ from cinder import test
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
HUAWEI_ISCSI_MODULE = ("cinder.volume.drivers.huawei.huawei_18000."
|
||||
HUAWEI_ISCSI_MODULE = ("cinder.volume.drivers.huawei.huawei_driver."
|
||||
"Huawei18000ISCSIDriver")
|
||||
HUAWEI_FC_MODULE = ("cinder.volume.drivers.huawei.huawei_18000."
|
||||
HUAWEI_FC_MODULE = ("cinder.volume.drivers.huawei.huawei_driver."
|
||||
"Huawei18000FCDriver")
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ class VolumeDriverCompatibility(test.TestCase):
|
||||
|
||||
def test_huawei_driver_iscsi_old(self):
|
||||
self._load_driver(
|
||||
'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSISCSIDriver')
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver')
|
||||
self.assertEqual(self._driver_module_name(), HUAWEI_ISCSI_MODULE)
|
||||
|
||||
def test_huawei_driver_iscsi_new(self):
|
||||
@ -56,7 +56,7 @@ class VolumeDriverCompatibility(test.TestCase):
|
||||
|
||||
def test_huawei_driver_fc_old(self):
|
||||
self._load_driver(
|
||||
'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSFCDriver')
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver')
|
||||
self.assertEqual(self._driver_module_name(), HUAWEI_FC_MODULE)
|
||||
|
||||
def test_huawei_driver_fc_new(self):
|
||||
|
@ -1,108 +0,0 @@
|
||||
# Copyright (c) 2013 - 2014 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
"""
|
||||
Provide a unified driver class for users.
|
||||
|
||||
The product type and the protocol should be specified in config file before.
|
||||
"""
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI, _LW
|
||||
from cinder.volume.drivers.huawei import huawei_18000
|
||||
from cinder.volume.drivers.huawei import huawei_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
huawei_opt = [
|
||||
cfg.StrOpt('cinder_huawei_conf_file',
|
||||
default='/etc/cinder/cinder_huawei_conf.xml',
|
||||
help='The configuration file for the Cinder Huawei '
|
||||
'driver')]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(huawei_opt)
|
||||
MAPPING = {'HVS': '18000'}
|
||||
|
||||
|
||||
class HuaweiVolumeDriver(object):
|
||||
"""Define an unified driver for Huawei drivers."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HuaweiVolumeDriver, self).__init__()
|
||||
self._product = {'18000': huawei_18000, 'HVS': huawei_18000}
|
||||
self._protocol = {'iSCSI': 'ISCSIDriver', 'FC': 'FCDriver'}
|
||||
|
||||
self.driver = self._instantiate_driver(*args, **kwargs)
|
||||
|
||||
def _instantiate_driver(self, *args, **kwargs):
|
||||
"""Instantiate the specified driver."""
|
||||
self.configuration = kwargs.get('configuration', None)
|
||||
if not self.configuration:
|
||||
msg = (_('_instantiate_driver: configuration not found.'))
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
self.configuration.append_config_values(huawei_opt)
|
||||
conf_file = self.configuration.cinder_huawei_conf_file
|
||||
(product, protocol) = self._get_conf_info(conf_file)
|
||||
|
||||
LOG.info(_LI('_instantiate_driver: Loading %(protocol)s driver for '
|
||||
'Huawei OceanStor %(product)s series storage arrays.'),
|
||||
{'protocol': protocol,
|
||||
'product': product})
|
||||
# Map HVS to 18000
|
||||
if product in MAPPING:
|
||||
LOG.warning(_LW("Product name %s is deprecated, update your "
|
||||
"configuration to the new product name."),
|
||||
product)
|
||||
product = MAPPING[product]
|
||||
|
||||
driver_module = self._product[product]
|
||||
driver_class = 'Huawei' + product + self._protocol[protocol]
|
||||
|
||||
driver_class = getattr(driver_module, driver_class)
|
||||
return driver_class(*args, **kwargs)
|
||||
|
||||
def _get_conf_info(self, filename):
|
||||
"""Get product type and connection protocol from config file."""
|
||||
root = huawei_utils.parse_xml_file(filename)
|
||||
product = root.findtext('Storage/Product').strip()
|
||||
protocol = root.findtext('Storage/Protocol').strip()
|
||||
if (product in self._product.keys() and
|
||||
protocol in self._protocol.keys()):
|
||||
return (product, protocol)
|
||||
else:
|
||||
msg = (_('"Product" or "Protocol" is illegal. "Product" should be '
|
||||
'set to 18000. "Protocol" should be set to either iSCSI '
|
||||
'or FC. Product: %(product)s Protocol: %(protocol)s')
|
||||
% {'product': six.text_type(product),
|
||||
'protocol': six.text_type(protocol)})
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""Set the attribute."""
|
||||
if getattr(self, 'driver', None):
|
||||
self.driver.__setattr__(name, value)
|
||||
return
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
""""Get the attribute."""
|
||||
drver = object.__getattribute__(self, 'driver')
|
||||
return getattr(drver, name)
|
41
cinder/volume/drivers/huawei/constants.py
Normal file
41
cinder/volume/drivers/huawei/constants.py
Normal file
@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
STATUS_HEALTH = '1'
|
||||
STATUS_RUNNING = '10'
|
||||
|
||||
HOSTGROUP_PREFIX = 'OpenStack_HostGroup_'
|
||||
LUNGROUP_PREFIX = 'OpenStack_LunGroup_'
|
||||
MAPPING_VIEW_PREFIX = 'OpenStack_Mapping_View_'
|
||||
QOS_NAME_PREFIX = 'OpenStack_'
|
||||
|
||||
DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30
|
||||
DEFAULT_WAIT_INTERVAL = 5
|
||||
ERROR_CONNECT_TO_SERVER = -403
|
||||
ERROR_UNAUTHORIZED_TO_SERVER = -401
|
||||
|
||||
MAX_HOSTNAME_LENTH = 31
|
||||
|
||||
OS_TYPE = {'Linux': '0',
|
||||
'Windows': '1',
|
||||
'Solaris': '2',
|
||||
'HP-UX': '3',
|
||||
'AIX': '4',
|
||||
'XenServer': '5',
|
||||
'Mac OS X': '6',
|
||||
'VMware ESX': '7'}
|
||||
|
||||
HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth',
|
||||
'maxBandWidth', 'latency', 'IOType']
|
@ -1,199 +0,0 @@
|
||||
# Copyright (c) 2013 - 2014 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
"""
|
||||
Volume Drivers for Huawei OceanStor 18000 storage arrays.
|
||||
"""
|
||||
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.huawei import rest_common
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
|
||||
class Huawei18000ISCSIDriver(driver.ISCSIDriver):
|
||||
"""ISCSI driver for Huawei OceanStor 18000 storage arrays.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Provide Huawei OceanStor 18000 storage volume driver.
|
||||
"""
|
||||
|
||||
VERSION = "1.1.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Huawei18000ISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Instantiate common class and log in storage system."""
|
||||
self.common = rest_common.RestCommon(configuration=self.configuration)
|
||||
return self.common.login()
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check configuration file."""
|
||||
return self.common._check_conf_file()
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create a volume."""
|
||||
lun_info = self.common.create_volume(volume)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Create a volume from a snapshot."""
|
||||
lun_info = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create a clone of the specified volume."""
|
||||
lun_info = self.common.create_cloned_volume(volume, src_vref)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend a volume."""
|
||||
return self.common.extend_volume(volume, new_size)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete a volume."""
|
||||
return self.common.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Create a snapshot."""
|
||||
lun_info = self.common.create_snapshot(snapshot)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Delete a snapshot."""
|
||||
return self.common.delete_snapshot(snapshot)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats."""
|
||||
data = self.common.update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['storage_protocol'] = 'iSCSI'
|
||||
data['driver_version'] = self.VERSION
|
||||
return data
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Map a volume to a host."""
|
||||
return self.common.initialize_connection_iscsi(volume, connector)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Terminate the map."""
|
||||
self.common.terminate_connection_iscsi(volume, connector)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
"""Export the volume."""
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Synchronously recreate an export for a volume."""
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Remove an export for a volume."""
|
||||
pass
|
||||
|
||||
|
||||
class Huawei18000FCDriver(driver.FibreChannelDriver):
|
||||
"""FC driver for Huawei OceanStor 18000 storage arrays.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Provide Huawei OceanStor 18000 storage volume driver.
|
||||
"""
|
||||
|
||||
VERSION = "1.1.0"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Huawei18000FCDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Instantiate common class and log in storage system."""
|
||||
self.common = rest_common.RestCommon(configuration=self.configuration)
|
||||
return self.common.login()
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check configuration file."""
|
||||
return self.common._check_conf_file()
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create a volume."""
|
||||
lun_info = self.common.create_volume(volume)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Create a volume from a snapshot."""
|
||||
lun_info = self.common.create_volume_from_snapshot(volume, snapshot)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create a clone of the specified volume."""
|
||||
lun_info = self.common.create_cloned_volume(volume, src_vref)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend a volume."""
|
||||
return self.common.extend_volume(volume, new_size)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete a volume."""
|
||||
return self.common.delete_volume(volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Create a snapshot."""
|
||||
lun_info = self.common.create_snapshot(snapshot)
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Delete a snapshot."""
|
||||
return self.common.delete_snapshot(snapshot)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats."""
|
||||
data = self.common.update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['storage_protocol'] = 'FC'
|
||||
data['driver_version'] = self.VERSION
|
||||
return data
|
||||
|
||||
@fczm_utils.AddFCZone
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Map a volume to a host."""
|
||||
return self.common.initialize_connection_fc(volume, connector)
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Terminate the map."""
|
||||
return self.common.terminate_connection_fc(volume, connector)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
"""Export the volume."""
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Synchronously recreate an export for a volume."""
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Remove an export for a volume."""
|
||||
pass
|
633
cinder/volume/drivers/huawei/huawei_driver.py
Normal file
633
cinder/volume/drivers/huawei/huawei_driver.py
Normal file
@ -0,0 +1,633 @@
|
||||
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LI, _LW
|
||||
from cinder import utils
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.huawei import constants
|
||||
from cinder.volume.drivers.huawei import huawei_utils
|
||||
from cinder.volume.drivers.huawei import rest_client
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
huawei_opt = [
|
||||
cfg.StrOpt('cinder_huawei_conf_file',
|
||||
default='/etc/cinder/cinder_huawei_conf.xml',
|
||||
help='The configuration file for the Cinder Huawei '
|
||||
'driver.')]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(huawei_opt)
|
||||
|
||||
|
||||
class HuaweiBaseDriver(driver.VolumeDriver):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HuaweiBaseDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration = kwargs.get('configuration', None)
|
||||
if not self.configuration:
|
||||
msg = _('_instantiate_driver: configuration not found.')
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
self.configuration.append_config_values(huawei_opt)
|
||||
self.xml_file_path = self.configuration.cinder_huawei_conf_file
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Instantiate common class and login storage system."""
|
||||
self.restclient = rest_client.RestClient(self.configuration)
|
||||
return self.restclient.login()
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check configuration file."""
|
||||
return huawei_utils.check_conf_file(self.xml_file_path)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume status."""
|
||||
return self.restclient.update_volume_stats()
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def create_volume(self, volume):
|
||||
"""Create a volume."""
|
||||
pool_info = self.restclient.find_pool_info()
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
volume_description = volume['name']
|
||||
volume_size = huawei_utils.get_volume_size(volume)
|
||||
|
||||
LOG.info(_LI(
|
||||
'Create volume: %(volume)s, size: %(size)s.'),
|
||||
{'volume': volume_name,
|
||||
'size': volume_size})
|
||||
|
||||
params = huawei_utils.get_lun_conf_params(self.xml_file_path)
|
||||
params['pool_id'] = pool_info['ID']
|
||||
params['volume_size'] = volume_size
|
||||
params['volume_description'] = volume_description
|
||||
|
||||
# Prepare LUN parameters.
|
||||
lun_param = huawei_utils.init_lun_parameters(volume_name, params)
|
||||
|
||||
# Create LUN on the array.
|
||||
lun_info = self.restclient.create_volume(lun_param)
|
||||
lun_id = lun_info['ID']
|
||||
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'ID': lun_id,
|
||||
'lun_info': lun_info}
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def delete_volume(self, volume):
|
||||
"""Delete a volume.
|
||||
|
||||
Three steps:
|
||||
Firstly, remove associate from lungroup.
|
||||
Secondly, remove associate from QoS policy.
|
||||
Thirdly, remove the lun.
|
||||
"""
|
||||
name = huawei_utils.encode_name(volume['id'])
|
||||
lun_id = volume.get('provider_location', None)
|
||||
LOG.info(_LI('Delete volume: %(name)s, array lun id: %(lun_id)s.'),
|
||||
{'name': name, 'lun_id': lun_id},)
|
||||
if lun_id:
|
||||
if self.restclient.check_lun_exist(lun_id):
|
||||
self.restclient.delete_lun(lun_id)
|
||||
else:
|
||||
LOG.warning(_LW("Can't find %s on the array."), lun_id)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Create a volume from a snapshot.
|
||||
|
||||
We use LUNcopy to copy a new volume from snapshot.
|
||||
The time needed increases as volume size does.
|
||||
"""
|
||||
snapshotname = huawei_utils.encode_name(snapshot['id'])
|
||||
|
||||
snapshot_id = snapshot.get('provider_location', None)
|
||||
if snapshot_id is None:
|
||||
snapshot_id = self.restclient.get_snapshotid_by_name(snapshotname)
|
||||
if snapshot_id is None:
|
||||
err_msg = (_(
|
||||
'create_volume_from_snapshot: Snapshot %(name)s '
|
||||
'does not exist.')
|
||||
% {'name': snapshotname})
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
|
||||
lun_info = self.create_volume(volume)
|
||||
|
||||
tgt_lun_id = lun_info['ID']
|
||||
luncopy_name = huawei_utils.encode_name(volume['id'])
|
||||
|
||||
LOG.info(_LI(
|
||||
'create_volume_from_snapshot: src_lun_id: %(src_lun_id)s, '
|
||||
'tgt_lun_id: %(tgt_lun_id)s, copy_name: %(copy_name)s.'),
|
||||
{'src_lun_id': snapshot_id,
|
||||
'tgt_lun_id': tgt_lun_id,
|
||||
'copy_name': luncopy_name})
|
||||
|
||||
event_type = 'LUNReadyWaitInterval'
|
||||
|
||||
wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
|
||||
event_type)
|
||||
|
||||
def _volume_ready():
|
||||
result = self.restclient.get_lun_info(tgt_lun_id)
|
||||
|
||||
if result['HEALTHSTATUS'] == "1":
|
||||
if result['RUNNINGSTATUS'] == "27":
|
||||
return True
|
||||
return False
|
||||
|
||||
huawei_utils.wait_for_condition(self.xml_file_path,
|
||||
_volume_ready,
|
||||
wait_interval,
|
||||
wait_interval * 10)
|
||||
|
||||
self._copy_volume(volume, luncopy_name,
|
||||
snapshot_id, tgt_lun_id)
|
||||
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Clone a new volume from an existing volume."""
|
||||
# Form the snapshot structure.
|
||||
snapshot = {'id': uuid.uuid4().__str__(), 'volume_id': src_vref['id']}
|
||||
|
||||
# Create snapshot.
|
||||
self.create_snapshot(snapshot)
|
||||
|
||||
try:
|
||||
# Create volume from snapshot.
|
||||
lun_info = self.create_volume_from_snapshot(volume, snapshot)
|
||||
finally:
|
||||
try:
|
||||
# Delete snapshot.
|
||||
self.delete_snapshot(snapshot)
|
||||
except exception.VolumeBackendAPIException:
|
||||
LOG.warning(_LW(
|
||||
'Failure deleting the snapshot %(snapshot_id)s '
|
||||
'of volume %(volume_id)s.'),
|
||||
{'snapshot_id': snapshot['id'],
|
||||
'volume_id': src_vref['id']},)
|
||||
|
||||
return {'provider_location': lun_info['ID'],
|
||||
'lun_info': lun_info}
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend a volume."""
|
||||
volume_size = huawei_utils.get_volume_size(volume)
|
||||
new_volume_size = int(new_size) * units.Gi / 512
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
|
||||
LOG.info(_LI(
|
||||
'Extend volume: %(volumename)s, oldsize:'
|
||||
' %(oldsize)s newsize: %(newsize)s.'),
|
||||
{'volumename': volume_name,
|
||||
'oldsize': volume_size,
|
||||
'newsize': new_volume_size},)
|
||||
|
||||
lun_id = self.restclient.get_volume_by_name(volume_name)
|
||||
|
||||
if lun_id is None:
|
||||
msg = (_(
|
||||
"Can't find lun info on the array, lun name is: %(name)s.")
|
||||
% {'name': volume_name})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
luninfo = self.restclient.extend_volume(lun_id, new_volume_size)
|
||||
|
||||
return {'provider_location': luninfo['ID'],
|
||||
'lun_info': luninfo}
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def create_snapshot(self, snapshot):
|
||||
snapshot_info = self.restclient.create_snapshot(snapshot)
|
||||
snapshot_id = snapshot_info['ID']
|
||||
self.restclient.active_snapshot(snapshot_id)
|
||||
|
||||
return {'provider_location': snapshot_info['ID'],
|
||||
'lun_info': snapshot_info}
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def delete_snapshot(self, snapshot):
|
||||
snapshotname = huawei_utils.encode_name(snapshot['id'])
|
||||
volume_name = huawei_utils.encode_name(snapshot['volume_id'])
|
||||
|
||||
LOG.info(_LI(
|
||||
'stop_snapshot: snapshot name: %(snapshot)s, '
|
||||
'volume name: %(volume)s.'),
|
||||
{'snapshot': snapshotname,
|
||||
'volume': volume_name},)
|
||||
|
||||
snapshot_id = snapshot.get('provider_location', None)
|
||||
if snapshot_id is None:
|
||||
snapshot_id = self.restclient.get_snapshotid_by_name(snapshotname)
|
||||
|
||||
if snapshot_id is not None:
|
||||
if self.restclient.check_snapshot_exist(snapshot_id):
|
||||
self.restclient.stop_snapshot(snapshot_id)
|
||||
self.restclient.delete_snapshot(snapshot_id)
|
||||
else:
|
||||
LOG.warning(_LW("Can't find snapshot on the array."))
|
||||
else:
|
||||
LOG.warning(_LW("Can't find snapshot on the array."))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def initialize_connection_fc(self, volume, connector):
|
||||
wwns = connector['wwpns']
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
|
||||
LOG.info(_LI(
|
||||
'initialize_connection_fc, initiator: %(wwpns)s,'
|
||||
' volume name: %(volume)s.'),
|
||||
{'wwpns': wwns,
|
||||
'volume': volume_name},)
|
||||
|
||||
host_name_before_hash = None
|
||||
host_name = connector['host']
|
||||
if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENTH):
|
||||
host_name_before_hash = host_name
|
||||
host_name = str(hash(host_name))
|
||||
|
||||
# Create hostgroup if not exist.
|
||||
host_id = self.restclient.add_host_with_check(host_name,
|
||||
host_name_before_hash)
|
||||
|
||||
# Add host into hostgroup.
|
||||
hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
|
||||
|
||||
free_wwns = self.restclient.get_connected_free_wwns()
|
||||
LOG.info(_LI("initialize_connection_fc, the array has free wwns: %s."),
|
||||
free_wwns)
|
||||
for wwn in wwns:
|
||||
if wwn in free_wwns:
|
||||
self.restclient.add_fc_port_to_host(host_id, wwn)
|
||||
|
||||
lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
|
||||
hostgroup_id,
|
||||
host_id)
|
||||
host_lun_id = self.restclient.find_host_lun_id(host_id, lun_id)
|
||||
|
||||
tgt_port_wwns = []
|
||||
for wwn in wwns:
|
||||
tgtwwpns = self.restclient.get_fc_target_wwpns(wwn)
|
||||
if tgtwwpns:
|
||||
tgt_port_wwns.append(tgtwwpns)
|
||||
|
||||
init_targ_map = {}
|
||||
for initiator in wwns:
|
||||
init_targ_map[initiator] = tgt_port_wwns
|
||||
|
||||
# Return FC properties.
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_lun': int(host_lun_id),
|
||||
'target_discovered': True,
|
||||
'target_wwn': tgt_port_wwns,
|
||||
'volume_id': volume['id'],
|
||||
'initiator_target_map': init_targ_map}, }
|
||||
|
||||
LOG.info(_LI("initialize_connection_fc, return data is: %s."),
|
||||
info)
|
||||
|
||||
return info
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def initialize_connection_iscsi(self, volume, connector):
|
||||
"""Map a volume to a host and return target iSCSI information."""
|
||||
LOG.info(_LI('Enter initialize_connection_iscsi.'))
|
||||
initiator_name = connector['initiator']
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
|
||||
LOG.info(_LI(
|
||||
'initiator name: %(initiator_name)s, '
|
||||
'volume name: %(volume)s.'),
|
||||
{'initiator_name': initiator_name,
|
||||
'volume': volume_name})
|
||||
|
||||
(iscsi_iqn,
|
||||
target_ip,
|
||||
portgroup_id) = self.restclient.get_iscsi_params(self.xml_file_path,
|
||||
connector)
|
||||
LOG.info(_LI('initialize_connection_iscsi, iscsi_iqn: %(iscsi_iqn)s, '
|
||||
'target_ip: %(target_ip)s, '
|
||||
'TargetPortGroup: %(portgroup_id)s.'),
|
||||
{'iscsi_iqn': iscsi_iqn,
|
||||
'target_ip': target_ip,
|
||||
'portgroup_id': portgroup_id},)
|
||||
|
||||
# Create hostgroup if not exist.
|
||||
host_name = connector['host']
|
||||
host_name_before_hash = None
|
||||
if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENTH):
|
||||
host_name_before_hash = host_name
|
||||
host_name = str(hash(host_name))
|
||||
host_id = self.restclient.add_host_with_check(host_name,
|
||||
host_name_before_hash)
|
||||
|
||||
# Add initiator to the host.
|
||||
self.restclient.ensure_initiator_added(initiator_name, host_id)
|
||||
hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
|
||||
|
||||
# Mapping lungroup and hostgroup to view.
|
||||
lun_id = self.restclient.mapping_hostgroup_and_lungroup(volume_name,
|
||||
hostgroup_id,
|
||||
host_id,
|
||||
portgroup_id)
|
||||
|
||||
hostlun_id = self.restclient.find_host_lun_id(host_id, lun_id)
|
||||
|
||||
LOG.info(_LI("initialize_connection_iscsi, host lun id is: %s."),
|
||||
hostlun_id)
|
||||
|
||||
# Return iSCSI properties.
|
||||
properties = {}
|
||||
properties['target_discovered'] = False
|
||||
properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
|
||||
properties['target_iqn'] = iscsi_iqn
|
||||
properties['target_lun'] = int(hostlun_id)
|
||||
properties['volume_id'] = volume['id']
|
||||
|
||||
LOG.info(_LI("initialize_connection_iscsi success. Return data: %s."),
|
||||
properties)
|
||||
return {'driver_volume_type': 'iscsi', 'data': properties}
|
||||
|
||||
@utils.synchronized('huawei', external=True)
|
||||
def terminate_connection_iscsi(self, volume, connector):
|
||||
"""Delete map between a volume and a host."""
|
||||
initiator_name = connector['initiator']
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
lun_id = volume.get('provider_location', None)
|
||||
host_name = connector['host']
|
||||
|
||||
LOG.info(_LI(
|
||||
'terminate_connection_iscsi: volume name: %(volume)s, '
|
||||
'initiator name: %(ini)s, '
|
||||
'lun_id: %(lunid)s.'),
|
||||
{'volume': volume_name,
|
||||
'ini': initiator_name,
|
||||
'lunid': lun_id},)
|
||||
|
||||
iscsi_conf = huawei_utils.get_iscsi_conf(self.xml_file_path)
|
||||
portgroup = None
|
||||
portgroup_id = None
|
||||
left_lunnum = -1
|
||||
for ini in iscsi_conf['Initiator']:
|
||||
if ini['Name'] == initiator_name:
|
||||
for key in ini:
|
||||
if key == 'TargetPortGroup':
|
||||
portgroup = ini['TargetPortGroup']
|
||||
break
|
||||
# Remove lun from lungroup.
|
||||
if lun_id:
|
||||
if self.restclient.check_lun_exist(lun_id):
|
||||
# Get lungroup id by lun id.
|
||||
lungroup_id = self.restclient.get_lungroupid_by_lunid(lun_id)
|
||||
if lungroup_id:
|
||||
self.restclient.remove_lun_from_lungroup(lungroup_id,
|
||||
lun_id)
|
||||
else:
|
||||
LOG.warning(_LW("Can't find lun on the array."))
|
||||
# Remove portgroup from mapping view if no lun left in lungroup.
|
||||
if portgroup:
|
||||
portgroup_id = self.restclient.find_tgt_port_group(portgroup)
|
||||
host_id = self.restclient.find_host(host_name)
|
||||
if host_id:
|
||||
mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
|
||||
view_id = self.restclient.find_mapping_view(mapping_view_name)
|
||||
if view_id:
|
||||
lungroup_id = self.restclient.find_lungroup_from_map(view_id)
|
||||
if lungroup_id:
|
||||
left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
|
||||
|
||||
if portgroup_id and view_id and (int(left_lunnum) <= 0):
|
||||
if self.restclient.is_portgroup_associated_to_view(view_id,
|
||||
portgroup_id):
|
||||
self.restclient.delete_portgroup_mapping_view(view_id,
|
||||
portgroup_id)
|
||||
if view_id and (int(left_lunnum) <= 0):
|
||||
if self.restclient.lungroup_associated(view_id, lungroup_id):
|
||||
self.restclient.delete_lungroup_mapping_view(view_id,
|
||||
lungroup_id)
|
||||
self.restclient.delete_lungroup(lungroup_id)
|
||||
if self.restclient.is_initiator_associated_to_host(initiator_name):
|
||||
self.restclient.remove_iscsi_from_host(initiator_name)
|
||||
hostgroup_name = constants.HOSTGROUP_PREFIX + host_id
|
||||
hostgroup_id = self.restclient.find_hostgroup(hostgroup_name)
|
||||
if hostgroup_id:
|
||||
if self.restclient.hostgroup_associated(view_id, hostgroup_id):
|
||||
self.restclient.delete_hostgoup_mapping_view(view_id,
|
||||
hostgroup_id)
|
||||
self.restclient.remove_host_from_hostgroup(hostgroup_id,
|
||||
host_id)
|
||||
self.restclient.delete_hostgroup(hostgroup_id)
|
||||
self.restclient.remove_host(host_id)
|
||||
self.restclient.delete_mapping_view(view_id)
|
||||
|
||||
def terminate_connection_fc(self, volume, connector):
|
||||
"""Delete map between a volume and a host."""
|
||||
wwns = connector['wwpns']
|
||||
volume_name = huawei_utils.encode_name(volume['id'])
|
||||
lun_id = volume.get('provider_location', None)
|
||||
host_name = connector['host']
|
||||
left_lunnum = -1
|
||||
|
||||
LOG.info(_LI('terminate_connection_fc: volume name: %(volume)s, '
|
||||
'wwpns: %(wwns)s, '
|
||||
'lun_id: %(lunid)s.'),
|
||||
{'volume': volume_name,
|
||||
'wwns': wwns,
|
||||
'lunid': lun_id},)
|
||||
if lun_id:
|
||||
if self.restclient.check_lun_exist(lun_id):
|
||||
# Get lungroup id by lun id.
|
||||
lungroup_id = self.restclient.get_lungroupid_by_lunid(lun_id)
|
||||
if not lungroup_id:
|
||||
LOG.info(_LI("Can't find lun in lungroup."))
|
||||
else:
|
||||
self.restclient.remove_lun_from_lungroup(lungroup_id,
|
||||
lun_id)
|
||||
else:
|
||||
LOG.warning(_LW("Can't find lun on the array."))
|
||||
tgt_port_wwns = []
|
||||
for wwn in wwns:
|
||||
tgtwwpns = self.restclient.get_fc_target_wwpns(wwn)
|
||||
if tgtwwpns:
|
||||
tgt_port_wwns.append(tgtwwpns)
|
||||
|
||||
init_targ_map = {}
|
||||
for initiator in wwns:
|
||||
init_targ_map[initiator] = tgt_port_wwns
|
||||
host_id = self.restclient.find_host(host_name)
|
||||
if host_id:
|
||||
mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
|
||||
view_id = self.restclient.find_mapping_view(mapping_view_name)
|
||||
if view_id:
|
||||
lungroup_id = self.restclient.find_lungroup_from_map(view_id)
|
||||
if lungroup_id:
|
||||
left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
|
||||
if int(left_lunnum) > 0:
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {}}
|
||||
else:
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'target_wwn': tgt_port_wwns,
|
||||
'initiator_target_map': init_targ_map}, }
|
||||
|
||||
return info
|
||||
|
||||
def migrate_volume(self, context, volume, host):
|
||||
return (False, None)
|
||||
|
||||
def create_export(self, context, volume):
|
||||
"""Export a volume."""
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Synchronously recreate an export for a volume."""
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Remove an export for a volume."""
|
||||
pass
|
||||
|
||||
def _copy_volume(self, volume, copy_name, src_lun, tgt_lun):
|
||||
luncopy_id = self.restclient.create_luncopy(copy_name,
|
||||
src_lun, tgt_lun)
|
||||
event_type = 'LUNcopyWaitInterval'
|
||||
wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
|
||||
event_type)
|
||||
|
||||
try:
|
||||
self.restclient.start_luncopy(luncopy_id)
|
||||
|
||||
def _luncopy_complete():
|
||||
luncopy_info = self.restclient.get_luncopy_info(luncopy_id)
|
||||
if luncopy_info['status'] == '40':
|
||||
# luncopy_info['status'] means for the running status of
|
||||
# the luncopy. If luncopy_info['status'] is equal to '40',
|
||||
# this luncopy is completely ready.
|
||||
return True
|
||||
elif luncopy_info['state'] != '1':
|
||||
# luncopy_info['state'] means for the healthy status of the
|
||||
# luncopy. If luncopy_info['state'] is not equal to '1',
|
||||
# this means that an error occurred during the LUNcopy
|
||||
# operation and we should abort it.
|
||||
err_msg = (_(
|
||||
'An error occurred during the LUNcopy operation. '
|
||||
'LUNcopy name: %(luncopyname)s. '
|
||||
'LUNcopy status: %(luncopystatus)s. '
|
||||
'LUNcopy state: %(luncopystate)s.')
|
||||
% {'luncopyname': luncopy_id,
|
||||
'luncopystatus': luncopy_info['status'],
|
||||
'luncopystate': luncopy_info['state']},)
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
huawei_utils.wait_for_condition(self.xml_file_path,
|
||||
_luncopy_complete,
|
||||
wait_interval)
|
||||
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.restclient.delete_luncopy(luncopy_id)
|
||||
self.delete_volume(volume)
|
||||
|
||||
self.restclient.delete_luncopy(luncopy_id)
|
||||
|
||||
|
||||
class Huawei18000ISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
|
||||
"""ISCSI driver for Huawei OceanStor 18000 storage arrays.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Provide Huawei OceanStor 18000 storage volume driver.
|
||||
"""
|
||||
|
||||
VERSION = "1.1.1"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Huawei18000ISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume status."""
|
||||
data = HuaweiBaseDriver.get_volume_stats(self, refresh=False)
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['storage_protocol'] = 'iSCSI'
|
||||
data['driver_version'] = self.VERSION
|
||||
data['vendor_name'] = 'Huawei'
|
||||
return data
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
return HuaweiBaseDriver.initialize_connection_iscsi(self,
|
||||
volume,
|
||||
connector)
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
return HuaweiBaseDriver.terminate_connection_iscsi(self,
|
||||
volume,
|
||||
connector)
|
||||
|
||||
|
||||
class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
|
||||
"""FC driver for Huawei OceanStor 18000 storage arrays.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver
|
||||
1.1.0 - Provide Huawei OceanStor 18000 storage volume driver.
|
||||
"""
|
||||
|
||||
VERSION = "1.1.1"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Huawei18000FCDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume status."""
|
||||
data = HuaweiBaseDriver.get_volume_stats(self, refresh=False)
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or self.__class__.__name__
|
||||
data['storage_protocol'] = 'FC'
|
||||
data['driver_version'] = self.VERSION
|
||||
data['verdor_name'] = 'Huawei'
|
||||
return data
|
||||
|
||||
@fczm_utils.AddFCZone
|
||||
def initialize_connection(self, volume, connector):
|
||||
return HuaweiBaseDriver.initialize_connection_fc(self,
|
||||
volume,
|
||||
connector)
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
return HuaweiBaseDriver.terminate_connection_fc(self,
|
||||
volume,
|
||||
connector)
|
@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2013 Huawei Technologies Co., Ltd.
|
||||
# Copyright (c) 2012 OpenStack LLC.
|
||||
# Copyright (c) 2015 Huawei Technologies Co., Ltd.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -14,32 +13,133 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import six
|
||||
import time
|
||||
import uuid
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder.i18n import _LE
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import utils
|
||||
from cinder.i18n import _, _LE, _LI
|
||||
from cinder.volume.drivers.huawei import constants
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
os_type = {'Linux': '0',
|
||||
'Windows': '1',
|
||||
'Solaris': '2',
|
||||
'HP-UX': '3',
|
||||
'AIX': '4',
|
||||
'XenServer': '5',
|
||||
'Mac OS X': '6',
|
||||
'VMware ESX': '7'}
|
||||
|
||||
opts_capability = {
|
||||
'smarttier': False,
|
||||
'smartcache': False,
|
||||
'smartpartition': False,
|
||||
'thin_provisioning': False,
|
||||
'thick_provisioning': False,
|
||||
}
|
||||
|
||||
|
||||
def parse_xml_file(filepath):
|
||||
opts_value = {
|
||||
'policy': None,
|
||||
'partitionname': None,
|
||||
'cachename': None,
|
||||
}
|
||||
|
||||
|
||||
opts_associate = {
|
||||
'smarttier': 'policy',
|
||||
'smartcache': 'cachename',
|
||||
'smartpartition': 'partitionname',
|
||||
}
|
||||
|
||||
|
||||
def get_volume_params(volume):
|
||||
opts = {}
|
||||
ctxt = context.get_admin_context()
|
||||
type_id = volume['volume_type_id']
|
||||
if type_id is not None:
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
specs = dict(volume_type).get('extra_specs')
|
||||
opts = _get_extra_spec_value(specs)
|
||||
else:
|
||||
opts.update(opts_capability)
|
||||
opts.update(opts_value)
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def _get_extra_spec_value(specs):
|
||||
"""Return the parameters for creating the volume."""
|
||||
opts = {}
|
||||
opts.update(opts_capability)
|
||||
opts.update(opts_value)
|
||||
|
||||
opts = _get_opts_from_specs(opts_capability, opts_value, specs)
|
||||
LOG.debug('get_volume_params opts %(opts)s.', {'opts': opts})
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def _get_opts_from_specs(opts_capability, opts_value, specs):
|
||||
opts = {}
|
||||
opts.update(opts_capability)
|
||||
opts.update(opts_value)
|
||||
|
||||
for key, value in specs.iteritems():
|
||||
|
||||
# Get the scope, if is using scope format.
|
||||
scope = None
|
||||
key_split = key.split(':')
|
||||
if len(key_split) > 2 and key_split[0] != "capabilities":
|
||||
continue
|
||||
|
||||
if len(key_split) == 1:
|
||||
key = key_split[0]
|
||||
else:
|
||||
scope = key_split[0]
|
||||
key = key_split[1]
|
||||
|
||||
if scope:
|
||||
scope = scope.lower()
|
||||
if key:
|
||||
key = key.lower()
|
||||
|
||||
if (scope in opts_capability) and (key in opts_value):
|
||||
if (scope in opts_associate) and (opts_associate[scope] == key):
|
||||
opts[key] = value
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
def _get_smartx_specs_params(lunsetinfo, smartx_opts):
|
||||
"""Get parameters from config file for creating lun."""
|
||||
# Default lun set information.
|
||||
if 'LUNType' in smartx_opts:
|
||||
lunsetinfo['LUNType'] = smartx_opts['LUNType']
|
||||
lunsetinfo['policy'] = smartx_opts['policy']
|
||||
|
||||
return lunsetinfo
|
||||
|
||||
|
||||
def get_lun_params(xml_file_path, smartx_opts):
|
||||
lunsetinfo = {}
|
||||
lunsetinfo = get_lun_conf_params(xml_file_path)
|
||||
lunsetinfo = _get_smartx_specs_params(lunsetinfo, smartx_opts)
|
||||
return lunsetinfo
|
||||
|
||||
|
||||
def parse_xml_file(xml_file_path):
|
||||
"""Get root of xml file."""
|
||||
try:
|
||||
tree = ET.parse(filepath)
|
||||
tree = ET.parse(xml_file_path)
|
||||
root = tree.getroot()
|
||||
return root
|
||||
except IOError as err:
|
||||
LOG.error(_LE('parse_xml_file: %s'), err)
|
||||
LOG.error(_LE('parse_xml_file: %s.'), err)
|
||||
raise
|
||||
|
||||
|
||||
@ -64,49 +164,7 @@ def get_xml_item(xml_root, item):
|
||||
return items_list
|
||||
|
||||
|
||||
def is_xml_item_exist(xml_root, item, attrib_key=None):
|
||||
"""Check if the given item exits in xml config file.
|
||||
|
||||
:param xml_root: The root of xml tree
|
||||
:param item: The xml tag to check
|
||||
:param attrib_key: The xml attrib to check
|
||||
:return: True of False
|
||||
"""
|
||||
items_list = get_xml_item(xml_root, item)
|
||||
if attrib_key:
|
||||
for tmp_dict in items_list:
|
||||
if tmp_dict['attrib'].get(attrib_key, None):
|
||||
return True
|
||||
else:
|
||||
if items_list and items_list[0]['text']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_xml_item_valid(xml_root, item, valid_list, attrib_key=None):
|
||||
"""Check if the given item is valid in xml config file.
|
||||
|
||||
:param xml_root: The root of xml tree
|
||||
:param item: The xml tag to check
|
||||
:param valid_list: The valid item value
|
||||
:param attrib_key: The xml attrib to check
|
||||
:return: True of False
|
||||
"""
|
||||
items_list = get_xml_item(xml_root, item)
|
||||
if attrib_key:
|
||||
for tmp_dict in items_list:
|
||||
value = tmp_dict['attrib'].get(attrib_key, None)
|
||||
if value not in valid_list:
|
||||
return False
|
||||
else:
|
||||
value = items_list[0]['text']
|
||||
if value not in valid_list:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_conf_host_os_type(host_ip, config):
|
||||
def get_conf_host_os_type(host_ip, conf):
|
||||
"""Get host OS type from xml config file.
|
||||
|
||||
:param host_ip: The IP of Nova host
|
||||
@ -114,7 +172,8 @@ def get_conf_host_os_type(host_ip, config):
|
||||
:return: host OS type
|
||||
"""
|
||||
os_conf = {}
|
||||
root = parse_xml_file(config)
|
||||
xml_file_path = conf.cinder_huawei_conf_file
|
||||
root = parse_xml_file(xml_file_path)
|
||||
hosts_list = get_xml_item(root, 'Host')
|
||||
for host in hosts_list:
|
||||
os = host['attrib']['OSType'].strip()
|
||||
@ -123,11 +182,302 @@ def get_conf_host_os_type(host_ip, config):
|
||||
host_os = None
|
||||
for k, v in os_conf.items():
|
||||
if host_ip in v:
|
||||
host_os = os_type.get(k, None)
|
||||
host_os = constants.OS_TYPE.get(k, None)
|
||||
if not host_os:
|
||||
host_os = os_type['Linux'] # default os type
|
||||
host_os = constants.OS_TYPE['Linux'] # Default OS type.
|
||||
|
||||
LOG.debug('_get_host_os_type: Host %(ip)s OS type is %(os)s.',
|
||||
{'ip': host_ip, 'os': host_os})
|
||||
|
||||
return host_os
|
||||
|
||||
|
||||
def get_qos_by_volume_type(volume_type):
|
||||
qos = {}
|
||||
qos_specs_id = volume_type.get('qos_specs_id')
|
||||
|
||||
# We prefer the qos_specs association
|
||||
# and override any existing extra-specs settings
|
||||
# if present.
|
||||
if qos_specs_id is not None:
|
||||
kvs = qos_specs.get_qos_specs(context.get_admin_context(),
|
||||
qos_specs_id)['specs']
|
||||
else:
|
||||
return qos
|
||||
|
||||
LOG.info(_LI('The QoS sepcs is: %s.'), kvs)
|
||||
for key, value in kvs.iteritems():
|
||||
if key in constants.HUAWEI_VALID_KEYS:
|
||||
if (key.upper() != 'IOTYPE') and (int(value) <= 0):
|
||||
err_msg = (_('Qos config is wrong. %(key)s'
|
||||
' must be set greater than 0.')
|
||||
% {'key': key})
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
elif (key.upper() == 'IOTYPE') and (value not in ['0', '1', '2']):
|
||||
raise exception.InvalidInput(
|
||||
reason=(_('Illegal value specified for IOTYPE: '
|
||||
'set to either 0, 1, or 2.')))
|
||||
else:
|
||||
qos[key.upper()] = value
|
||||
|
||||
return qos
|
||||
|
||||
|
||||
def get_volume_qos(volume):
|
||||
qos = {}
|
||||
ctxt = context.get_admin_context()
|
||||
type_id = volume['volume_type_id']
|
||||
if type_id is not None:
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
qos = get_qos_by_volume_type(volume_type)
|
||||
|
||||
return qos
|
||||
|
||||
|
||||
def _get_volume_type(type_id):
|
||||
ctxt = context.get_admin_context()
|
||||
return volume_types.get_volume_type(ctxt, type_id)
|
||||
|
||||
|
||||
def get_lun_conf_params(xml_file_path):
|
||||
"""Get parameters from config file for creating lun."""
|
||||
lunsetinfo = {
|
||||
'LUNType': 'Thick',
|
||||
'StripUnitSize': '64',
|
||||
'WriteType': '1',
|
||||
'MirrorSwitch': '1',
|
||||
'PrefetchType': '3',
|
||||
'PrefetchValue': '0',
|
||||
'PrefetchTimes': '0',
|
||||
'policy': '0',
|
||||
'readcachepolicy': '2',
|
||||
'writecachepolicy': '5',
|
||||
}
|
||||
# Default lun set information.
|
||||
root = parse_xml_file(xml_file_path)
|
||||
luntype = root.findtext('LUN/LUNType')
|
||||
if luntype:
|
||||
if luntype.strip() in ['Thick', 'Thin']:
|
||||
lunsetinfo['LUNType'] = luntype.strip()
|
||||
if luntype.strip() == 'Thick':
|
||||
lunsetinfo['LUNType'] = 0
|
||||
if luntype.strip() == 'Thin':
|
||||
lunsetinfo['LUNType'] = 1
|
||||
|
||||
elif luntype is not '' and luntype is not None:
|
||||
err_msg = (_(
|
||||
'Config file is wrong. LUNType must be "Thin"'
|
||||
' or "Thick". LUNType: %(fetchtype)s.')
|
||||
% {'fetchtype': luntype})
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
|
||||
stripunitsize = root.findtext('LUN/StripUnitSize')
|
||||
if stripunitsize is not None:
|
||||
lunsetinfo['StripUnitSize'] = stripunitsize.strip()
|
||||
writetype = root.findtext('LUN/WriteType')
|
||||
if writetype is not None:
|
||||
lunsetinfo['WriteType'] = writetype.strip()
|
||||
mirrorswitch = root.findtext('LUN/MirrorSwitch')
|
||||
if mirrorswitch is not None:
|
||||
lunsetinfo['MirrorSwitch'] = mirrorswitch.strip()
|
||||
|
||||
prefetch = root.find('LUN/Prefetch')
|
||||
fetchtype = prefetch.attrib['Type']
|
||||
if prefetch is not None and prefetch.attrib['Type']:
|
||||
if fetchtype in ['0', '1', '2', '3']:
|
||||
lunsetinfo['PrefetchType'] = fetchtype.strip()
|
||||
typevalue = prefetch.attrib['Value'].strip()
|
||||
if lunsetinfo['PrefetchType'] == '1':
|
||||
double_value = int(typevalue) * 2
|
||||
typevalue_double = six.text_type(double_value)
|
||||
lunsetinfo['PrefetchValue'] = typevalue_double
|
||||
elif lunsetinfo['PrefetchType'] == '2':
|
||||
lunsetinfo['PrefetchValue'] = typevalue
|
||||
else:
|
||||
err_msg = (_(
|
||||
'PrefetchType config is wrong. PrefetchType'
|
||||
' must be in 0,1,2,3. PrefetchType is: %(fetchtype)s.')
|
||||
% {'fetchtype': fetchtype})
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
else:
|
||||
LOG.info(_LI(
|
||||
'Use default PrefetchType. '
|
||||
'PrefetchType: Intelligent.'))
|
||||
|
||||
return lunsetinfo
|
||||
|
||||
|
||||
def encode_name(name):
|
||||
uuid_str = name.replace("-", "")
|
||||
vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)
|
||||
vol_encoded = base64.urlsafe_b64encode(vol_uuid.bytes)
|
||||
vol_encoded = vol_encoded.decode("utf-8") # Make it compatible to py3.
|
||||
newuuid = vol_encoded.replace("=", "")
|
||||
return newuuid
|
||||
|
||||
|
||||
def init_lun_parameters(name, parameters):
|
||||
"""Initialize basic LUN parameters."""
|
||||
lunparam = {"TYPE": "11",
|
||||
"NAME": name,
|
||||
"PARENTTYPE": "216",
|
||||
"PARENTID": parameters['pool_id'],
|
||||
"DESCRIPTION": parameters['volume_description'],
|
||||
"ALLOCTYPE": parameters['LUNType'],
|
||||
"CAPACITY": parameters['volume_size'],
|
||||
"WRITEPOLICY": parameters['WriteType'],
|
||||
"MIRRORPOLICY": parameters['MirrorSwitch'],
|
||||
"PREFETCHPOLICY": parameters['PrefetchType'],
|
||||
"PREFETCHVALUE": parameters['PrefetchValue'],
|
||||
"DATATRANSFERPOLICY": parameters['policy'],
|
||||
"READCACHEPOLICY": parameters['readcachepolicy'],
|
||||
"WRITECACHEPOLICY": parameters['writecachepolicy'],
|
||||
}
|
||||
|
||||
return lunparam
|
||||
|
||||
|
||||
def volume_in_use(volume):
|
||||
"""Check if the given volume is in use."""
|
||||
return (volume['volume_attachment'] and
|
||||
len(volume['volume_attachment']) > 0)
|
||||
|
||||
|
||||
def get_wait_interval(xml_file_path, event_type):
|
||||
"""Get wait interval from huawei conf file."""
|
||||
root = parse_xml_file(xml_file_path)
|
||||
wait_interval = root.findtext('LUN/%s' % event_type)
|
||||
if wait_interval is None:
|
||||
wait_interval = constants.DEFAULT_WAIT_INTERVAL
|
||||
LOG.info(_LI(
|
||||
"Wait interval for %(event_type)s is not configured in huawei "
|
||||
"conf file. Use default: %(default_wait_interval)d."),
|
||||
{"event_type": event_type,
|
||||
"default_wait_interval": wait_interval})
|
||||
|
||||
return int(wait_interval)
|
||||
|
||||
|
||||
def _get_default_timeout(xml_file_path):
|
||||
"""Get timeout from huawei conf file."""
|
||||
root = parse_xml_file(xml_file_path)
|
||||
timeout = root.findtext('LUN/Timeout')
|
||||
if timeout is None:
|
||||
timeout = constants.DEFAULT_WAIT_TIMEOUT
|
||||
LOG.info(_LI(
|
||||
"Timeout is not configured in huawei conf file. "
|
||||
"Use default: %(default_timeout)d."),
|
||||
{"default_timeout": timeout})
|
||||
|
||||
return timeout
|
||||
|
||||
|
||||
def wait_for_condition(xml_file_path, func, interval, timeout=None):
|
||||
start_time = time.time()
|
||||
if timeout is None:
|
||||
timeout = _get_default_timeout(xml_file_path)
|
||||
|
||||
def _inner():
|
||||
try:
|
||||
res = func()
|
||||
except Exception as ex:
|
||||
raise exception.VolumeBackendAPIException(ex)
|
||||
if res:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if int(time.time()) - start_time > timeout:
|
||||
msg = (_('wait_for_condition: %s timed out.')
|
||||
% func.__name__)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_inner)
|
||||
timer.start(interval=interval).wait()
|
||||
|
||||
|
||||
def get_login_info(xml_file_path):
|
||||
"""Get login IP, user name and password from config file."""
|
||||
logininfo = {}
|
||||
root = parse_xml_file(xml_file_path)
|
||||
|
||||
logininfo['RestURL'] = root.findtext('Storage/RestURL').strip()
|
||||
|
||||
for key in ['UserName', 'UserPassword']:
|
||||
node = root.find('Storage/%s' % key)
|
||||
node_text = node.text
|
||||
logininfo[key] = node_text
|
||||
|
||||
return logininfo
|
||||
|
||||
|
||||
def _change_file_mode(filepath):
|
||||
utils.execute('chmod', '640', filepath, run_as_root=True)
|
||||
|
||||
|
||||
def get_iscsi_conf(xml_file_path):
|
||||
"""Get iSCSI info from config file."""
|
||||
iscsiinfo = {}
|
||||
root = parse_xml_file(xml_file_path)
|
||||
TargetIP = root.findtext('iSCSI/DefaultTargetIP').strip()
|
||||
iscsiinfo['DefaultTargetIP'] = TargetIP
|
||||
initiator_list = []
|
||||
|
||||
for dic in root.findall('iSCSI/Initiator'):
|
||||
# Strip values of dict.
|
||||
tmp_dic = {}
|
||||
for k in dic.items():
|
||||
tmp_dic[k[0]] = k[1].strip()
|
||||
|
||||
initiator_list.append(tmp_dic)
|
||||
|
||||
iscsiinfo['Initiator'] = initiator_list
|
||||
|
||||
return iscsiinfo
|
||||
|
||||
|
||||
def check_qos_high_priority(qos):
|
||||
"""Check QoS priority."""
|
||||
for key, value in qos.iteritems():
|
||||
if (key.find('MIN') == 0) or (key.find('LATENCY') == 0):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_conf_file(xml_file_path):
|
||||
"""Check the config file, make sure the essential items are set."""
|
||||
root = parse_xml_file(xml_file_path)
|
||||
resturl = root.findtext('Storage/RestURL')
|
||||
username = root.findtext('Storage/UserName')
|
||||
pwd = root.findtext('Storage/UserPassword')
|
||||
pool_node = root.findall('Storage/StoragePool')
|
||||
|
||||
if (not resturl) or (not username) or (not pwd):
|
||||
err_msg = (_(
|
||||
'check_conf_file: Config file invalid. RestURL,'
|
||||
' UserName and UserPassword must be set.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(reason=err_msg)
|
||||
|
||||
if not pool_node:
|
||||
err_msg = (_(
|
||||
'check_conf_file: Config file invalid. '
|
||||
'StoragePool must be set.'))
|
||||
LOG.error(err_msg)
|
||||
raise exception.InvalidInput(reason=err_msg)
|
||||
|
||||
|
||||
def get_volume_size(volume):
|
||||
"""Calculate the volume size.
|
||||
|
||||
We should divide the given volume size by 512 for the 18000 system
|
||||
calculates volume size with sectors, which is 512 bytes.
|
||||
"""
|
||||
volume_size = units.Gi / 512 # 1G
|
||||
if int(volume['size']) != 0:
|
||||
volume_size = int(volume['size']) * units.Gi / 512
|
||||
|
||||
return volume_size
|
||||
|
1219
cinder/volume/drivers/huawei/rest_client.py
Normal file
1219
cinder/volume/drivers/huawei/rest_client.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -109,10 +109,10 @@ CONF = cfg.CONF
|
||||
CONF.register_opts(volume_manager_opts)
|
||||
|
||||
MAPPING = {
|
||||
'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSISCSIDriver':
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver',
|
||||
'cinder.volume.drivers.huawei.huawei_hvs.HuaweiHVSFCDriver':
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver',
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000ISCSIDriver':
|
||||
'cinder.volume.drivers.huawei.huawei_driver.Huawei18000ISCSIDriver',
|
||||
'cinder.volume.drivers.huawei.huawei_18000.Huawei18000FCDriver':
|
||||
'cinder.volume.drivers.huawei.huawei_driver.Huawei18000FCDriver',
|
||||
'cinder.volume.drivers.fujitsu_eternus_dx_fc.FJDXFCDriver':
|
||||
'cinder.volume.drivers.fujitsu.eternus_dx_fc.FJDXFCDriver',
|
||||
'cinder.volume.drivers.fujitsu_eternus_dx_iscsi.FJDXISCSIDriver':
|
||||
|
2
tox.ini
2
tox.ini
@ -66,7 +66,7 @@ commands =
|
||||
cinder.tests.unit.test_hitachi_hbsd_snm2_iscsi \
|
||||
cinder.tests.unit.test_hp_xp_fc \
|
||||
cinder.tests.unit.test_hplefthand \
|
||||
cinder.tests.unit.test_huawei_18000 \
|
||||
cinder.tests.unit.test_huawei_drivers \
|
||||
cinder.tests.unit.test_huawei_drivers_compatibility \
|
||||
cinder.tests.unit.test_ibm_xiv_ds8k \
|
||||
cinder.tests.unit.test_infortrend_cli \
|
||||
|
Loading…
x
Reference in New Issue
Block a user