Skip to content

Commit 1ed02d5

Browse files
committed
Detach disks using alias when possible
This makes us attempt to first look up a disk device by alias using the volume_uuid, before falling back to the old method of using the guest target device name. Related to blueprint libvirt-dev-alias Change-Id: I1dfe4ad3df81bc810835af9b09cfc6c06e9a5388
1 parent 9a62959 commit 1ed02d5

File tree

4 files changed

+146
-8
lines changed

4 files changed

+146
-8
lines changed

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9929,17 +9929,21 @@ def test_attach_volume_with_vir_domain_affect_live_flag(self,
99299929
@mock.patch('threading.Event', new=mock.Mock())
99309930
@mock.patch('nova.virt.libvirt.host.Host._get_domain')
99319931
def test_detach_volume_with_vir_domain_affect_live_flag(self,
9932-
mock_get_domain):
9932+
mock_get_domain, use_alias=True):
99339933
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
99349934
instance = objects.Instance(**self.test_instance)
9935+
volume_id = uuids.volume
9936+
alias_xml = '<alias name="%s"/>' % vconfig.make_libvirt_device_alias(
9937+
volume_id)
99359938
mock_xml_with_disk = """<domain>
99369939
<devices>
99379940
<disk type='file'>
9941+
%(alias)s
99389942
<source file='/path/to/fake-volume'/>
99399943
<target dev='vdc' bus='virtio'/>
99409944
</disk>
99419945
</devices>
9942-
</domain>"""
9946+
</domain>""" % {'alias': use_alias and alias_xml or ''}
99439947
mock_xml_without_disk = """<domain>
99449948
<devices>
99459949
</devices>
@@ -9951,14 +9955,26 @@ def test_detach_volume_with_vir_domain_affect_live_flag(self,
99519955
mock_xml_with_disk, # persistent domain
99529956
mock_xml_with_disk, # live domain
99539957
mock_xml_without_disk, # persistent gone
9954-
mock_xml_without_disk # live gone
9958+
mock_xml_without_disk, # persistent gone (no-alias retry)
9959+
mock_xml_without_disk, # live gone
9960+
mock_xml_without_disk, # live gone (no-alias retry)
99559961
]
9962+
if not use_alias:
9963+
# If we're not using the alias to detach we will end up with two
9964+
# extra checks of the XML with the alias (which will fail to find
9965+
# anything) before we fall back to by-path and succeed.
9966+
return_list = [
9967+
mock_xml_with_disk, # persistent check for missing alias
9968+
mock_xml_with_disk, # live check for missing alias
9969+
] + return_list
9970+
99569971
# Doubling the size of return list because we test with two guest power
99579972
# states
99589973
mock_dom.XMLDesc.side_effect = return_list + return_list
99599974

99609975
connection_info = {"driver_volume_type": "fake",
99619976
"data": {"device_path": "/fake",
9977+
"volume_id": volume_id,
99629978
"access_mode": "rw"}}
99639979

