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'
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)
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}
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."""
'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."""
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 = []
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
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'])
--- /dev/null
+# 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
--- /dev/null
+---
+features:
+ - Added backend driver for Fujitsu ETERNUS DX (FC).