Merge "Add hypermetro support for Huawei driver"

This commit is contained in:
Jenkins 2015-10-25 02:08:42 +00:00 committed by Gerrit Code Review
commit fc3ee36a2e
6 changed files with 950 additions and 42 deletions

View File

@ -30,11 +30,24 @@ from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import fc_zone_helper
from cinder.volume.drivers.huawei import huawei_driver
from cinder.volume.drivers.huawei import huawei_utils
from cinder.volume.drivers.huawei import hypermetro
from cinder.volume.drivers.huawei import rest_client
from cinder.volume.drivers.huawei import smartx
LOG = logging.getLogger(__name__)
hypermetro_devices = """{
"remote_device": {
"RestURL": "http://100.115.10.69:8082/deviceManager/rest",
"UserName": "admin",
"UserPassword": "Admin@storage1",
"StoragePool": "StoragePool001",
"domain_name": "hypermetro-domain",
"remote_target_ip": "111.111.101.241"
}
}
"""
test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 2,
'volume_name': 'vol1',
@ -59,6 +72,32 @@ fake_smartx_value = {'smarttier': 'true',
'partitionname': 'partition-test',
}
fake_hypermetro_opts = {'hypermetro': 'true',
'smarttier': False,
'smartcache': False,
'smartpartition': False,
'thin_provisioning_support': False,
'thick_provisioning_support': False,
}
hyper_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 2,
'volume_name': 'vol1',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'provider_auth': None,
'project_id': 'project',
'display_name': 'vol1',
'display_description': 'test volume',
'volume_type_id': None,
'host': 'ubuntu@huawei#OpenStack_Pool',
'provider_location': '11',
'volume_metadata': [{'key': 'hypermetro_id',
'value': '1'},
{'key': 'remote_lun_id',
'value': '11'}],
}
test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 1,
'volume_name': 'vol1',
@ -118,6 +157,24 @@ test_new_type = {
'description': None,
}
hypermetro_devices = """
{
"remote_device": {
"RestURL": "http://100.115.10.69:8082/deviceManager/rest",
"UserName":"admin",
"UserPassword":"Admin@storage2",
"StoragePool":"StoragePool001",
"domain_name":"hypermetro_test"}
}
"""
FAKE_FIND_POOL_RESPONSE = {'CAPACITY': '985661440',
'ID': '0',
'TOTALCAPACITY': '985661440'}
FAKE_CREATE_VOLUME_RESPONSE = {"ID": "1",
"NAME": "5mFHcBv4RkCcD+JyrWc0SA"}
FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'wwpns': ['10000090fa0d6754'],
'wwnns': ['10000090fa0d6755'],
@ -265,7 +322,7 @@ FAKE_QUERY_ALL_LUN_RESPONSE = """
"code": 0
},
"data": [{
"ID": "11",
"ID": "1",
"NAME": "IexzQZJWSXuX2e9I7c8GNQ"
}]
}
@ -920,6 +977,20 @@ FAKE_GET_FC_INI_RESPONSE = """
}
"""
FAKE_HYPERMETRODOMAIN_RESPONSE = """
{
"error":{
"code": 0
},
"data":{
"PRODUCTVERSION": "V100R001C10",
"ID": "11",
"NAME": "hypermetro_test",
"RUNNINGSTATUS": "42"
}
}
"""
FAKE_QOS_INFO_RESPONSE = """
{
"error":{
@ -944,8 +1015,42 @@ FAKE_GET_FC_PORT_RESPONSE = """
}
"""
FAKE_SMARTCACHEPARTITION_RESPONSE = """
{
"error":{
"code":0
},
"data":{
"ID":"11",
"NAME":"cache-name"
}
}
"""
FAKE_CONNECT_FC_RESPONCE = {
"driver_volume_type": 'fibre_channel',
"data": {
"target_wwn": ["10000090fa0d6754"],
"target_lun": "1",
"volume_id": "21ec7341-9256-497b-97d9-ef48edcf0635"
}
}
FAKE_METRO_INFO_RESPONCE = {
"error": {
"code": 0
},
"data": {
"PRODUCTVERSION": "V100R001C10",
"ID": "11",
"NAME": "hypermetro_test",
"RUNNINGSTATUS": "42"
}
}
# mock login info map
MAP_COMMAND_TO_FAKE_RESPONSE = {}
MAP_COMMAND_TO_FAKE_RESPONSE['/xx/sessions'] = (
FAKE_GET_LOGIN_STORAGE_RESPONSE)
@ -1243,6 +1348,12 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/fc_port/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator/GET'] = (
FAKE_GET_FC_PORT_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?range=[0-100]/GET'] = (
FAKE_GET_FC_PORT_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?PARENTTYPE=21&PARENTID=1/GET'] = (
FAKE_GET_FC_PORT_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/lun/associate/cachepartition/POST'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
@ -1252,6 +1363,33 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?range=[0-100]&PARENTID=1/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?PARENTTYPE=21&PARENTID=1/GET'] = (
FAKE_GET_FC_PORT_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/system/'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/SMARTCACHEPARTITION/0/GET'] = (
FAKE_SMARTCACHEPARTITION_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/SMARTCACHEPARTITION/REMOVE_ASSOCIATE/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/cachepartition/0/GET'] = (
FAKE_SMARTCACHEPARTITION_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroDomain?range=[0-100]/GET'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/POST'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/GET'] = (
FAKE_HYPERMETRODOMAIN_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/disable_hcpair/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
def Fake_sleep(time):
pass
@ -1373,6 +1511,7 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.cinder_huawei_conf_file = self.fake_conf_file
self.xml_file_path = self.configuration.cinder_huawei_conf_file
self.configuration.hypermetro_devices = hypermetro_devices
self.stubs.Set(time, 'sleep', Fake_sleep)
driver = Fake18000ISCSIStorage(configuration=self.configuration)
self.driver = driver
@ -1722,26 +1861,133 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
(qos_id, lun_list) = self.driver.restclient.find_available_qos(qos)
self.assertEqual(("11", u'["0", "1", "2"]'), (qos_id, lun_list))
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
@mock.patch.object(huawei_utils, 'get_volume_params',
return_value=fake_hypermetro_opts)
@mock.patch.object(rest_client.RestClient, 'login_with_ip',
return_value='123456789')
@mock.patch.object(rest_client.RestClient, 'find_all_pools',
return_value=FAKE_STORAGE_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'find_pool_info',
return_value=FAKE_FIND_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'create_volume',
return_value=FAKE_CREATE_VOLUME_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
return_value='11')
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'ID': '11'})
def test_create_volume_exist(self, mock_lun_info, mock_volume_info):
@mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
return_value=True)
@mock.patch.object(hypermetro.HuaweiHyperMetro,
'_create_hypermetro_pair',
return_value={"ID": '11',
"NAME": 'hypermetro-pair'})
@mock.patch.object(rest_client.RestClient, 'logout',
return_value=None)
def test_create_hypermetro_success(self, mock_logout,
mock_hyper_pair_info,
mock_volume_ready,
mock_hyper_domain,
mock_create_volume,
mock_pool_info,
mock_all_pool_info,
mock_login_return,
mock_hypermetro_opts):
self.driver.restclient.login()
lun_param = {'NAME': 'IexzQZJWSXuX2e9I7c8GNQ'}
metadata = {"hypermetro_id": '11',
"remote_lun_id": '1'}
lun_info = self.driver.create_volume(hyper_volume)
mock_logout.assert_called_with()
self.assertEqual(metadata, lun_info['metadata'])
fack_error_volume_exist = {"error": {"code": 1077948993}}
with mock.patch.object(rest_client.RestClient, 'call',
return_value=fack_error_volume_exist):
lun_info = self.driver.restclient.create_volume(lun_param)
self.assertEqual('11', lun_info['ID'])
@mock.patch.object(huawei_utils, 'get_volume_params',
return_value=fake_hypermetro_opts)
@mock.patch.object(rest_client.RestClient, 'login_with_ip',
return_value='123456789')
@mock.patch.object(rest_client.RestClient, 'find_all_pools',
return_value=FAKE_STORAGE_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'find_pool_info',
return_value=FAKE_FIND_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'create_volume',
return_value=FAKE_CREATE_VOLUME_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
return_value='11')
@mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
return_value=True)
@mock.patch.object(hypermetro.HuaweiHyperMetro,
'_create_hypermetro_pair')
@mock.patch.object(rest_client.RestClient, 'delete_lun',
return_value=None)
@mock.patch.object(rest_client.RestClient, 'logout',
return_value=None)
def test_create_hypermetro_fail(self, mock_logout,
mock_delete_lun,
mock_hyper_pair_info,
mock_volume_ready,
mock_hyper_domain,
mock_create_volume,
mock_pool_info,
mock_all_pool_info,
mock_login_return,
mock_hypermetro_opts):
self.driver.restclient.login()
mock_hyper_pair_info.side_effect = exception.VolumeBackendAPIException(
data='Create hypermetro error.')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, hyper_volume)
mock_delete_lun.assert_called_with('1')
mock_logout.assert_called_with()
fack_error_volume_exist = {"error": {"code": 123456789}}
with mock.patch.object(rest_client.RestClient, 'call',
return_value=fack_error_volume_exist):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.restclient.create_volume,
lun_param)
@mock.patch.object(rest_client.RestClient, 'login_with_ip',
return_value='123456789')
@mock.patch.object(rest_client.RestClient, 'check_lun_exist',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'check_hypermetro_exist',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'get_hypermetro_by_id',
return_value=FAKE_METRO_INFO_RESPONCE)
@mock.patch.object(rest_client.RestClient, 'delete_hypermetro',
return_value=FAKE_COMMON_SUCCESS_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'delete_lun',
return_value=None)
@mock.patch.object(rest_client.RestClient, 'logout',
return_value=None)
def test_delete_hypermetro_success(self, mock_logout,
mock_delete_lun,
mock_delete_hypermetro,
mock_metro_info,
mock_check_hyermetro,
mock_lun_exit,
mock_login_info):
self.driver.restclient.login()
result = self.driver.delete_volume(hyper_volume)
mock_logout.assert_called_with()
self.assertTrue(result)
@mock.patch.object(rest_client.RestClient, 'login_with_ip',
return_value='123456789')
@mock.patch.object(rest_client.RestClient, 'check_lun_exist',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'check_hypermetro_exist',
return_value=True)
@mock.patch.object(rest_client.RestClient, 'get_hypermetro_by_id',
return_value=FAKE_METRO_INFO_RESPONCE)
@mock.patch.object(rest_client.RestClient, 'delete_hypermetro')
@mock.patch.object(rest_client.RestClient, 'delete_lun',
return_value=None)
@mock.patch.object(rest_client.RestClient, 'logout',
return_value=None)
def test_delete_hypermetro_fail(self, mock_logout,
mock_delete_lun,
mock_delete_hypermetro,
mock_metro_info,
mock_check_hyermetro,
mock_lun_exit,
mock_login_info):
self.driver.restclient.login()
mock_delete_hypermetro.side_effect = (
exception.VolumeBackendAPIException(data='Delete hypermetro '
'error.'))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_volume, hyper_volume)
mock_delete_lun.assert_called_with('11')
def create_fake_conf_file(self):
"""Create a fake Config file.
@ -1848,6 +2094,7 @@ class Huawei18000FCDriverTestCase(test.TestCase):
self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.cinder_huawei_conf_file = self.fake_conf_file
self.xml_file_path = self.configuration.cinder_huawei_conf_file
self.configuration.hypermetro_devices = hypermetro_devices
self.stubs.Set(time, 'sleep', Fake_sleep)
driver = Fake18000FCStorage(configuration=self.configuration)
self.driver = driver
@ -2243,6 +2490,41 @@ class Huawei18000FCDriverTestCase(test.TestCase):
None)
self.assertEqual(expected_pool_capacity, pool_capacity)
@mock.patch.object(huawei_utils, 'get_volume_params',
return_value=fake_hypermetro_opts)
@mock.patch.object(rest_client.RestClient, 'login_with_ip',
return_value='123456789')
@mock.patch.object(rest_client.RestClient, 'find_all_pools',
return_value=FAKE_STORAGE_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'find_pool_info',
return_value=FAKE_FIND_POOL_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'create_volume',
return_value=FAKE_CREATE_VOLUME_RESPONSE)
@mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
return_value='11')
@mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
return_value=True)
@mock.patch.object(hypermetro.HuaweiHyperMetro,
'_create_hypermetro_pair',
return_value={"ID": '11',
"NAME": 'hypermetro-pair'})
@mock.patch.object(rest_client.RestClient, 'logout',
return_value=None)
def test_create_hypermetro_success(self, mock_hypermetro_opts,
mock_login_return,
mock_all_pool_info,
mock_pool_info,
mock_create_volume,
mock_hyper_domain,
mock_volume_ready,
mock_pair_info,
mock_logout):
self.driver.restclient.login()
metadata = {"hypermetro_id": '11',
"remote_lun_id": '1'}
lun_info = self.driver.create_volume(hyper_volume)
self.assertEqual(metadata, lun_info['metadata'])
def create_fake_conf_file(self):
"""Create a fake Config file

