From a34103c53f5560f802d558ecd9ae5a46d0176980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Ribaud?= Date: Mon, 1 Aug 2022 11:34:32 +0200 Subject: [PATCH] Add libvirt test to ensure metadata are working. This patch add libvirt functional test to ensure metadata are transmitted up to the metadata service. 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: I67c33f249ceb6cd51b6843acd347205777e89be0 --- nova/tests/fixtures/libvirt.py | 35 ++++ .../functional/libvirt/test_server_shares.py | 175 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 nova/tests/functional/libvirt/test_server_shares.py diff --git a/nova/tests/fixtures/libvirt.py b/nova/tests/fixtures/libvirt.py index 3c38c12e46..caf9942d59 100644 --- a/nova/tests/fixtures/libvirt.py +++ b/nova/tests/fixtures/libvirt.py @@ -914,6 +914,25 @@ def _parse_vcpu_info(element): return vcpu_info +def _parse_filesystem_info(element): + filesystem_info = {} + filesystem_info['type'] = element.get('type', 'mount') + + driver = element.find('./driver') + if driver is not None: + filesystem_info['driver_type'] = driver.get('type') + + source = element.find('./source') + if source is not None: + filesystem_info['source'] = source.get('dir') + + target = element.find('./target') + if target is not None: + filesystem_info['target'] = target.get('dir') + + return filesystem_info + + def _parse_nic_info(element): nic_info = {} nic_info['type'] = element.get('type', 'bridge') @@ -1177,6 +1196,13 @@ class Domain(object): disks_info += [_parse_disk_info(disk)] devices['disks'] = disks_info + # Manage shares + filesystems_info = [] + filesystems = device_nodes.findall('./filesystem') + for filesystem in filesystems: + filesystems_info += [_parse_filesystem_info(filesystem)] + devices['filesystem'] = filesystems_info + nics_info = [] nics = device_nodes.findall('./interface') for nic in nics: @@ -1458,6 +1484,13 @@ class Domain(object):
""" disks += strformat % dict(source_attr=source_attr, **disk) + filesystems = '' + for filesystem in self._def['devices']['filesystem']: + filesystems += ''' + + + + ''' % dict(source_attr=source_attr, **filesystem) nics = '' for func, nic in enumerate(self._def['devices']['nics']): if func > 7: @@ -1590,6 +1623,7 @@ class Domain(object): /usr/bin/kvm %(disks)s + %(filesystems)s
@@ -1624,6 +1658,7 @@ class Domain(object): 'arch': self._def['os']['arch'], 'loader': loader, 'disks': disks, + 'filesystems': filesystems, 'nics': nics, 'hostdevs': hostdevs, 'vpmems': vpmems, diff --git a/nova/tests/functional/libvirt/test_server_shares.py b/nova/tests/functional/libvirt/test_server_shares.py new file mode 100644 index 0000000000..79feb50c38 --- /dev/null +++ b/nova/tests/functional/libvirt/test_server_shares.py @@ -0,0 +1,175 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures +from lxml import etree +from requests import request + +from nova import context as nova_context +from nova.objects import instance +from nova.tests import fixtures as nova_fixtures +from nova.tests.functional.libvirt import base + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils + +from unittest import mock + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +class ServerSharesTestBase(base.ServersTestBase): + api_major_version = 'v2.1' + microversion = 'latest' + ADMIN_API = True + FAKE_LIBVIRT_VERSION = 7000000 + FAKE_QEMU_VERSION = 5002000 + + def setUp(self): + super(ServerSharesTestBase, self).setUp() + + self.context = nova_context.get_admin_context() + self.manila_fixture = self.useFixture(nova_fixtures.ManilaFixture()) + self.flags(ram_allocation_ratio=1.0) + self.flags(file_backed_memory=8192, group='libvirt') + self.compute = self.start_compute( + 'host1', + libvirt_version=self.FAKE_LIBVIRT_VERSION, + qemu_version=self.FAKE_QEMU_VERSION + ) + + self.api_fixture = self.useFixture(nova_fixtures.OSMetadataServer()) + self.md_url = self.api_fixture.md_url + + self.host = self.computes[self.compute].driver._host + + def _get_xml(self, server): + self.instance = instance.Instance.get_by_uuid( + self.context, server['id']) + guest = self.host.get_guest(self.instance) + xml = guest.get_xml_desc() + return xml + + def _get_filesystem_tag(self, xml, tag): + tags = [] + tree = etree.fromstring(xml) + device_nodes = tree.find('./devices') + filesystems = device_nodes.findall('./filesystem') + for filesystem in filesystems: + target = filesystem.find('./target') + tags.append(target.get('dir')) + return tags + + def _assert_filesystem_tag(self, xml, tag): + tags = self._get_filesystem_tag(xml, tag) + self.assertIn(tag, tags) + + def _assert_filesystem_tag_not_present(self, xml, tag): + tags = self._get_filesystem_tag(xml, tag) + self.assertNotIn(tag, tags) + + def _get_metadata_url(self, server): + # make sure that the metadata service returns information about the + # server we created above + + def fake_get_fixed_ip_by_address(self, ctxt, address): + return {'instance_uuid': server['id']} + + self.useFixture( + fixtures.MonkeyPatch( + 'nova.network.neutron.API.get_fixed_ip_by_address', + fake_get_fixed_ip_by_address)) + url = '%sopenstack/latest/meta_data.json' % self.md_url + return url + + def _assert_share_in_metadata(self, metatdata_url, share_id, tag): + device_share_and_tag = [] + res = request('GET', metatdata_url, timeout=5) + self.assertEqual(200, res.status_code) + metadata = jsonutils.loads(res.text) + for device in metadata['devices']: + device_share_and_tag.append((device['share_id'], device['tag'])) + self.assertIn((share_id, tag), device_share_and_tag) + + +class ServerSharesTest(ServerSharesTestBase): + + def test_server_share_metadata(self): + """Verify that share metadata are available""" + with mock.patch( + 'nova.virt.libvirt.volume.nfs.LibvirtNFSVolumeDriver.' + 'disconnect_volume' + ), mock.patch( + 'nova.virt.libvirt.volume.nfs.LibvirtNFSVolumeDriver.' + 'connect_volume' + ): + traits = self._get_provider_traits( + self.compute_rp_uuids[self.compute]) + for trait in ( + 'COMPUTE_STORAGE_VIRTIO_FS', 'COMPUTE_MEM_BACKING_FILE'): + self.assertIn(trait, traits) + server = self._create_server(networks='auto') + self._stop_server(server) + + share_id = '4b021746-d0eb-4031-92aa-23c3bec182cd' + self._attach_share(server, share_id) + self._start_server(server) + + # tag is the filesystem target directory. + # if post /server/{server_id}/share was called without a specific + # tag then the tag is the share id. + self._assert_filesystem_tag(self._get_xml(server), share_id) + + self._assert_share_in_metadata( + self._get_metadata_url(server), share_id, share_id) + return (server, share_id) + + def test_server_cephfs_share_metadata(self): + """Verify that cephfs share metadata are available""" + with mock.patch( + 'nova.virt.libvirt.volume.cephfs.LibvirtCEPHFSVolumeDriver.' + 'disconnect_volume' + ), mock.patch( + 'nova.virt.libvirt.volume.cephfs.LibvirtCEPHFSVolumeDriver.' + 'connect_volume' + ): + # update the mock to call the cephfs fake values + self.manila_fixture.mock_get.side_effect = ( + self.manila_fixture.fake_get_cephfs + ) + self.manila_fixture.mock_get_access.side_effect = ( + self.manila_fixture.fake_get_access_cephfs + ) + + traits = self._get_provider_traits( + self.compute_rp_uuids[self.compute]) + for trait in ( + 'COMPUTE_STORAGE_VIRTIO_FS', 'COMPUTE_MEM_BACKING_FILE'): + self.assertIn(trait, traits) + server = self._create_server(networks='auto') + self._stop_server(server) + + share_id = '4b021746-d0eb-4031-92aa-23c3bec182cd' + self._attach_share(server, share_id) + self._start_server(server) + + # tag is the filesystem target directory. + # if post /server/{server_id}/share was called without a specific + # tag then the tag is the share id. + self._assert_filesystem_tag(self._get_xml(server), share_id) + + self._assert_share_in_metadata( + self._get_metadata_url(server), share_id, share_id) + return (server, share_id)