99649980
with mock.patch.object(drvr, '_disconnect_volume') as \
@@ -9972,9 +9988,10 @@ def test_detach_volume_with_vir_domain_affect_live_flag(self,
99729988

99739989
mock_get_domain.assert_called_with(instance)
99749990
xml = """<disk type="file" device="disk">
9991+
%(alias)s
99759992
<source file="/path/to/fake-volume"/>
99769993
<target bus="virtio" dev="vdc"/>
9977-
</disk>"""
9994+
</disk>""" % {'alias': use_alias and alias_xml or ''}
99789995
# we expect two separate detach calls
99799996
self.assertEqual(2, mock_dom.detachDeviceFlags.call_count)
99809997
# one for the persistent domain
@@ -9993,6 +10010,10 @@ def test_detach_volume_with_vir_domain_affect_live_flag(self,
999310010
mock_disconnect_volume.assert_called_with(
999410011
self.context, connection_info, instance, encryption=None)
999510012

10013+
def test_detach_volume_with_vir_domain_affect_live_flag_legacy(self):
10014+
self.test_detach_volume_with_vir_domain_affect_live_flag(
10015+
use_alias=False)
10016+
999610017
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
999710018
@mock.patch('nova.virt.libvirt.host.Host._get_domain')
999810019
def test_detach_volume_disk_not_found(self, mock_get_domain,
@@ -10090,7 +10111,7 @@ def test_detach_volume_order_with_encryptors(self, mock_get_guest,
1009010111
mock.call.detach_volume(
1009110112
mock_guest,
1009210113
instance.uuid,
10093-
# it is functools.partial(mock_guest.get_disk, 'vdc')
10114+
# it is functools.partial(driver._get_guest_disk_device, 'vdc')
1009410115
# see assert below
1009510116
mock.ANY,
1009610117
device_name='vdc',
@@ -10103,8 +10124,10 @@ def test_detach_volume_order_with_encryptors(self, mock_get_guest,
1010310124
)
1010410125
])
1010510126
get_device_conf_func = mock_detach_with_retry.mock_calls[0][1][2]
10106-
self.assertEqual(mock_guest.get_disk, get_device_conf_func.func)
10107-
self.assertEqual(('vdc',), get_device_conf_func.args)
10127+
self.assertEqual(drvr._get_guest_disk_device,
10128+
get_device_conf_func.func)
10129+
self.assertEqual((mock_get_guest.return_value, 'vdc'),
10130+
get_device_conf_func.args)
1010810131

1010910132
def test_extend_volume(self):
1011010133
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)

nova/tests/unit/virt/libvirt/test_guest.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,84 @@ def test_get_config(self):
260260
self.assertEqual('kvm', result.virt_type)
261261
self.assertEqual('fake', result.name)
262262

263+
def test_get_device_by_alias(self):
264+
xml = """
265+
<domain type='qemu'>
266+
<name>QEMUGuest1</name>
267+
<uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
268+
<memory unit='KiB'>219136</memory>
269+
<currentMemory unit='KiB'>219136</currentMemory>
270+
<vcpu placement='static'>1</vcpu>
271+
<os>
272+
<type arch='i686' machine='pc'>hvm</type>
273+
<boot dev='hd'/>
274+
</os>
275+
<clock offset='utc'/>
276+
<on_poweroff>destroy</on_poweroff>
277+
<on_reboot>restart</on_reboot>
278+
<on_crash>destroy</on_crash>
279+
<devices>
280+
<emulator>/usr/bin/qemu</emulator>
281+
<disk type='block' device='disk'>
282+
<alias name="qemu-disk1"/>
283+
<driver name='qemu' type='raw'/>
284+
<source dev='/dev/HostVG/QEMUGuest2'/>
285+
<target dev='hda' bus='ide'/>
286+
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
287+
</disk>
288+
<disk type='network' device='disk'>
289+
<alias name="ua-netdisk"/>
290+
<driver name='qemu' type='raw'/>
291+
<auth username='myname'>
292+
<secret type='iscsi' usage='mycluster_myname'/>
293+
</auth>
294+
<source protocol='iscsi' name='iqn.1992-01.com.example'>
295+
<host name='example.org' port='6000'/>
296+
</source>
297+
<target dev='vda' bus='virtio'/>
298+
</disk>
299+
<disk type='network' device='disk'>
300+
<driver name='qemu' type='raw'/>
301+
<source protocol='iscsi' name='iqn.1992-01.com.example/1'>
302+
<host name='example.org' port='6000'/>
303+
</source>
304+
<target dev='vdb' bus='virtio'/>
305+
</disk>
306+
<hostdev mode='subsystem' type='pci' managed='yes'>
307+
<source>
308+
<address domain='0x0000' bus='0x06' slot='0x12' function='0x5'/>
309+
</source>
310+
</hostdev>
311+
<hostdev mode='subsystem' type='pci' managed='yes'>
312+
<source>
313+
<address domain='0x0000' bus='0x06' slot='0x12' function='0x6'/>
314+
</source>
315+
</hostdev>
316+
<interface type="bridge">
317+
<mac address="fa:16:3e:f9:af:ae"/>
318+
<model type="virtio"/>
319+
<driver name="qemu"/>
320+
<source bridge="qbr84008d03-11"/>
321+
<target dev="tap84008d03-11"/>
322+
</interface>
323+
<controller type='usb' index='0'/>
324+
<controller type='pci' index='0' model='pci-root'/>
325+
<memballoon model='none'/>
326+
</devices>
327+
</domain>
328+
"""
329+
self.domain.XMLDesc.return_value = xml
330+
331+
dev = self.guest.get_device_by_alias('qemu-disk1')
332+
self.assertIsInstance(dev, vconfig.LibvirtConfigGuestDisk)
333+
self.assertEqual('hda', dev.target_dev)
334+
335+
dev = self.guest.get_device_by_alias('ua-netdisk')
336+
self.assertIsInstance(dev, vconfig.LibvirtConfigGuestDisk)
337+
self.assertEqual('vda', dev.target_dev)
338+
339+
self.assertIsNone(self.guest.get_device_by_alias('nope'))
340+
263341
def test_get_devices(self):
264342
xml = """
265343
<domain type='qemu'>

nova/virt/libvirt/driver.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,33 @@ def _detach_sync(
27192719
'instance %s: %s', device_name, instance_uuid, str(ex))
27202720
raise
27212721

2722+
def _get_guest_disk_device(self, guest, disk_dev, volume_uuid=None,
2723+
from_persistent_config=False):
2724+
"""Attempt to find the guest disk
2725+
2726+
If a volume_uuid is provided, we will look for the device based
2727+
on the nova-specified alias. If not, or we do not find it that way,
2728+
fall back to the old way of using the disk_dev.
2729+
"""
2730+
if volume_uuid is not None:
2731+
dev_alias = vconfig.make_libvirt_device_alias(volume_uuid)
2732+
dev = guest.get_device_by_alias(
2733+
dev_alias,
2734+
from_persistent_config=from_persistent_config)
2735+
if dev:
2736+
LOG.debug('Found disk %s by alias %s', disk_dev, dev_alias)
2737+
return dev
2738+
dev = guest.get_disk(disk_dev,
2739+
from_persistent_config=from_persistent_config)
2740+
if dev:
2741+
# NOTE(danms): Only log that we fell back to the old way if it
2742+
# worked. Since we call this method after detach is done to
2743+
# ensure it is gone, we will always "fall back" to make sure it
2744+
# is gone by the "old way" and thus shouldn't announce it.
2745+
LOG.info('Device %s not found by alias %s, falling back',
2746+
disk_dev, dev_alias)
2747+
return dev
2748+
27222749
def detach_volume(self, context, connection_info, instance, mountpoint,
27232750
encryption=None):
27242751
disk_dev = mountpoint.rpartition("/")[2]
@@ -2729,7 +2756,11 @@ def detach_volume(self, context, connection_info, instance, mountpoint,
27292756
# detaching any attached encryptors or disconnecting the underlying
27302757
# volume in _disconnect_volume. Otherwise, the encryptor or volume
27312758
# driver may report that the volume is still in use.
2732-
get_dev = functools.partial(guest.get_disk, disk_dev)
2759+
volume_id = driver_block_device.get_volume_id(connection_info)
2760+
get_dev = functools.partial(self._get_guest_disk_device,
2761+
guest,
2762+
disk_dev,
2763+
volume_uuid=volume_id)
27332764
self._detach_with_retry(
27342765
guest,
27352766
instance.uuid,

nova/virt/libvirt/guest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ def get_all_disks(self):
414414

415415
return self.get_all_devices(vconfig.LibvirtConfigGuestDisk)
416416

417+
def get_device_by_alias(self, devalias, devtype=None,
418+
from_persistent_config=False):
419+
for dev in self.get_all_devices(devtype):
420+
if dev.alias == devalias:
421+
return dev
422+
417423
def get_all_devices(
418424
self,
419425
devtype: vconfig.LibvirtConfigGuestDevice = None,

0 commit comments

Comments
 (0)