From 981b84c49c170b4289e2a9c3df7feb245f5803bc Mon Sep 17 00:00:00 2001 From: "Walter A. Boring IV" Date: Thu, 16 Jul 2015 16:09:02 -0700 Subject: [PATCH] FC discover existing devices for removal Due to the way Nova live migration works, we can't rely on having the device path that was discovered at connect_volume time existing in the device_info. This patch uses the existing connection_properties to rediscover the existing volume paths on the system at the time disconnect_volume is called. This ensures that whatever paths/devices are associated with at volume at disconnect_volume time exists, they will be removed. Change-Id: I699fcff5dee595caa54ca7e80f67966edd1370ef Closes-Bug: 1406161 --- os_brick/initiator/connector.py | 45 +++++++++++++++------- os_brick/tests/initiator/test_connector.py | 25 ++++++++++-- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/os_brick/initiator/connector.py b/os_brick/initiator/connector.py index c93554e..052296a 100644 --- a/os_brick/initiator/connector.py +++ b/os_brick/initiator/connector.py @@ -818,6 +818,28 @@ class FibreChannelConnector(InitiatorConnector): self._linuxscsi.set_execute(execute) self._linuxfc.set_execute(execute) + def _get_possible_volume_paths(self, connection_properties, hbas): + ports = connection_properties['target_wwn'] + possible_devs = self._get_possible_devices(hbas, ports) + + lun = connection_properties.get('target_lun', 0) + host_paths = self._get_host_devices(possible_devs, lun) + return host_paths + + def _get_volume_paths(self, connection_properties): + """Get a list of existing paths for a volume on the system.""" + volume_paths = [] + # first fetch all of the potential paths that might exist + # and then filter those by what's actually on the system. + hbas = self._linuxfc.get_fc_hbas_info() + host_paths = self._get_possible_volume_paths(connection_properties, + hbas) + for path in host_paths: + if os.path.exists(path): + volume_paths.append(path) + + return volume_paths + @synchronized('connect_volume') def connect_volume(self, connection_properties): """Attach the volume to instance_name. @@ -831,11 +853,8 @@ class FibreChannelConnector(InitiatorConnector): device_info = {'type': 'block'} hbas = self._linuxfc.get_fc_hbas_info() - ports = connection_properties['target_wwn'] - possible_devs = self._get_possible_devices(hbas, ports) - - lun = connection_properties.get('target_lun', 0) - host_devices = self._get_host_devices(possible_devs, lun) + host_devices = self._get_possible_volume_paths(connection_properties, + hbas) if len(host_devices) == 0: # this is empty because we don't have any FC HBAs @@ -961,18 +980,16 @@ class FibreChannelConnector(InitiatorConnector): multipath_id = device_info['multipath_id'] mdev_info = self._linuxscsi.find_multipath_device(multipath_id) devices = mdev_info['devices'] - LOG.debug("devices to remove = %s", devices) self._linuxscsi.flush_multipath_device(multipath_id) else: - device = device_info['path'] - # now resolve this to a /dev/sdX path - if os.path.exists(device): - # get the /dev/sdX device from the - # /dev/disk/by-path entry - dev_name = os.path.realpath(device) - dev_info = self._linuxscsi.get_device_info(dev_name) - devices = [dev_info] + devices = [] + volume_paths = self._get_volume_paths(connection_properties) + for path in volume_paths: + real_path = self._linuxscsi.get_name_from_path(path) + device_info = self._linuxscsi.get_device_info(real_path) + devices.append(device_info) + LOG.debug("devices to remove = %s", devices) self._remove_devices(connection_properties, devices) def _remove_devices(self, connection_properties, devices): diff --git a/os_brick/tests/initiator/test_connector.py b/os_brick/tests/initiator/test_connector.py index aea5c4a..e0945e2 100644 --- a/os_brick/tests/initiator/test_connector.py +++ b/os_brick/tests/initiator/test_connector.py @@ -920,15 +920,34 @@ class FibreChannelConnectorTestCase(ConnectorTestCase): 'target_lun': 1, }} + @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas') + @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info') + def test_get_volume_paths(self, fake_fc_hbas_info, + fake_fc_hbas, fake_exists): + fake_fc_hbas.side_effect = self.fake_get_fc_hbas + fake_fc_hbas_info.side_effect = self.fake_get_fc_hbas_info + + name = 'volume-00000001' + vol = {'id': 1, 'name': name} + location = '10.0.2.15:3260' + wwn = '1234567890123456' + connection_info = self.fibrechan_connection(vol, location, wwn) + volume_paths = self.connector._get_volume_paths( + connection_info['data']) + + expected = ['/dev/disk/by-path/pci-0000:05:00.2' + '-fc-0x1234567890123456-lun-1'] + self.assertEqual(expected, volume_paths) + @mock.patch.object(os.path, 'exists', return_value=True) @mock.patch.object(os.path, 'realpath', return_value='/dev/sdb') @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas') @mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas_info') - @mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device') @mock.patch.object(linuxscsi.LinuxSCSI, 'remove_scsi_device') @mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_info') def test_connect_volume(self, get_device_info_mock, remove_device_mock, - find_device_mock, get_fc_hbas_info_mock, + get_fc_hbas_info_mock, get_fc_hbas_mock, realpath_mock, exists_mock): get_fc_hbas_mock.side_effect = self.fake_get_fc_hbas get_fc_hbas_info_mock.side_effect = self.fake_get_fc_hbas_info @@ -940,7 +959,6 @@ class FibreChannelConnectorTestCase(ConnectorTestCase): 'address': '1:0:0:1', 'host': 1, 'channel': 0, 'id': 0, 'lun': 1}]} - find_device_mock.return_value = devices get_device_info_mock.return_value = devices['devices'][0] location = '10.0.2.15:3260' @@ -957,6 +975,7 @@ class FibreChannelConnectorTestCase(ConnectorTestCase): exp_wwn) self.assertEqual(dev_info['type'], 'block') self.assertEqual(dev_info['path'], dev_str) + self.assertTrue('multipath_id' not in dev_info) self.assertTrue('devices' not in dev_info) self.connector.disconnect_volume(connection_info['data'], dev_info)