DRIVER_PATH = "cinder.volume.drivers.pure"
BASE_DRIVER_OBJ = DRIVER_PATH + ".PureBaseVolumeDriver"
ISCSI_DRIVER_OBJ = DRIVER_PATH + ".PureISCSIDriver"
+FC_DRIVER_OBJ = DRIVER_PATH + ".PureFCDriver"
ARRAY_OBJ = DRIVER_PATH + ".FlashArray"
TARGET = "pure-target"
API_TOKEN = "12345678-abcd-1234-abcd-1234567890ab"
VOLUME_BACKEND_NAME = "Pure_iSCSI"
-PORT_NAMES = ["ct0.eth2", "ct0.eth3", "ct1.eth2", "ct1.eth3"]
-ISCSI_IPS = ["10.0.0." + str(i + 1) for i in range(len(PORT_NAMES))]
+ISCSI_PORT_NAMES = ["ct0.eth2", "ct0.eth3", "ct1.eth2", "ct1.eth3"]
+FC_PORT_NAMES = ["ct0.fc2", "ct0.fc3", "ct1.fc2", "ct1.fc3"]
+ISCSI_IPS = ["10.0.0." + str(i + 1) for i in range(len(ISCSI_PORT_NAMES))]
+FC_WWNS = ["21000024ff59fe9" + str(i + 1) for i in range(len(FC_PORT_NAMES))]
HOSTNAME = "computenode1"
PURE_HOST_NAME = pure.PureBaseVolumeDriver._generate_purity_host_name(HOSTNAME)
PURE_HOST = {
SNAPSHOT_WITH_CGROUP['cgsnapshot_id'] = \
"4a2f7e3a-312a-40c5-96a8-536b8a0fe075"
INITIATOR_IQN = "iqn.1993-08.org.debian:01:222"
-CONNECTOR = {"initiator": INITIATOR_IQN, "host": HOSTNAME}
+INITIATOR_WWN = "5001500150015081"
+ISCSI_CONNECTOR = {"initiator": INITIATOR_IQN, "host": HOSTNAME}
+FC_CONNECTOR = {"wwpns": {INITIATOR_WWN}, "host": HOSTNAME}
TARGET_IQN = "iqn.2010-06.com.purestorage:flasharray.12345abc"
+TARGET_WWN = "21000024ff59fe94"
TARGET_PORT = "3260"
+INITIATOR_TARGET_MAP =\
+ {
+ '5001500150015081': ['21000024ff59fe93',
+ '21000024ff59fe92',
+ '21000024ff59fe91',
+ '21000024ff59fe94'],
+ }
+DEVICE_MAPPING =\
+ {
+ "fabric": {'initiator_port_wwn_list': {INITIATOR_WWN},
+ 'target_port_wwn_list': FC_WWNS
+ },
+ }
+
ISCSI_PORTS = [{"name": name,
"iqn": TARGET_IQN,
"portal": ip + ":" + TARGET_PORT,
"wwn": None,
- } for name, ip in zip(PORT_NAMES, ISCSI_IPS)]
+ } for name, ip in zip(ISCSI_PORT_NAMES, ISCSI_IPS)]
+FC_PORTS = [{"name": name,
+ "iqn": None,
+ "portal": None,
+ "wwn": wwn,
+ } for name, wwn in zip(FC_PORT_NAMES, FC_WWNS)]
NON_ISCSI_PORT = {
"name": "ct0.fc1",
"iqn": None,
"total": 0,
}
-CONNECTION_INFO = {
+ISCSI_CONNECTION_INFO = {
"driver_volume_type": "iscsi",
"data": {
"target_iqn": TARGET_IQN,
"access_mode": "rw",
},
}
+FC_CONNECTION_INFO = {
+ "driver_volume_type": "fibre_channel",
+ "data": {
+ "target_wwn": FC_WWNS,
+ "target_lun": 1,
+ "target_discovered": True,
+ "access_mode": "rw",
+ "initiator_target_map": INITIATOR_TARGET_MAP,
+ },
+}
class FakePureStorageHTTPError(Exception):
vol_name = VOLUME["name"] + "-cinder"
mock_host.return_value = {"name": "some-host"}
# Branch with manually created host
- self.driver.terminate_connection(VOLUME, CONNECTOR)
+ self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with("some-host", vol_name)
self.assertFalse(self.array.list_host_connections.called)
self.assertFalse(self.array.delete_host.called)
self.array.list_host_connections.return_value = []
mock_host.return_value = PURE_HOST.copy()
mock_host.return_value.update(hgroup="some-group")
- self.driver.terminate_connection(VOLUME, CONNECTOR)
+ self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.assertTrue(self.array.list_host_connections.called)
self.assertTrue(self.array.delete_host.called)
self.array.list_host_connections.return_value = [
{"lun": 2, "name": PURE_HOST_NAME, "vol": "some-vol"}]
mock_host.return_value = PURE_HOST
- self.driver.terminate_connection(VOLUME, CONNECTOR)
+ self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
# Branch where host gets deleted
self.array.reset_mock()
self.array.list_host_connections.return_value = []
- self.driver.terminate_connection(VOLUME, CONNECTOR)
+ self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
self.array.reset_mock()
self.array.disconnect_host.side_effect = \
self.purestorage_module.PureHTTPError(code=400, text="reason")
- self.driver.terminate_connection(VOLUME, CONNECTOR)
+ self.driver.terminate_connection(VOLUME, ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.array.list_host_connections.assert_called_with(PURE_HOST_NAME,
private=True)
text="Some other error"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
- self.driver.terminate_connection, VOLUME, CONNECTOR)
+ self.driver.terminate_connection,
+ VOLUME,
+ ISCSI_CONNECTOR)
self.array.disconnect_host.assert_called_with(PURE_HOST_NAME, vol_name)
self.assertFalse(self.array.list_host_connections.called)
self.assertFalse(self.array.delete_host.called)
good_host.update(iqn=["another-wrong-iqn", INITIATOR_IQN])
bad_host = {"name": "bad-host", "iqn": ["wrong-iqn"]}
self.array.list_hosts.return_value = [bad_host]
- real_result = self.driver._get_host(CONNECTOR)
- self.assertIs(real_result, None)
+ real_result = self.driver._get_host(ISCSI_CONNECTOR)
+ self.assertIs(None, real_result)
self.array.list_hosts.return_value.append(good_host)
- real_result = self.driver._get_host(CONNECTOR)
+ real_result = self.driver._get_host(ISCSI_CONNECTOR)
self.assertEqual(good_host, real_result)
self.assert_error_propagates([self.array.list_hosts],
- self.driver._get_host, CONNECTOR)
+ self.driver._get_host, ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._connect")
@mock.patch(ISCSI_DRIVER_OBJ + "._get_target_iscsi_port")
"vol": VOLUME["name"] + "-cinder",
"lun": 1,
}
- result = CONNECTION_INFO
- real_result = self.driver.initialize_connection(VOLUME, CONNECTOR)
+ result = ISCSI_CONNECTION_INFO
+ real_result = self.driver.initialize_connection(VOLUME,
+ ISCSI_CONNECTOR)
self.assertDictMatch(result, real_result)
mock_get_iscsi_port.assert_called_with()
- mock_connection.assert_called_with(VOLUME, CONNECTOR, None)
+ mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assert_error_propagates([mock_get_iscsi_port, mock_connection],
self.driver.initialize_connection,
- VOLUME, CONNECTOR)
+ VOLUME, ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._connect")
@mock.patch(ISCSI_DRIVER_OBJ + "._get_target_iscsi_port")
def test_initialize_connection_with_auth(self, mock_get_iscsi_port,
mock_connection):
auth_type = "CHAP"
- chap_username = CONNECTOR["host"]
+ chap_username = ISCSI_CONNECTOR["host"]
chap_password = "password"
mock_get_iscsi_port.return_value = ISCSI_PORTS[0]
initiator_update = [{"key": pure.CHAP_SECRET_KEY,
"auth_username": chap_username,
"auth_password": chap_password,
}
- result = CONNECTION_INFO.copy()
+ result = ISCSI_CONNECTION_INFO.copy()
result["data"]["auth_method"] = auth_type
result["data"]["auth_username"] = chap_username
result["data"]["auth_password"] = chap_password
# Branch where no credentials were generated
real_result = self.driver.initialize_connection(VOLUME,
- CONNECTOR)
- mock_connection.assert_called_with(VOLUME, CONNECTOR, None)
+ ISCSI_CONNECTOR)
+ mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assertDictMatch(result, real_result)
# Branch where new credentials were generated
mock_connection.return_value["initiator_update"] = initiator_update
result["initiator_update"] = initiator_update
real_result = self.driver.initialize_connection(VOLUME,
- CONNECTOR)
- mock_connection.assert_called_with(VOLUME, CONNECTOR, None)
+ ISCSI_CONNECTOR)
+ mock_connection.assert_called_with(VOLUME, ISCSI_CONNECTOR, None)
self.assertDictMatch(result, real_result)
self.assert_error_propagates([mock_get_iscsi_port, mock_connection],
self.driver.initialize_connection,
- VOLUME, CONNECTOR)
+ VOLUME, ISCSI_CONNECTOR)
@mock.patch(ISCSI_DRIVER_OBJ + "._choose_target_iscsi_port")
@mock.patch(ISCSI_DRIVER_OBJ + "._run_iscsiadm_bare")
# Branch where host already exists
mock_host.return_value = PURE_HOST
self.array.connect_host.return_value = {"vol": vol_name, "lun": 1}
- real_result = self.driver._connect(VOLUME, CONNECTOR, None)
+ real_result = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
self.assertEqual(result, real_result)
- mock_host.assert_called_with(self.driver, CONNECTOR)
+ mock_host.assert_called_with(self.driver, ISCSI_CONNECTOR)
self.assertFalse(mock_generate.called)
self.assertFalse(self.array.create_host.called)
self.array.connect_host.assert_called_with(PURE_HOST_NAME, vol_name)
# Branch where new host is created
mock_host.return_value = None
mock_generate.return_value = PURE_HOST_NAME
- real_result = self.driver._connect(VOLUME, CONNECTOR, None)
- mock_host.assert_called_with(self.driver, CONNECTOR)
+ real_result = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
+ mock_host.assert_called_with(self.driver, ISCSI_CONNECTOR)
mock_generate.assert_called_with(HOSTNAME)
self.array.create_host.assert_called_with(PURE_HOST_NAME,
iqnlist=[INITIATOR_IQN])
self.assert_error_propagates(
[mock_host, mock_generate, self.array.connect_host,
self.array.create_host],
- self.driver._connect, VOLUME, CONNECTOR, None)
+ self.driver._connect, VOLUME, ISCSI_CONNECTOR, None)
self.mock_config.use_chap_auth = True
- chap_user = CONNECTOR["host"]
+ chap_user = ISCSI_CONNECTOR["host"]
chap_password = "sOmEseCr3t"
# Branch where chap is used and credentials already exist
initiator_data = [{"key": pure.CHAP_SECRET_KEY,
"value": chap_password}]
- self.driver._connect(VOLUME, CONNECTOR, initiator_data)
+ self.driver._connect(VOLUME, ISCSI_CONNECTOR, initiator_data)
result["auth_username"] = chap_user
result["auth_password"] = chap_password
self.assertDictMatch(result, real_result)
# Branch where chap is used and credentials are generated
mock_gen_secret.return_value = chap_password
- self.driver._connect(VOLUME, CONNECTOR, None)
+ self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
result["auth_username"] = chap_user
result["auth_password"] = chap_password
result["initiator_update"] = {
code=400,
text="Connection already exists"
)
- actual = self.driver._connect(VOLUME, CONNECTOR, None)
+ actual = self.driver._connect(VOLUME, ISCSI_CONNECTOR, None)
self.assertEqual(expected, actual)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
text="Connection already exists"
)
self.assertRaises(exception.PureDriverException, self.driver._connect,
- VOLUME, CONNECTOR, None)
+ VOLUME, ISCSI_CONNECTOR, None)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
text="Connection already exists"
)
self.assertRaises(self.purestorage_module.PureHTTPError,
- self.driver._connect, VOLUME, CONNECTOR, None)
+ self.driver._connect, VOLUME, ISCSI_CONNECTOR, None)
+ self.assertTrue(self.array.connect_host.called)
+ self.assertTrue(self.array.list_volume_private_connections)
+
+
+class PureFCDriverTestCase(PureDriverTestCase):
+
+ def setUp(self):
+ super(PureFCDriverTestCase, self).setUp()
+ self.driver = pure.PureFCDriver(configuration=self.mock_config)
+ self.driver._array = self.array
+ self.driver._lookup_service = mock.Mock()
+
+ def test_do_setup(self):
+ self.purestorage_module.FlashArray.return_value = self.array
+ self.array.get_rest_version.return_value = \
+ self.driver.SUPPORTED_REST_API_VERSIONS[0]
+ self.driver.do_setup(None)
+ self.purestorage_module.FlashArray.assert_called_with(
+ TARGET,
+ api_token=API_TOKEN
+ )
+ self.assertEqual(self.array, self.driver._array)
+ self.assertEqual(
+ self.driver.SUPPORTED_REST_API_VERSIONS,
+ self.purestorage_module.FlashArray.supported_rest_versions
+ )
+
+ def test_get_host(self):
+ good_host = PURE_HOST.copy()
+ good_host.update(wwn=["another-wrong-wwn", INITIATOR_WWN])
+ bad_host = {"name": "bad-host", "wwn": ["wrong-wwn"]}
+ self.array.list_hosts.return_value = [bad_host]
+ actual_result = self.driver._get_host(FC_CONNECTOR)
+ self.assertIs(None, actual_result)
+ self.array.list_hosts.return_value.append(good_host)
+ actual_result = self.driver._get_host(FC_CONNECTOR)
+ self.assertEqual(good_host, actual_result)
+ self.assert_error_propagates([self.array.list_hosts],
+ self.driver._get_host, FC_CONNECTOR)
+
+ @mock.patch(FC_DRIVER_OBJ + "._connect")
+ def test_initialize_connection(self, mock_connection):
+ lookup_service = self.driver._lookup_service
+ (lookup_service.get_device_mapping_from_network.
+ return_value) = DEVICE_MAPPING
+ mock_connection.return_value = {"vol": VOLUME["name"] + "-cinder",
+ "lun": 1,
+ }
+ self.array.list_ports.return_value = FC_PORTS
+ actual_result = self.driver.initialize_connection(VOLUME, FC_CONNECTOR)
+ self.assertDictMatch(FC_CONNECTION_INFO, actual_result)
+
+ @mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
+ @mock.patch(FC_DRIVER_OBJ + "._generate_purity_host_name", spec=True)
+ def test_connect(self, mock_generate, mock_host):
+ vol_name = VOLUME["name"] + "-cinder"
+ result = {"vol": vol_name, "lun": 1}
+
+ # Branch where host already exists
+ mock_host.return_value = PURE_HOST
+ self.array.connect_host.return_value = {"vol": vol_name, "lun": 1}
+ real_result = self.driver._connect(VOLUME, FC_CONNECTOR, None)
+ self.assertEqual(result, real_result)
+ mock_host.assert_called_with(self.driver, FC_CONNECTOR)
+ self.assertFalse(mock_generate.called)
+ self.assertFalse(self.array.create_host.called)
+ self.array.connect_host.assert_called_with(PURE_HOST_NAME, vol_name)
+
+ # Branch where new host is created
+ mock_host.return_value = None
+ mock_generate.return_value = PURE_HOST_NAME
+ real_result = self.driver._connect(VOLUME, FC_CONNECTOR, None)
+ mock_host.assert_called_with(self.driver, FC_CONNECTOR)
+ mock_generate.assert_called_with(HOSTNAME)
+ self.array.create_host.assert_called_with(PURE_HOST_NAME,
+ wwnlist={INITIATOR_WWN})
+ self.assertEqual(result, real_result)
+
+ mock_generate.reset_mock()
+ self.array.reset_mock()
+ self.assert_error_propagates(
+ [mock_host, mock_generate, self.array.connect_host,
+ self.array.create_host],
+ self.driver._connect, VOLUME, FC_CONNECTOR, None)
+
+ @mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
+ def test_connect_already_connected(self, mock_host):
+ mock_host.return_value = PURE_HOST
+ expected = {"host": PURE_HOST_NAME, "lun": 1}
+ self.array.list_volume_private_connections.return_value = \
+ [expected, {"host": "extra", "lun": 2}]
+ self.array.connect_host.side_effect = \
+ self.purestorage_module.PureHTTPError(
+ code=400,
+ text="Connection already exists"
+ )
+ actual = self.driver._connect(VOLUME, FC_CONNECTOR, None)
+ self.assertEqual(expected, actual)
+ self.assertTrue(self.array.connect_host.called)
+ self.assertTrue(self.array.list_volume_private_connections)
+
+ @mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
+ def test_connect_already_connected_list_hosts_empty(self, mock_host):
+ mock_host.return_value = PURE_HOST
+ self.array.list_volume_private_connections.return_value = {}
+ self.array.connect_host.side_effect = \
+ self.purestorage_module.PureHTTPError(
+ code=400,
+ text="Connection already exists"
+ )
+ self.assertRaises(exception.PureDriverException, self.driver._connect,
+ VOLUME, FC_CONNECTOR, None)
+ self.assertTrue(self.array.connect_host.called)
+ self.assertTrue(self.array.list_volume_private_connections)
+
+ @mock.patch(FC_DRIVER_OBJ + "._get_host", autospec=True)
+ def test_connect_already_connected_list_hosts_exception(self, mock_host):
+ mock_host.return_value = PURE_HOST
+ self.array.list_volume_private_connections.side_effect = \
+ self.purestorage_module.PureHTTPError(code=400, text="")
+ self.array.connect_host.side_effect = \
+ self.purestorage_module.PureHTTPError(
+ code=400,
+ text="Connection already exists"
+ )
+ self.assertRaises(self.purestorage_module.PureHTTPError,
+ self.driver._connect, VOLUME, FC_CONNECTOR, None)
self.assertTrue(self.array.connect_host.called)
self.assertTrue(self.array.list_volume_private_connections)
from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder import utils
+from cinder.volume import driver
from cinder.volume.drivers.san import san
from cinder.volume import utils as volume_utils
+from cinder.zonemanager import utils as fczm_utils
try:
import purestorage
"""
raise NotImplementedError
- @log_debug_trace
- def terminate_connection(self, volume, connector, **kwargs):
- """Terminate connection."""
+ def _disconnect(self, volume, connector, **kwargs):
vol_name = self._get_vol_name(volume)
host = self._get_host(connector)
if host:
host_name = host["name"]
- self._disconnect_host(host_name, vol_name)
+ result = self._disconnect_host(host_name, vol_name)
else:
LOG.error(_LE("Unable to disconnect host from volume."))
+ result = False
+
+ return result
+
+ @log_debug_trace
+ def terminate_connection(self, volume, connector, **kwargs):
+ """Terminate connection."""
+ self._disconnect(volume, connector, **kwargs)
@log_debug_trace
def _disconnect_host(self, host_name, vol_name):
+ """Return value indicates if host was deleted on array or not"""
try:
self._array.disconnect_host(host_name, vol_name)
except purestorage.PureHTTPError as err:
LOG.info(_LI("Deleting unneeded host %(host_name)r."),
{"host_name": host_name})
self._array.delete_host(host_name)
+ return True
+
+ return False
@log_debug_trace
def get_volume_stats(self, refresh=False):
connection["initiator_update"] = initiator_update
return connection
+
+
+class PureFCDriver(PureBaseVolumeDriver, driver.FibreChannelDriver):
+
+ VERSION = "1.0.0"
+
+ def __init__(self, *args, **kwargs):
+ execute = kwargs.pop("execute", utils.execute)
+ super(PureFCDriver, self).__init__(execute=execute, *args, **kwargs)
+ self._storage_protocol = "FC"
+ self._lookup_service = fczm_utils.create_lookup_service()
+
+ def do_setup(self, context):
+ super(PureFCDriver, self).do_setup(context)
+
+ def _get_host(self, connector):
+ """Return dict describing existing Purity host object or None."""
+ hosts = self._array.list_hosts()
+ for host in hosts:
+ for wwn in connector["wwpns"]:
+ if wwn in str(host["wwn"]).lower():
+ return host
+
+ def _get_array_wwns(self):
+ """Return list of wwns from the array"""
+ ports = self._array.list_ports()
+ return [port["wwn"] for port in ports if port["wwn"]]
+
+ @log_debug_trace
+ @fczm_utils.AddFCZone
+ def initialize_connection(self, volume, connector, initiator_data=None):
+ """Allow connection to connector and return connection info."""
+
+ connection = self._connect(volume, connector, initiator_data)
+ target_wwns = self._get_array_wwns()
+ init_targ_map = self._build_initiator_target_map(target_wwns,
+ connector)
+ properties = {
+ "driver_volume_type": "fibre_channel",
+ "data": {
+ 'target_discovered': True,
+ "target_lun": connection["lun"],
+ "target_wwn": target_wwns,
+ 'access_mode': 'rw',
+ 'initiator_target_map': init_targ_map,
+ }
+ }
+
+ return properties
+
+ @utils.synchronized('PureFCDriver._connect', external=True)
+ def _connect(self, volume, connector, initiator_data):
+ """Connect the host and volume; return dict describing connection."""
+ wwns = connector["wwpns"]
+
+ vol_name = self._get_vol_name(volume)
+ host = self._get_host(connector)
+
+ if host:
+ host_name = host["name"]
+ LOG.info(_LI("Re-using existing purity host %(host_name)r"),
+ {"host_name": host_name})
+ else:
+ host_name = self._generate_purity_host_name(connector["host"])
+ LOG.info(_LI("Creating host object %(host_name)r with WWN:"
+ " %(wwn)s."), {"host_name": host_name, "wwn": wwns})
+ self._array.create_host(host_name, wwnlist=wwns)
+
+ return self._connect_host_to_vol(host_name, vol_name)
+
+ def _build_initiator_target_map(self, target_wwns, connector):
+ """Build the target_wwns and the initiator target map."""
+ init_targ_map = {}
+
+ if self._lookup_service:
+ # use FC san lookup to determine which NSPs to use
+ # for the new VLUN.
+ dev_map = self._lookup_service.get_device_mapping_from_network(
+ connector['wwpns'],
+ target_wwns)
+
+ for fabric_name in dev_map:
+ fabric = dev_map[fabric_name]
+ for initiator in fabric['initiator_port_wwn_list']:
+ if initiator not in init_targ_map:
+ init_targ_map[initiator] = []
+ init_targ_map[initiator] += fabric['target_port_wwn_list']
+ init_targ_map[initiator] = list(set(
+ init_targ_map[initiator]))
+ else:
+ init_targ_map = dict.fromkeys(connector["wwpns"], target_wwns)
+
+ return init_targ_map
+
+ @log_debug_trace
+ @fczm_utils.RemoveFCZone
+ def terminate_connection(self, volume, connector, **kwargs):
+ """Terminate connection."""
+ no_more_connections = self._disconnect(volume, connector, **kwargs)
+
+ properties = {"driver_volume_type": "fibre_channel", "data": {}}
+
+ if no_more_connections:
+ target_wwns = self._get_array_wwns()
+ init_targ_map = self._build_initiator_target_map(target_wwns,
+ connector)
+ properties["data"] = {"target_wwn": target_wwns,
+ "initiator_target_map": init_targ_map}
+
+ return properties