Merge "Add hypermetro support for Huawei driver"
This commit is contained in:
commit
fc3ee36a2e
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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 {}
|
||||
|
321
cinder/volume/drivers/huawei/hypermetro.py
Normal file
321
cinder/volume/drivers/huawei/hypermetro.py
Normal 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
|
@ -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"]
|
||||
|
Loading…
Reference in New Issue
Block a user