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',
'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',
'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'],
"code": 0
},
"data": [{
- "ID": "11",
+ "ID": "1",
"NAME": "IexzQZJWSXuX2e9I7c8GNQ"
}]
}
}
"""
+FAKE_HYPERMETRODOMAIN_RESPONSE = """
+{
+ "error":{
+ "code": 0
+ },
+ "data":{
+ "PRODUCTVERSION": "V100R001C10",
+ "ID": "11",
+ "NAME": "hypermetro_test",
+ "RUNNINGSTATUS": "42"
+ }
+}
+"""
+
FAKE_QOS_INFO_RESPONSE = """
{
"error":{
}
"""
+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)
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)
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
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
(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()
+
+ @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)
- 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')
+ @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.
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
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
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
QOS_KEYS = ['MAXIOPS', 'MINIOPS', 'MINBANDWidth',
'MAXBANDWidth', 'LATENCY', 'IOTYPE']
MAX_LUN_NUM_IN_QOS = 64
+HYPERMETRO_CLASS = "cinder.volume.drivers.huawei.hypermetro.HuaweiHyperMetro"
# License for the specific language governing permissions and limitations
# under the License.
+import json
import six
import uuid
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
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)
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."""
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):
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)
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)
Volume migration support
Volume retype support
FC zone enhancement
+ Volume hypermetro support
"""
VERSION = "1.1.1"
# 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}, }
+
+ loc_tgt_wwn = fc_info['data']['target_wwn']
+ local_ini_tgt_map = fc_info['data']['initiator_target_map']
+
+ # 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])
- LOG.info(_LI("initialize_connection, return data is: %s."),
- info)
+ 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
- return info
+ 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."""
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()
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}}
+ fc_info = {'driver_volume_type': 'fibre_channel',
+ 'data': {'target_wwn': tgt_port_wwns,
+ 'initiator_target_map': init_targ_map}}
+
+ # 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."),
- info)
+ fc_info)
- return info
+ return fc_info
# under the License.
import base64
+import json
import six
import time
import uuid
'smartpartition': False,
'thin_provisioning_support': False,
'thick_provisioning_support': False,
+ 'hypermetro': False,
}
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 {}
--- /dev/null
+# 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
# under the License.
import json
+import six
import socket
import time
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()
{'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,
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, '
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(
'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:
smarttier=True,
smartcache=True,
smartpartition=True,
+ hypermetro=True,
))
data['pools'].append(pool)
return data
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)
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"]