Add Fujitsu ETERNUS DX Volume Driver (FC part)
As I explained in my blueprint, this patch completes Fujitsu ETERNUS DX Volume Driver. Fujitsu ETERNUS DX Volume Driver consists of two parts, iSCSI and FC. The iSCSI part [1] had been reviewed and thanks to the nice reviews, it's merged. The iSCSI and FC parts have a lot of common codes, and all the common codes are included in the iSCSI part. [1] https://review.openstack.org/201500/ DocImpact Implements: blueprint fujitsu-eternus-dx-driver Change-Id: If61145ee999bffd82223a99a7d59de315a5ecd3b
This commit is contained in:
parent
551cd6ae07
commit
0ea3394dec
@ -26,6 +26,7 @@ from cinder.volume import configuration as conf
|
||||
|
||||
with mock.patch.dict('sys.modules', pywbem=mock.Mock()):
|
||||
from cinder.volume.drivers.fujitsu import eternus_dx_common as dx_common
|
||||
from cinder.volume.drivers.fujitsu import eternus_dx_fc as dx_fc
|
||||
from cinder.volume.drivers.fujitsu import eternus_dx_iscsi as dx_iscsi
|
||||
|
||||
CONFIG_FILE_NAME = 'cinder_fujitsu_eternus_dx.xml'
|
||||
@ -693,6 +694,119 @@ class FakeEternusConnection(object):
|
||||
return instance
|
||||
|
||||
|
||||
class FJFCDriverTestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FJFCDriverTestCase, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(FJFCDriverTestCase, self).setUp()
|
||||
|
||||
# Make fake xml-configuration file.
|
||||
self.config_file = tempfile.NamedTemporaryFile("w+", suffix='.xml')
|
||||
self.addCleanup(self.config_file.close)
|
||||
self.config_file.write(CONF)
|
||||
self.config_file.flush()
|
||||
|
||||
# Make fake Object by using mock as configuration object.
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
self.configuration.cinder_eternus_config_file = self.config_file.name
|
||||
|
||||
self.stubs.Set(dx_common.FJDXCommon, '_get_eternus_connection',
|
||||
self.fake_eternus_connection)
|
||||
|
||||
instancename = FakeCIMInstanceName()
|
||||
self.stubs.Set(dx_common.FJDXCommon, '_create_eternus_instance_name',
|
||||
instancename.fake_create_eternus_instance_name)
|
||||
|
||||
# Set iscsi driver to self.driver.
|
||||
driver = dx_fc.FJDXFCDriver(configuration=self.configuration)
|
||||
self.driver = driver
|
||||
|
||||
def fake_eternus_connection(self):
|
||||
conn = FakeEternusConnection()
|
||||
return conn
|
||||
|
||||
def test_get_volume_stats(self):
|
||||
ret = self.driver.get_volume_stats(True)
|
||||
stats = {'vendor_name': ret['vendor_name'],
|
||||
'total_capacity_gb': ret['total_capacity_gb'],
|
||||
'free_capacity_gb': ret['free_capacity_gb']}
|
||||
self.assertEqual(FAKE_STATS, stats)
|
||||
|
||||
def test_create_and_delete_volume(self):
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
self.driver.delete_volume(TEST_VOLUME)
|
||||
|
||||
@mock.patch.object(dx_common.FJDXCommon, '_get_mapdata')
|
||||
def test_map_unmap(self, mock_mapdata):
|
||||
fake_data = {'target_wwn': FC_TARGET_WWN,
|
||||
'target_lun': 0}
|
||||
|
||||
mock_mapdata.return_value = fake_data
|
||||
fake_mapdata = dict(fake_data)
|
||||
fake_mapdata['initiator_target_map'] = {
|
||||
initiator: FC_TARGET_WWN for initiator in TEST_WWPN
|
||||
}
|
||||
|
||||
fake_mapdata['volume_id'] = TEST_VOLUME['id']
|
||||
fake_mapdata['target_discovered'] = True
|
||||
fake_info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': fake_mapdata}
|
||||
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
info = self.driver.initialize_connection(TEST_VOLUME,
|
||||
TEST_CONNECTOR)
|
||||
self.assertEqual(fake_info, info)
|
||||
self.driver.terminate_connection(TEST_VOLUME,
|
||||
TEST_CONNECTOR)
|
||||
self.driver.delete_volume(TEST_VOLUME)
|
||||
|
||||
def test_create_and_delete_snapshot(self):
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
snap_info = self.driver.create_snapshot(TEST_SNAP)
|
||||
self.assertEqual(FAKE_SNAP_INFO, snap_info)
|
||||
|
||||
self.driver.delete_snapshot(TEST_SNAP)
|
||||
self.driver.delete_volume(TEST_VOLUME)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
snap_info = self.driver.create_snapshot(TEST_SNAP)
|
||||
self.assertEqual(FAKE_SNAP_INFO, snap_info)
|
||||
|
||||
model_info = self.driver.create_volume_from_snapshot(TEST_CLONE,
|
||||
TEST_SNAP)
|
||||
self.assertEqual(FAKE_MODEL_INFO2, model_info)
|
||||
|
||||
self.driver.delete_snapshot(TEST_SNAP)
|
||||
self.driver.delete_volume(TEST_CLONE)
|
||||
self.driver.delete_volume(TEST_VOLUME)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
model_info = self.driver.create_cloned_volume(TEST_CLONE, TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO2, model_info)
|
||||
|
||||
self.driver.delete_volume(TEST_CLONE)
|
||||
self.driver.delete_volume(TEST_VOLUME)
|
||||
|
||||
def test_extend_volume(self):
|
||||
model_info = self.driver.create_volume(TEST_VOLUME)
|
||||
self.assertEqual(FAKE_MODEL_INFO1, model_info)
|
||||
|
||||
self.driver.extend_volume(TEST_VOLUME, 10)
|
||||
|
||||
|
||||
class FJISCSIDriverTestCase(test.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FJISCSIDriverTestCase, self).__init__(*args, **kwargs)
|
||||
|
@ -705,7 +705,10 @@ class FJDXCommon(object):
|
||||
mapdata['target_discovered'] = True
|
||||
mapdata['volume_id'] = volume['id']
|
||||
|
||||
if self.protocol == 'iSCSI':
|
||||
if self.protocol == 'fc':
|
||||
device_info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': mapdata}
|
||||
elif self.protocol == 'iSCSI':
|
||||
device_info = {'driver_volume_type': 'iscsi',
|
||||
'data': mapdata}
|
||||
|
||||
@ -726,6 +729,37 @@ class FJDXCommon(object):
|
||||
LOG.debug('terminate_connection, map_exist: %s.', map_exist)
|
||||
return map_exist
|
||||
|
||||
def build_fc_init_tgt_map(self, connector, target_wwn=None):
|
||||
"""Build parameter for Zone Manager"""
|
||||
LOG.debug('build_fc_init_tgt_map, target_wwn: %s.', target_wwn)
|
||||
|
||||
initiatorlist = self._find_initiator_names(connector)
|
||||
|
||||
if target_wwn is None:
|
||||
target_wwn = []
|
||||
target_portlist = self._get_target_port()
|
||||
for target_port in target_portlist:
|
||||
target_wwn.append(target_port['Name'])
|
||||
|
||||
init_tgt_map = {initiator: target_wwn for initiator in initiatorlist}
|
||||
|
||||
LOG.debug('build_fc_init_tgt_map, '
|
||||
'initiator target mapping: %s.', init_tgt_map)
|
||||
return init_tgt_map
|
||||
|
||||
def check_attached_volume_in_zone(self, connector):
|
||||
"""Check Attached Volume in Same FC Zone or not"""
|
||||
LOG.debug('check_attached_volume_in_zone, connector: %s.', connector)
|
||||
|
||||
aglist = self._find_affinity_group(connector)
|
||||
if not aglist:
|
||||
attached = False
|
||||
else:
|
||||
attached = True
|
||||
|
||||
LOG.debug('check_attached_volume_in_zone, attached: %s.', attached)
|
||||
return attached
|
||||
|
||||
@lockutils.synchronized('ETERNUS-vol', 'cinder-', True)
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend volume on ETERNUS."""
|
||||
@ -836,6 +870,8 @@ class FJDXCommon(object):
|
||||
'eternus_pool': eternus_pool,
|
||||
'pooltype': POOL_TYPE_dic[pooltype]})
|
||||
|
||||
return eternus_pool
|
||||
|
||||
@lockutils.synchronized('ETERNUS-update', 'cinder-', True)
|
||||
def update_volume_stats(self):
|
||||
"""get pool capacity."""
|
||||
@ -890,13 +926,67 @@ class FJDXCommon(object):
|
||||
if not aglist:
|
||||
LOG.debug('_get_mapdata, ag_list:%s.', aglist)
|
||||
else:
|
||||
if self.protocol == 'iSCSI':
|
||||
if self.protocol == 'fc':
|
||||
mapdata = self._get_mapdata_fc(aglist, vol_instance,
|
||||
target_portlist)
|
||||
elif self.protocol == 'iSCSI':
|
||||
mapdata = self._get_mapdata_iscsi(aglist, vol_instance,
|
||||
multipath)
|
||||
|
||||
LOG.debug('_get_mapdata, mapdata: %s.', mapdata)
|
||||
return mapdata
|
||||
|
||||
def _get_mapdata_fc(self, aglist, vol_instance, target_portlist):
|
||||
"""_get_mapdata for FibreChannel."""
|
||||
target_wwn = []
|
||||
|
||||
try:
|
||||
ag_volmaplist = self._reference_eternus_names(
|
||||
aglist[0],
|
||||
ResultClass='CIM_ProtocolControllerForUnit')
|
||||
vo_volmaplist = self._reference_eternus_names(
|
||||
vol_instance.path,
|
||||
ResultClass='CIM_ProtocolControllerForUnit')
|
||||
except pywbem.CIM_Error:
|
||||
msg = (_('_get_mapdata_fc, '
|
||||
'getting host-affinity from aglist/vol_instance failed, '
|
||||
'affinitygroup: %(ag)s, '
|
||||
'ReferenceNames, '
|
||||
'cannot connect to ETERNUS.')
|
||||
% {'ag': aglist[0]})
|
||||
LOG.exception(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
volmap = None
|
||||
for vo_volmap in vo_volmaplist:
|
||||
if vo_volmap in ag_volmaplist:
|
||||
volmap = vo_volmap
|
||||
break
|
||||
|
||||
try:
|
||||
volmapinstance = self._get_eternus_instance(
|
||||
volmap,
|
||||
LocalOnly=False)
|
||||
except pywbem.CIM_Error:
|
||||
msg = (_('_get_mapdata_fc, '
|
||||
'getting host-affinity instance failed, '
|
||||
'volmap: %(volmap)s, '
|
||||
'GetInstance, '
|
||||
'cannot connect to ETERNUS.')
|
||||
% {'volmap': volmap})
|
||||
LOG.exception(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
target_lun = int(volmapinstance['DeviceNumber'], 16)
|
||||
|
||||
for target_port in target_portlist:
|
||||
target_wwn.append(target_port['Name'])
|
||||
|
||||
mapdata = {'target_wwn': target_wwn,
|
||||
'target_lun': target_lun}
|
||||
LOG.debug('_get_mapdata_fc, mapdata: %s.', mapdata)
|
||||
return mapdata
|
||||
|
||||
def _get_mapdata_iscsi(self, aglist, vol_instance, multipath):
|
||||
"""_get_mapdata for iSCSI."""
|
||||
target_portals = []
|
||||
@ -1502,7 +1592,10 @@ class FJDXCommon(object):
|
||||
LOG.debug('_get_target_port, protocol: %s.', self.protocol)
|
||||
|
||||
target_portlist = []
|
||||
if self.protocol == 'iSCSI':
|
||||
if self.protocol == 'fc':
|
||||
prtcl_endpoint = 'FUJITSU_SCSIProtocolEndpoint'
|
||||
connection_type = 2
|
||||
elif self.protocol == 'iSCSI':
|
||||
prtcl_endpoint = 'FUJITSU_iSCSIProtocolEndpoint'
|
||||
connection_type = 7
|
||||
|
||||
@ -1673,7 +1766,11 @@ class FJDXCommon(object):
|
||||
|
||||
initiatornamelist = []
|
||||
|
||||
if self.protocol == 'iSCSI' and connector['initiator']:
|
||||
if self.protocol == 'fc' and connector['wwpns']:
|
||||
LOG.debug('_find_initiator_names, wwpns: %s.',
|
||||
connector['wwpns'])
|
||||
initiatornamelist = connector['wwpns']
|
||||
elif self.protocol == 'iSCSI' and connector['initiator']:
|
||||
LOG.debug('_find_initiator_names, initiator: %s.',
|
||||
connector['initiator'])
|
||||
initiatornamelist.append(connector['initiator'])
|
||||
|
214
cinder/volume/drivers/fujitsu/eternus_dx_fc.py
Normal file
214
cinder/volume/drivers/fujitsu/eternus_dx_fc.py
Normal file
@ -0,0 +1,214 @@
|
||||
# Copyright (c) 2015 FUJITSU LIMITED
|
||||
# Copyright (c) 2012 EMC Corporation.
|
||||
# Copyright (c) 2012 OpenStack Foundation
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""
|
||||
FibreChannel Cinder Volume driver for Fujitsu ETERNUS DX S3 series.
|
||||
"""
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.fujitsu import eternus_dx_common
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FJDXFCDriver(driver.FibreChannelDriver):
|
||||
"""FC Cinder Volume Driver for Fujitsu ETERNUS DX S3 series."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(FJDXFCDriver, self).__init__(*args, **kwargs)
|
||||
self.common = eternus_dx_common.FJDXCommon(
|
||||
'fc',
|
||||
configuration=self.configuration)
|
||||
self.VERSION = self.common.VERSION
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create volume."""
|
||||
LOG.debug('create_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
|
||||
location, metadata = self.common.create_volume(volume)
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_volume, info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
LOG.debug('create_volume_from_snapshot, '
|
||||
'volume id: %(vid)s, snap id: %(sid)s, enter method.',
|
||||
{'vid': volume['id'], 'sid': snapshot['id']})
|
||||
|
||||
location, metadata = (
|
||||
self.common.create_volume_from_snapshot(volume, snapshot))
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_volume_from_snapshot, '
|
||||
'info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create cloned volume."""
|
||||
LOG.debug('create_cloned_volume, '
|
||||
'target volume id: %(tid)s, '
|
||||
'source volume id: %(sid)s, enter method.',
|
||||
{'tid': volume['id'], 'sid': src_vref['id']})
|
||||
|
||||
location, metadata = (
|
||||
self.common.create_cloned_volume(volume, src_vref))
|
||||
|
||||
v_metadata = self._get_metadata(volume)
|
||||
metadata.update(v_metadata)
|
||||
|
||||
LOG.debug('create_cloned_volume, '
|
||||
'info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location),
|
||||
'metadata': metadata}
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Delete volume on ETERNUS."""
|
||||
LOG.debug('delete_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
|
||||
vol_exist = self.common.delete_volume(volume)
|
||||
|
||||
LOG.debug('delete_volume, '
|
||||
'delete: %s, exit method.', vol_exist)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
LOG.debug('create_snapshot, '
|
||||
'snap id: %(sid)s, volume id: %(vid)s, enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
location, metadata = self.common.create_snapshot(snapshot)
|
||||
|
||||
LOG.debug('create_snapshot, info: %s, exit method.', metadata)
|
||||
return {'provider_location': six.text_type(location)}
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
LOG.debug('delete_snapshot, '
|
||||
'snap id: %(sid)s, volume id: %(vid)s, enter method.',
|
||||
{'sid': snapshot['id'], 'vid': snapshot['volume_id']})
|
||||
|
||||
vol_exist = self.common.delete_snapshot(snapshot)
|
||||
|
||||
LOG.debug('delete_snapshot, '
|
||||
'delete: %s, exit method.', vol_exist)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
return
|
||||
|
||||
def create_export(self, context, volume, connector):
|
||||
"""Driver entry point to get the export info for a new volume."""
|
||||
return
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Driver entry point to remove an export for a volume."""
|
||||
return
|
||||
|
||||
@fczm_utils.AddFCZone
|
||||
def initialize_connection(self, volume, connector):
|
||||
"""Allow connection to connector and return connection info."""
|
||||
LOG.debug('initialize_connection, volume id: %(vid)s, '
|
||||
'wwpns: %(wwpns)s, enter method.',
|
||||
{'vid': volume['id'], 'wwpns': connector['wwpns']})
|
||||
|
||||
info = self.common.initialize_connection(volume, connector)
|
||||
|
||||
data = info['data']
|
||||
init_tgt_map = (
|
||||
self.common.build_fc_init_tgt_map(connector, data['target_wwn']))
|
||||
data['initiator_target_map'] = init_tgt_map
|
||||
|
||||
info['data'] = data
|
||||
LOG.debug('initialize_connection, '
|
||||
'info: %s, exit method.', info)
|
||||
return info
|
||||
|
||||
@fczm_utils.RemoveFCZone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
LOG.debug('terminate_connection, volume id: %(vid)s, '
|
||||
'wwpns: %(wwpns)s, enter method.',
|
||||
{'vid': volume['id'], 'wwpns': connector['wwpns']})
|
||||
|
||||
map_exist = self.common.terminate_connection(volume, connector)
|
||||
attached = self.common.check_attached_volume_in_zone(connector)
|
||||
|
||||
info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {}}
|
||||
|
||||
if not attached:
|
||||
# No more volumes attached to the host
|
||||
init_tgt_map = self.common.build_fc_init_tgt_map(connector)
|
||||
info['data'] = {'initiator_target_map': init_tgt_map}
|
||||
|
||||
LOG.debug('terminate_connection, unmap: %(unmap)s, '
|
||||
'connection info: %(info)s, exit method',
|
||||
{'unmap': map_exist, 'info': info})
|
||||
return info
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats."""
|
||||
LOG.debug('get_volume_stats, refresh: %s, enter method.', refresh)
|
||||
|
||||
pool_name = None
|
||||
if refresh is True:
|
||||
data, pool_name = self.common.update_volume_stats()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
data['volume_backend_name'] = backend_name or 'FJDXFCDriver'
|
||||
data['storage_protocol'] = 'FC'
|
||||
self._stats = data
|
||||
|
||||
LOG.debug('get_volume_stats, '
|
||||
'pool name: %s, exit method.', pool_name)
|
||||
return self._stats
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend volume."""
|
||||
LOG.debug('extend_volume, '
|
||||
'volume id: %s, enter method.', volume['id'])
|
||||
|
||||
used_pool_name = self.common.extend_volume(volume, new_size)
|
||||
|
||||
LOG.debug('extend_volume, '
|
||||
'used pool name: %s, exit method.', used_pool_name)
|
||||
|
||||
def _get_metadata(self, volume):
|
||||
v_metadata = volume.get('volume_metadata')
|
||||
if v_metadata:
|
||||
ret = {data['key']: data['value'] for data in v_metadata}
|
||||
else:
|
||||
ret = volume.get('metadata', {})
|
||||
|
||||
return ret
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added backend driver for Fujitsu ETERNUS DX (FC).
|
Loading…
Reference in New Issue
Block a user