View File

@ -42,6 +42,10 @@ ERROR_UNAUTHORIZED_TO_SERVER = -401
SOCKET_TIMEOUT = 52
ERROR_VOLUME_ALREADY_EXIST = 1077948993
LOGIN_SOCKET_TIMEOUT = 4
ERROR_VOLUME_NOT_EXIST = 1077939726
RELOGIN_ERROR_PASS = [ERROR_VOLUME_NOT_EXIST]
HYPERMETRO_RUNNSTATUS_STOP = 41
HYPERMETRO_RUNNSTATUS_NORMAL = 1
THICK_LUNTYPE = 0
THIN_LUNTYPE = 1
@ -62,3 +66,4 @@ HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth',
QOS_KEYS = ['MAXIOPS', 'MINIOPS', 'MINBANDWidth',
'MAXBANDWidth', 'LATENCY', 'IOTYPE']
MAX_LUN_NUM_IN_QOS = 64
HYPERMETRO_CLASS = "cinder.volume.drivers.huawei.hypermetro.HuaweiHyperMetro"

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import six
import uuid
@ -28,6 +29,7 @@ from cinder.volume import driver
from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import fc_zone_helper
from cinder.volume.drivers.huawei import huawei_utils
from cinder.volume.drivers.huawei import hypermetro
from cinder.volume.drivers.huawei import rest_client
from cinder.volume.drivers.huawei import smartx
from cinder.volume import utils as volume_utils
@ -39,8 +41,11 @@ LOG = logging.getLogger(__name__)
huawei_opts = [
cfg.StrOpt('cinder_huawei_conf_file',
default='/etc/cinder/cinder_huawei_conf.xml',
help='The configuration file for the Cinder Huawei '
'driver.')]
help='The configuration file for the Cinder Huawei driver.'),
cfg.StrOpt('hypermetro_devices',
default=None,
help='The remote device hypermetro will use.'),
]
CONF = cfg.CONF
CONF.register_opts(huawei_opts)
@ -57,6 +62,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
self.configuration.append_config_values(huawei_opts)
self.xml_file_path = self.configuration.cinder_huawei_conf_file
self.hypermetro_devices = self.configuration.hypermetro_devices
def do_setup(self, context):
"""Instantiate common class and login storage system."""
@ -127,9 +133,31 @@ class HuaweiBaseDriver(driver.VolumeDriver):
raise exception.InvalidInput(
reason=_('Create volume error. Because %s.') % err)
return {'provider_location': lun_info['ID'],
# Update the metadata.
LOG.info(_LI('Create volume option: %s.'), opts)
metadata = huawei_utils.get_volume_metadata(volume)
if opts.get('hypermetro'):
hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
self.configuration)
try:
metro_id, remote_lun_id = hyperm.create_hypermetro(lun_id,
lun_param)
except exception.VolumeBackendAPIException as err:
LOG.exception(_LE('Create hypermetro error: %s.'), err)
self._delete_lun_with_check(lun_id)
raise
LOG.info(_LI("Hypermetro id: %(metro_id)s. "
"Remote lun id: %(remote_lun_id)s."),
{'metro_id': metro_id,
'remote_lun_id': remote_lun_id})
metadata.update({'hypermetro_id': metro_id,
'remote_lun_id': remote_lun_id})
return {'provider_location': lun_id,
'ID': lun_id,
'lun_info': lun_info}
'metadata': metadata}
@utils.synchronized('huawei', external=True)
def delete_volume(self, volume):
@ -150,6 +178,17 @@ class HuaweiBaseDriver(driver.VolumeDriver):
if qos_id:
self.remove_qos_lun(lun_id, qos_id)
metadata = huawei_utils.get_volume_metadata(volume)
if 'hypermetro_id' in metadata:
hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
self.configuration)
try:
hyperm.delete_hypermetro(volume)
except exception.VolumeBackendAPIException as err:
LOG.exception(_LE('Delete hypermetro error: %s.'), err)
self.restclient.delete_lun(lun_id)
raise
self.restclient.delete_lun(lun_id)
else:
LOG.warning(_LW("Can't find lun %s on the array."), lun_id)
@ -188,11 +227,11 @@ class HuaweiBaseDriver(driver.VolumeDriver):
if constants.MIGRATION_COMPLETE == item['RUNNINGSTATUS']:
return True
if constants.MIGRATION_FAULT == item['RUNNINGSTATUS']:
err_msg = (_('Lun migration error.'))
err_msg = _('Lun migration error.')
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
if not found_migration_task:
err_msg = (_("Cannot find migration task."))
err_msg = _("Cannot find migration task.")
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
@ -1010,6 +1049,7 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
Volume migration support
Volume retype support
FC zone enhancement
Volume hypermetro support
"""
VERSION = "1.1.1"
@ -1090,23 +1130,82 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
# Add host into hostgroup.
hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
self.restclient.do_mapping(lun_id, hostgroup_id, host_id)
map_info = self.restclient.do_mapping(lun_id,
hostgroup_id,
host_id)
host_lun_id = self.restclient.find_host_lun_id(host_id, lun_id)
# 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}, }
fc_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,
'map_info': map_info}, }
LOG.info(_LI("initialize_connection, return data is: %s."),
info)
loc_tgt_wwn = fc_info['data']['target_wwn']
local_ini_tgt_map = fc_info['data']['initiator_target_map']
return info
# Deal with hypermetro connection.
metadata = huawei_utils.get_volume_metadata(volume)
LOG.info(_LI("initialize_connection, metadata is: %s."), metadata)
if 'hypermetro_id' in metadata:
hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
self.configuration)
rmt_fc_info = hyperm.connect_volume_fc(volume, connector)
rmt_tgt_wwn = rmt_fc_info['data']['target_wwn']
rmt_ini_tgt_map = rmt_fc_info['data']['initiator_target_map']
fc_info['data']['target_wwn'] = (loc_tgt_wwn + rmt_tgt_wwn)
wwns = connector['wwpns']
for wwn in wwns:
if (wwn in local_ini_tgt_map
and wwn in rmt_ini_tgt_map):
fc_info['data']['initiator_target_map'][wwn].extend(
rmt_ini_tgt_map[wwn])
elif (wwn not in local_ini_tgt_map
and wwn in rmt_ini_tgt_map):
fc_info['data']['initiator_target_map'][wwn] = (
rmt_ini_tgt_map[wwn])
# else, do nothing
loc_map_info = fc_info['data']['map_info']
rmt_map_info = rmt_fc_info['data']['map_info']
same_host_id = self._get_same_hostid(loc_map_info,
rmt_map_info)
self.restclient.change_hostlun_id(loc_map_info, same_host_id)
hyperm.rmt_client.change_hostlun_id(rmt_map_info, same_host_id)
fc_info['data']['target_lun'] = same_host_id
hyperm.rmt_client.logout()
LOG.info(_LI("Return FC info is: %s."), fc_info)
return fc_info
def _get_same_hostid(self, loc_fc_info, rmt_fc_info):
loc_aval_luns = loc_fc_info['aval_luns']
loc_aval_luns = json.loads(loc_aval_luns)
rmt_aval_luns = rmt_fc_info['aval_luns']
rmt_aval_luns = json.loads(rmt_aval_luns)
same_host_id = None
for i in range(1, 512):
if i in rmt_aval_luns and i in loc_aval_luns:
same_host_id = i
break
LOG.info(_LI("The same hostid is: %s."), same_host_id)
if not same_host_id:
msg = _("Can't find the same host id from arrays.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return same_host_id
@utils.synchronized('huawei', external=True)
@fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs):
"""Delete map between a volume and a host."""
@ -1150,8 +1249,8 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
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': {}}
fc_info = {'driver_volume_type': 'fibre_channel',
'data': {}}
else:
if not self.fcsan_lookup_service:
self.fcsan_lookup_service = fczm_utils.create_lookup_service()
@ -1195,10 +1294,19 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
if view_id:
self.restclient.delete_mapping_view(view_id)
info = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': tgt_port_wwns,
'initiator_target_map': init_targ_map}}
LOG.info(_LI("terminate_connection, return data is: %s."),
info)
fc_info = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': tgt_port_wwns,
'initiator_target_map': init_targ_map}}
return info
# Deal with hypermetro connection.
metadata = huawei_utils.get_volume_metadata(volume)
LOG.info(_LI("Detach Volume, metadata is: %s."), metadata)
if 'hypermetro_id' in metadata:
hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
self.configuration)
hyperm.disconnect_volume_fc(volume, connector)
LOG.info(_LI("terminate_connection, return data is: %s."),
fc_info)
return fc_info

View File

@ -14,6 +14,7 @@
# under the License.
import base64
import json
import six
import time
import uuid
@ -40,6 +41,7 @@ opts_capability = {
'smartpartition': False,
'thin_provisioning_support': False,
'thick_provisioning_support': False,
'hypermetro': False,
}
@ -539,3 +541,29 @@ def get_pools(xml_file_path):
LOG.error(msg)
raise exception.InvalidInput(msg)
return pool_names
def get_remote_device_info(valid_hypermetro_devices):
remote_device_info = {}
try:
if valid_hypermetro_devices:
remote_device_info = json.loads(valid_hypermetro_devices)
else:
return
except ValueError as err:
msg = _("Get remote device info error. %s.") % err
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if len(remote_device_info) == 1:
for device_key, device_value in remote_device_info.items():
return remote_device_info.get(device_key)
def get_volume_metadata(volume):
if 'volume_metadata' in volume:
metadata = volume.get('volume_metadata')
return {item['key']: item['value'] for item in metadata}
return {}

View File

@ -0,0 +1,321 @@
# 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 six
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _, _LI, _LW
from cinder.volume.drivers.huawei import constants
from cinder.volume.drivers.huawei import huawei_utils
from cinder.volume.drivers.huawei import rest_client
LOG = logging.getLogger(__name__)
class HuaweiHyperMetro(object):
def __init__(self, client, rmt_client, configuration):
self.client = client
self.rmt_client = rmt_client
self.configuration = configuration
self.xml_file_path = self.configuration.cinder_huawei_conf_file
def create_hypermetro(self, local_lun_id, lun_param):
"""Create hypermetro."""
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
# Get the remote pool info.
config_pool = device_info['StoragePool']
remote_pool = self.rmt_client.find_all_pools()
pool = self.rmt_client.find_pool_info(config_pool,
remote_pool)
# Create remote lun
lun_param['PARENTID'] = pool['ID']
remotelun_info = self.rmt_client.create_volume(lun_param)
remote_lun_id = remotelun_info['ID']
# Get hypermetro domain
try:
domain_name = device_info['domain_name']
domain_id = self.rmt_client.get_hyper_domain_id(domain_name)
self._wait_volume_ready(remote_lun_id)
hypermetro = self._create_hypermetro_pair(domain_id,
local_lun_id,
remote_lun_id)
return hypermetro['ID'], remote_lun_id
except Exception as err:
self.rmt_client.delete_lun(remote_lun_id)
msg = _('Create hypermetro error. %s.') % err
raise exception.VolumeBackendAPIException(data=msg)
except exception.VolumeBackendAPIException:
raise
except Exception as err:
msg = _("Create remote LUN error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
def delete_hypermetro(self, volume):
"""Delete hypermetro."""
metadata = huawei_utils.get_volume_metadata(volume)
metro_id = metadata['hypermetro_id']
remote_lun_id = metadata['remote_lun_id']
if metro_id:
exst_flag = self.client.check_hypermetro_exist(metro_id)
if exst_flag:
metro_info = self.client.get_hypermetro_by_id(metro_id)
metro_status = int(metro_info['data']['RUNNINGSTATUS'])
LOG.debug("Hypermetro status is: %s.", metro_status)
if constants.HYPERMETRO_RUNNSTATUS_STOP != metro_status:
self.client.stop_hypermetro(metro_id)
# Delete hypermetro
self.client.delete_hypermetro(metro_id)
# Delete remote lun.
if remote_lun_id:
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
if self.rmt_client.check_lun_exist(remote_lun_id):
self.rmt_client.delete_lun(remote_lun_id)
except Exception as err:
msg = _("Delete remote lun err. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
def _create_hypermetro_pair(self, domain_id, lun_id, remote_lun_id):
"""Create a HyperMetroPair."""
hcp_param = {"DOMAINID": domain_id,
"HCRESOURCETYPE": '1',
"ISFIRSTSYNC": False,
"LOCALOBJID": lun_id,
"RECONVERYPOLICY": '1',
"REMOTEOBJID": remote_lun_id,
"SPEED": '2'}
return self.client.create_hypermetro(hcp_param)
def connect_volume_fc(self, volume, connector):
"""Create map between a volume and a host for FC."""
self.xml_file_path = self.configuration.cinder_huawei_conf_file
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
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})
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
if lun_id is None:
lun_id = self.rmt_client.get_volume_by_name(volume_name)
if lun_id is None:
msg = _("Can't get volume id. Volume name: %s.") % volume_name
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
host_name_before_hash = None
host_name = connector['host']
if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
host_name_before_hash = host_name
host_name = six.text_type(hash(host_name))
# Create hostgroup if not exist.
host_id = self.rmt_client.add_host_with_check(
host_name, host_name_before_hash)
online_wwns_in_host = (
self.rmt_client.get_host_online_fc_initiators(host_id))
online_free_wwns = self.rmt_client.get_online_free_wwns()
for wwn in wwns:
if (wwn not in online_wwns_in_host
and wwn not in online_free_wwns):
wwns_in_host = (
self.rmt_client.get_host_fc_initiators(host_id))
iqns_in_host = (
self.rmt_client.get_host_iscsi_initiators(host_id))
if not wwns_in_host and not iqns_in_host:
self.rmt_client.remove_host(host_id)
msg = _('Can not add FC port to host.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
for wwn in wwns:
if wwn in online_free_wwns:
self.rmt_client.add_fc_port_to_host(host_id, wwn)
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
# Add host into hostgroup.
hostgroup_id = self.rmt_client.add_host_into_hostgroup(host_id)
map_info = self.rmt_client.do_mapping(lun_id,
hostgroup_id,
host_id)
host_lun_id = self.rmt_client.find_host_lun_id(host_id, lun_id)
except exception.VolumeBackendAPIException:
raise
except Exception as err:
msg = _("Connect volume fc: connect volume error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Return FC properties.
fc_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,
'map_info': map_info},
}
LOG.info(_LI('Remote return FC info is: %s.'), fc_info)
return fc_info
def disconnect_volume_fc(self, volume, connector):
"""Delete map between a volume and a host for FC."""
# Login remote storage device.
self.xml_file_path = self.configuration.cinder_huawei_conf_file
metro_devices = self.configuration.hypermetro_devices
device_info = huawei_utils.get_remote_device_info(metro_devices)
self.rmt_client = rest_client.RestClient(self.configuration)
self.rmt_client.login_with_ip(device_info)
try:
wwns = connector['wwpns']
volume_name = huawei_utils.encode_name(volume['id'])
metadata = huawei_utils.get_volume_metadata(volume)
lun_id = metadata['remote_lun_id']
host_name = connector['host']
left_lunnum = -1
lungroup_id = None
view_id = None
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 host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
host_name = six.text_type(hash(host_name))
hostid = self.rmt_client.find_host(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
if lun_id and self.rmt_client.check_lun_exist(lun_id):
if lungroup_id:
lungroup_ids = self.rmt_client.get_lungroupids_by_lunid(
lun_id)
if lungroup_id in lungroup_ids:
self.rmt_client.remove_lun_from_lungroup(
lungroup_id, lun_id)
else:
LOG.warning(_LW("Lun is not in lungroup. "
"Lun id: %(lun_id)s, "
"lungroup id: %(lungroup_id)s"),
{"lun_id": lun_id,
"lungroup_id": lungroup_id})
(tgt_port_wwns, init_targ_map) = (
self.rmt_client.get_init_targ_map(wwns))
hostid = self.rmt_client.find_host(host_name)
if hostid:
mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
view_id = self.rmt_client.find_mapping_view(
mapping_view_name)
if view_id:
lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id)
if lungroup_id:
left_lunnum = self.rmt_client.get_lunnum_from_lungroup(
lungroup_id)
except Exception as err:
msg = _("Remote detatch volume error. %s.") % err
LOG.exception(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
self.rmt_client.logout()
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 _wait_volume_ready(self, lun_id):
event_type = 'LUNReadyWaitInterval'
wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
event_type)
def _volume_ready():
result = self.rmt_client.get_lun_info(lun_id)
if (result['HEALTHSTATUS'] == constants.STATUS_HEALTH
and result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY):
return True
return False
huawei_utils.wait_for_condition(self.xml_file_path,
_volume_ready,
wait_interval,
wait_interval * 10)
def retype(self, volume, new_type):
return False
def get_hypermetro_stats(self, hypermetro_id):
pass

View File

@ -14,6 +14,7 @@
# under the License.
import json
import six
import socket
import time
@ -38,6 +39,7 @@ class RestClient(object):
self.configuration = configuration
self.xml_file_path = configuration.cinder_huawei_conf_file
self.productversion = None
self.init_http_head()
def init_http_head(self):
self.cookie = http_cookiejar.CookieJar()
@ -149,8 +151,47 @@ class RestClient(object):
{'old_url': old_url,
'new_url': self.url})
result = self.do_call(url, data, method)
if result['error']['code'] in constants.RELOGIN_ERROR_PASS:
result['error']['code'] = 0
return result
def login_with_ip(self, login_info):
"""Login 18000 array with the specific URL."""
urlstr = login_info['RestURL']
url_list = urlstr.split(";")
for item_url in url_list:
url = item_url + "xx/sessions"
data = json.dumps({"username": login_info['UserName'],
"password": login_info['UserPassword'],
"scope": '0'})
result = self.call(url, data)
if result['error']['code'] == constants.ERROR_CONNECT_TO_SERVER:
continue
if (result['error']['code'] != 0) or ('data' not in result):
msg = (_("Login error, reason is: %s.") % result)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
device_id = result['data']['deviceid']
self.device_id = device_id
self.url = item_url + device_id
self.headers['iBaseToken'] = result['data']['iBaseToken']
return device_id
msg = _("Login error: Can not connect to server.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def logout(self):
"""Logout the session."""
url = "/sessions"
if self.url:
result = self.call(url, None, "DELETE")
self._assert_rest_result(result, _('Logout session error.'))
def _assert_rest_result(self, result, err_str):
if result['error']['code'] != 0:
msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
@ -383,6 +424,7 @@ class RestClient(object):
mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
lungroup_id = self._find_lungroup(lungroup_name)
view_id = self.find_mapping_view(mapping_view_name)
map_info = {}
LOG.info(_LI(
'do_mapping, lun_group: %(lun_group)s, '
@ -418,6 +460,13 @@ class RestClient(object):
self._associate_portgroup_to_view(view_id,
tgtportgroup_id)
version = self.find_array_version()
if version >= constants.ARRAY_VERSION:
aval_luns = self.find_view_by_id(view_id)
map_info["lun_id"] = lun_id
map_info["view_id"] = view_id
map_info["aval_luns"] = aval_luns
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE(
@ -425,6 +474,8 @@ class RestClient(object):
'view. Remove lun from lungroup now.'))
self.remove_lun_from_lungroup(lungroup_id, lun_id)
return map_info
def ensure_initiator_added(self, xml_file_path, initiator_name, host_id):
added = self._initiator_is_added_to_array(initiator_name)
if not added:
@ -1114,6 +1165,7 @@ class RestClient(object):
smarttier=True,
smartcache=True,
smartpartition=True,
hypermetro=True,
))
data['pools'].append(pool)
return data
@ -1200,6 +1252,9 @@ class RestClient(object):
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
# Deal with the remote tgt ip.
if 'remote_target_ip' in connector:
target_ips.append(connector['remote_target_ip'])
LOG.info(_LI('Get the default ip: %s.'), target_ips)
for ip in target_ips:
target_iqn = self._get_tgt_iqn_from_rest(ip)
@ -1678,3 +1733,112 @@ class RestClient(object):
constants.FC_PORT_CONNECTED):
port_list_from_contr.append(item['WWN'])
return port_list_from_contr
def get_hyper_domain_id(self, domain_name):
url = "/HyperMetroDomain?range=[0-100]"
result = self.call(url, None, "GET")
domain_id = None
if "data" in result:
for item in result['data']:
if domain_name == item['NAME']:
domain_id = item['ID']
break
msg = _('get_hyper_domain_id error.')
self._assert_rest_result(result, msg)
return domain_id
def create_hypermetro(self, hcp_param):
url = "/HyperMetroPair"
data = json.dumps(hcp_param)
result = self.call(url, data, "POST")
msg = _('create_hypermetro_pair error.')
self._assert_rest_result(result, msg)
self._assert_data_in_result(result, msg)
return result['data']
def delete_hypermetro(self, metro_id):
url = "/HyperMetroPair/" + metro_id
result = self.call(url, None, "DELETE")
msg = _('delete_hypermetro error.')
self._assert_rest_result(result, msg)
def sync_hypermetro(self, metro_id):
url = "/HyperMetroPair/synchronize_hcpair"
data = json.dumps({"ID": metro_id,
"TYPE": "15361"})
result = self.call(url, data, "PUT")
msg = _('sync_hypermetro error.')
self._assert_rest_result(result, msg)
def stop_hypermetro(self, metro_id):
url = '/HyperMetroPair/disable_hcpair'
data = json.dumps({"ID": metro_id,
"TYPE": "15361"})
result = self.call(url, data, "PUT")
msg = _('stop_hypermetro error.')
self._assert_rest_result(result, msg)
def get_hypermetro_by_id(self, metro_id):
url = "/HyperMetroPair/" + metro_id
result = self.call(url, None, "GET")
msg = _('get_hypermetro_by_id error.')
self._assert_rest_result(result, msg)
return result
def check_hypermetro_exist(self, metro_id):
url = "/HyperMetroPair/" + metro_id
result = self.call(url, None, "GET")
error_code = result['error']['code']
if (error_code == constants.ERROR_CONNECT_TO_SERVER
or error_code == constants.ERROR_UNAUTHORIZED_TO_SERVER):
LOG.error(_LE("Can not open the recent url, login again."))
self.login()
result = self.call(url, None, "GET")
error_code = result['error']['code']
if (error_code == constants.ERROR_CONNECT_TO_SERVER
or error_code == constants.ERROR_UNAUTHORIZED_TO_SERVER):
msg = _("check_hypermetro_exist error.")
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if error_code != 0:
return False
return True
def change_hostlun_id(self, map_info, hostlun_id):
url = "/mappingview"
view_id = six.text_type(map_info['view_id'])
lun_id = six.text_type(map_info['lun_id'])
hostlun_id = six.text_type(hostlun_id)
data = json.dumps({"TYPE": 245,
"ID": view_id,
"ASSOCIATEOBJTYPE": 11,
"ASSOCIATEOBJID": lun_id,
"ASSOCIATEMETADATA": [{"LUNID": lun_id,
"hostLUNId": hostlun_id}]
})
result = self.call(url, data, "PUT")
msg = 'change hostlun id error.'
self._assert_rest_result(result, msg)
def find_view_by_id(self, view_id):
url = "/MAPPINGVIEW/" + view_id
result = self.call(url, None, "GET")
msg = _('Change hostlun id error.')
self._assert_rest_result(result, msg)
if 'data' in result:
return result["data"]["AVAILABLEHOSTLUNIDLIST"]