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:
@@ -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 '
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user