Add metadata for shares

This patch add device metatada to an instance if a share is is attached.
Users can get the share information (share_id and tag) from the metadata
and create automation to mount the share on the VM.

Manila is the OpenStack Shared Filesystems service.
These series of patches implement changes required in Nova to allow the shares
provided by Manila to be associated with and attached to instances using
virtiofs.

Implements: blueprint libvirt-virtiofs-attach-manila-shares

Change-Id: I4c7bb15dbe423ec2815efe9d9505b1e8f2a3c4f5
This commit is contained in:
René Ribaud
2022-07-05 16:09:58 +02:00
parent 9ea6063ea8
commit 5bc088de0d
6 changed files with 139 additions and 14 deletions
+8
View File
@@ -71,6 +71,7 @@ NEWTON_TWO = '2016-10-06'
OCATA = '2017-02-22'
ROCKY = '2018-08-27'
VICTORIA = '2020-10-14'
EPOXY = '2025-04-04'
OPENSTACK_VERSIONS = [
FOLSOM,
@@ -82,6 +83,7 @@ OPENSTACK_VERSIONS = [
OCATA,
ROCKY,
VICTORIA,
EPOXY,
]
VERSION = "version"
@@ -426,6 +428,12 @@ class InstanceMetadata(object):
device_metadata['serial'] = device.serial
if 'path' in device:
device_metadata['path'] = device.path
elif self._check_os_version(EPOXY, version) and isinstance(
device, metadata_obj.ShareMetadata
):
device_metadata['type'] = 'share'
device_metadata['share_id'] = device.share_id
device_metadata['tag'] = device.tag
else:
LOG.debug('Metadata for device of unknown type %s has not '
'been included in the '
+10
View File
@@ -111,6 +111,16 @@ class DiskMetadata(DeviceMetadata):
}
@base.NovaObjectRegistry.register
class ShareMetadata(DeviceMetadata):
VERSION = '1.0'
fields = {
'share_id': fields.StringField(nullable=True),
'tag': fields.StringField(nullable=True),
}
@base.NovaObjectRegistry.register
class InstanceDeviceMetadata(base.NovaObject):
VERSION = '1.0'
+1
View File
@@ -1170,6 +1170,7 @@ object_data = {
'ServiceList': '1.19-5325bce13eebcbf22edc9678285270cc',
'ShareMapping': '1.1-3f1f4e053d37c1ddcdd0040cee569c1f',
'ShareMappingList': '1.0-634980d5efdf3656e28c8dec3d862ab9',
'ShareMetadata': '1.0-09f69ac0bd47371417b5477a277e43af',
'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963',
'TagList': '1.1-55231bdb671ecf7641d6a2e9109b5d8e',
'TaskLog': '1.0-78b0534366f29aa3eebb01860fbe18fe',
+28 -3
View File
@@ -198,15 +198,21 @@ def fake_metadata_objects():
mac='00:00:00:00:00:00',
tags=['foo']
)
share_obj = metadata_obj.ShareMetadata(
share_id='ca3c176e-fd5a-438b-b595-e18a358f6909',
tag='my_share_tag'
)
mdlist = metadata_obj.InstanceDeviceMetadata(
instance_uuid='b65cee2f-8c69-4aeb-be2f-f79742548fc2',
devices=[nic_obj, ide_disk_obj, scsi_disk_obj, usb_disk_obj,
fake_device_obj, device_with_fake_bus_obj, nic_vlans_obj,
nic_vf_trusted_obj])
nic_vf_trusted_obj, share_obj])
return mdlist
def fake_metadata_dicts(include_vlan=False, include_vf_trusted=False):
def fake_metadata_dicts(
include_vlan=False, include_vf_trusted=False, include_shares=False
):
nic_meta = {
'type': 'nic',
'bus': 'pci',
@@ -236,6 +242,13 @@ def fake_metadata_dicts(include_vlan=False, include_vf_trusted=False):
'path': '/dev/sda',
'tags': ['baz'],
}
share_meta = {
'type': 'share',
'bus': 'none',
'address': 'none',
'share_id': 'ca3c176e-fd5a-438b-b595-e18a358f6909',
'tag': 'my_share_tag',
}
scsi_disk_meta = copy.copy(ide_disk_meta)
scsi_disk_meta['bus'] = 'scsi'
@@ -254,6 +267,8 @@ def fake_metadata_dicts(include_vlan=False, include_vf_trusted=False):
nic_meta['vf_trusted'] = False
vlan_nic_meta['vf_trusted'] = False
vf_trusted_nic_meta['vf_trusted'] = True
if include_shares:
dicts.append(share_meta)
return dicts
@@ -512,6 +527,11 @@ class MetadataTestCase(test.TestCase):
'openstack/2020-10-14/vendor_data.json',
'openstack/2020-10-14/network_data.json',
'openstack/2020-10-14/vendor_data2.json',
'openstack/2025-04-04/meta_data.json',
'openstack/2025-04-04/user_data',
'openstack/2025-04-04/vendor_data.json',
'openstack/2025-04-04/network_data.json',
'openstack/2025-04-04/vendor_data2.json',
'openstack/latest/meta_data.json',
'openstack/latest/user_data',
'openstack/latest/vendor_data.json',
@@ -600,6 +620,10 @@ class MetadataTestCase(test.TestCase):
True, expose_trusted)
if md._check_os_version(base.VICTORIA, os_version):
expected_metadata['dedicated_cpus'] = []
if md._check_os_version(base.EPOXY, os_version):
expose_shares = md._check_os_version(base.VICTORIA, os_version)
expected_metadata['devices'] = fake_metadata_dicts(
True, True, expose_shares)
md._metadata_as_json(os_version, 'non useless path parameter')
self.assertEqual(md.md_mimetype, base.MIME_TYPE_APPLICATION_JSON)
mock_json_dump_as_bytes.assert_called_once_with(expected_metadata)
@@ -673,7 +697,8 @@ class OpenStackMetadataTestCase(test.TestCase):
mdinst = fake_InstanceMetadata(self, inst)
mdjson = mdinst.lookup("/openstack/latest/meta_data.json")
mddict = jsonutils.loads(mdjson)
self.assertEqual(fake_metadata_dicts(True, True), mddict['devices'])
self.assertEqual(
fake_metadata_dicts(True, True, True), mddict["devices"])
def test_top_level_listing(self):
# request for /openstack/<version>/ should show metadata.json
+63 -11
View File
@@ -2332,6 +2332,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
<alias name='net0'/>
<address type='ccw' cssid='0xfe' ssid='0x0' devno='0x0001'/>
</interface>
<filesystem type="mount" accessmode='passthrough'>
<driver type='virtiofs'/>
<source dir="/mnt/nfsmount"/>
<target dir="my_share_tag"/>
</filesystem>
<hostdev mode="subsystem" type="pci" managed="yes">
<source>
<address bus="0x06" domain="0x0000" function="0x1"
@@ -2428,11 +2433,28 @@ class LibvirtConnTestCase(test.NoDBTestCase,
pci_utils.get_mac_by_pci_address.side_effect = None
pci_utils.get_mac_by_pci_address.return_value = 'da:d1:f2:91:95:c1'
fake_share_mapping = {
'created_at': None,
'updated_at': None,
'id': 1,
'uuid': uuids.share_mapping,
'instance_uuid': uuids.instance,
'share_id': '33714a70-38d5-40d6-88e4-a382ae1c6dfe',
'status': 'inactive',
'tag': 'my_share_tag',
'export_location': '192.168.122.152:/manila/share',
'share_proto': 'NFS',
}
with test.nested(
mock.patch('nova.objects.VirtualInterfaceList'
'.get_by_instance_uuid', return_value=vifs),
mock.patch('nova.objects.BlockDeviceMappingList'
'.get_by_instance_uuid', return_value=bdms),
mock.patch(
'nova.db.main.api.share_mapping_get_by_instance_uuid',
return_value=[fake_share_mapping]),
mock.patch('nova.virt.libvirt.host.Host.get_guest',
return_value=guest),
mock.patch.object(nova.virt.libvirt.guest.Guest, 'get_xml_desc',
@@ -2441,7 +2463,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
metadata_obj = drvr._build_device_metadata(self.context,
instance_ref)
metadata = metadata_obj.devices
self.assertEqual(11, len(metadata))
self.assertEqual(12, len(metadata))
self.assertIsInstance(metadata[0],
objects.DiskMetadata)
self.assertIsInstance(metadata[0].bus,
@@ -2504,10 +2526,16 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(['mytag3'], metadata[9].tags)
self.assertFalse(metadata[9].vf_trusted)
self.assertIsInstance(metadata[10],
objects.ShareMetadata)
self.assertEqual(
'33714a70-38d5-40d6-88e4-a382ae1c6dfe', metadata[10].share_id)
self.assertEqual(
'my_share_tag', metadata[10].tag)
self.assertIsInstance(metadata[11],
objects.NetworkInterfaceMetadata)
self.assertEqual(['pf_tag'], metadata[10].tags)
self.assertEqual('da:d1:f2:91:95:c1', metadata[10].mac)
self.assertEqual('0000:06:00.1', metadata[10].bus.address)
self.assertEqual(['pf_tag'], metadata[11].tags)
self.assertEqual('da:d1:f2:91:95:c1', metadata[11].mac)
self.assertEqual('0000:06:00.1', metadata[11].bus.address)
@mock.patch.object(host.Host, 'get_connection')
@mock.patch.object(nova.virt.libvirt.guest.Guest, 'get_xml_desc')
@@ -12696,12 +12724,17 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'instance', data, block_device_info=bdi))
self.assertEqual(0, mock_get_instance_disk_info.call_count)
@mock.patch('nova.objects.instance.Instance.save',
return_value=None)
@mock.patch('nova.virt.libvirt.LibvirtDriver._build_device_metadata',
return_value=None)
@mock.patch.object(host.Host, 'has_min_version', return_value=True)
@mock.patch.object(fakelibvirt.virDomain, "migrateToURI3")
@mock.patch.object(fakelibvirt.virDomain, "XMLDesc")
def test_live_migration_update_graphics_xml(self, mock_xml,
mock_migrateToURI3,
mock_min_version):
def test_live_migration_update_graphics_xml(
self, mock_xml, mock_migrateToURI3, mock_min_version, mock_metadata,
mock_save
):
self.compute = manager.ComputeManager()
instance_ref = objects.Instance(**self.test_instance)
target_connection = '127.0.0.2'
@@ -17593,6 +17626,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.context, instance, [], mock.ANY, None
)
@mock.patch('nova.objects.instance.Instance.save',
return_value=None)
@mock.patch('nova.virt.libvirt.LibvirtDriver._build_device_metadata',
return_value=None)
@mock.patch('nova.virt.libvirt.LibvirtDriver.get_info')
@mock.patch('nova.virt.libvirt.LibvirtDriver._create_guest_with_network')
@mock.patch('nova.virt.libvirt.LibvirtDriver._get_guest_xml')
@@ -17603,7 +17640,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'_get_all_assigned_mediated_devices')
def test_hard_reboot(self, mock_get_mdev, mock_destroy, mock_get_disk_info,
mock_get_guest_xml, mock_create_guest_with_network,
mock_get_info):
mock_get_info, mock_metadata, mock_save):
self.context.auth_token = True # any non-None value will suffice
instance = objects.Instance(**self.test_instance)
network_info = _fake_network_info(self)
@@ -17674,6 +17711,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.context, dummyxml, instance, network_info,
block_device_info, vifs_already_plugged=True,
external_events=[])
mock_metadata.assert_called_once_with(self.context, instance)
@mock.patch('nova.objects.instance.Instance.save',
return_value=None)
@@ -17778,6 +17816,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.context, dummyxml, instance, network_info, block_device_info,
vifs_already_plugged=True,
external_events=[])
mock_metadata.assert_called_once_with(self.context, instance)
def _mount_or_umount_share(self, func, side_effect=False):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
@@ -17835,6 +17874,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self._mount_or_umount_share(
'umount_share', processutils.ProcessExecutionError)
@mock.patch('nova.objects.instance.Instance.save',
return_value=None)
@mock.patch('nova.virt.libvirt.LibvirtDriver._build_device_metadata',
return_value=None)
@mock.patch('oslo_utils.fileutils.ensure_tree', new=mock.Mock())
@mock.patch('nova.virt.libvirt.LibvirtDriver.get_info')
@mock.patch('nova.virt.libvirt.LibvirtDriver._create_guest_with_network')
@@ -17844,7 +17887,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
'nova.virt.libvirt.LibvirtDriver._get_all_assigned_mediated_devices',
new=mock.Mock(return_value={}))
def test_hard_reboot_wait_for_plug(
self, mock_get_guest_xml, mock_create_guest_with_network, mock_get_info
self, mock_get_guest_xml, mock_create_guest_with_network,
mock_get_info, mock_metadata, mock_save
):
self.flags(
group="workarounds",
@@ -17876,6 +17920,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
]
)
@mock.patch('nova.objects.instance.Instance.save',
return_value=None)
@mock.patch('nova.virt.libvirt.LibvirtDriver._build_device_metadata',
return_value=None)
@mock.patch('oslo_utils.fileutils.ensure_tree')
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall')
@mock.patch('nova.virt.libvirt.LibvirtDriver._create_guest_with_network')
@@ -17894,7 +17942,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_get_guest_config, mock_get_instance_path,
mock_get_instance_disk_info, mock_create_images_and_backing,
mock_create_domand_and_network,
mock_looping_call, mock_ensure_tree):
mock_looping_call, mock_ensure_tree, mock_metadata, mock_save):
"""For a hard reboot, we shouldn't need an additional call to glance
to get the image metadata.
@@ -28161,6 +28209,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
[mock.call(device1), mock.call(device2)],
drvr._host.device_start.mock_calls)
@mock.patch.object(libvirt_driver.LibvirtDriver, '_build_device_metadata')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(
libvirt_driver.LibvirtDriver, '_get_all_assigned_mediated_devices',
new=mock.Mock(return_value={}))
@@ -28179,7 +28229,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
@mock.patch(
'oslo_service.loopingcall.FixedIntervalLoopingCall', new=mock.Mock())
def _test_hard_reboot_allocate_missing_mdevs(
self, mock_get_xml, mock_image_meta, mock_allocate_mdevs):
self, mock_get_xml, mock_image_meta, mock_allocate_mdevs,
mock_db, mock_build_metadata):
mock_compute = mock.Mock()
mock_compute.reportclient.get_allocations_for_consumer.return_value = (
mock.sentinel.allocations)
@@ -28193,6 +28244,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
flavor=objects.Flavor(extra_specs={'resources:VGPU': 1}))
share_info = objects.ShareMappingList()
mock_build_metadata.return_value = objects.InstanceDeviceMetadata()
drvr._hard_reboot(
ctxt, instance, mock.sentinel.network_info, share_info
)
+29
View File
@@ -4208,6 +4208,10 @@ class LibvirtDriver(driver.ComputeDriver):
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_reboot)
timer.start(interval=0.5).wait()
# Rebuild device_metadata to get shares
instance.device_metadata = self._build_device_metadata(
context, instance)
def pause(self, instance):
"""Pause VM instance."""
self._host.get_guest(instance).pause()
@@ -12750,6 +12754,25 @@ class LibvirtDriver(driver.ComputeDriver):
device.bus = bus
return device
def _build_share_metadata(self, dev, shares):
"""Builds a metadata object for a share
:param dev: The vconfig.LibvirtConfigGuestFilesys to build
metadata for.
:param shares: The list of ShareMapping objects.
:return: A ShareMetadata object, or None.
"""
device = objects.ShareMetadata()
for share in shares:
if dev.driver_type == 'virtiofs' and share.tag == dev.target_dir:
device.share_id = share.share_id
device.tag = share.tag
return device
LOG.warning('Device %s of type filesystem found but it is not '
'linked to any share.', dev)
return None
def _build_hostdev_metadata(self, dev, vifs_to_expose, vlans_by_mac):
"""Builds a metadata object for a hostdev. This can only be a PF, so we
don't need trusted_by_mac like in _build_interface_metadata because
@@ -12808,6 +12831,10 @@ class LibvirtDriver(driver.ComputeDriver):
context, instance.uuid)
tagged_bdms = {_get_device_name(bdm): bdm for bdm in bdms if bdm.tag}
shares = objects.ShareMappingList.get_by_instance_uuid(
context, instance.uuid
)
devices = []
guest = self._host.get_guest(instance)
xml = guest.get_xml_desc()
@@ -12826,6 +12853,8 @@ class LibvirtDriver(driver.ComputeDriver):
if isinstance(dev, vconfig.LibvirtConfigGuestHostdevPCI):
device = self._build_hostdev_metadata(dev, vifs_to_expose,
vlans_by_mac)
if isinstance(dev, vconfig.LibvirtConfigGuestFilesys):
device = self._build_share_metadata(dev, shares)
if device:
devices.append(device)
if devices: