diff options
31 files changed, 400 insertions, 144 deletions
diff --git a/.zuul.yaml b/.zuul.yaml index 29918cafc8..abe4d2fa4a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -419,6 +419,7 @@ # Added in Yoga. NOVNC_FROM_PACKAGE: False NOVA_USE_UNIFIED_LIMITS: True + MYSQL_REDUCE_MEMORY: True devstack_services: # Disable OVN services br-ex-tcpdump: false @@ -756,7 +757,7 @@ irrelevant-files: *policies-irrelevant-files - tempest-integrated-compute-enforce-scope-new-defaults: irrelevant-files: *policies-irrelevant-files - - grenade-skip-level: + - grenade-skip-level-always: irrelevant-files: *policies-irrelevant-files - nova-grenade-multinode: irrelevant-files: *policies-irrelevant-files @@ -793,6 +794,8 @@ irrelevant-files: *policies-irrelevant-files - tempest-integrated-compute-enforce-scope-new-defaults: irrelevant-files: *policies-irrelevant-files + - grenade-skip-level-always: + irrelevant-files: *policies-irrelevant-files - nova-grenade-multinode: irrelevant-files: *policies-irrelevant-files - tempest-ipv6-only: diff --git a/doc/source/admin/upgrades.rst b/doc/source/admin/upgrades.rst index 00a714970b..61fd0cf258 100644 --- a/doc/source/admin/upgrades.rst +++ b/doc/source/admin/upgrades.rst @@ -41,21 +41,27 @@ Rolling upgrade process To reduce downtime, the compute services can be upgraded in a rolling fashion. It means upgrading a few services at a time. This results in a condition where both old (N) and new (N+1) nova-compute services co-exist for a certain time -period. Note that, there is no upgrade of the hypervisor here, this is just +period (or even N with N+2 upgraded nova-compute services, see below). +Note that, there is no upgrade of the hypervisor here, this is just upgrading the nova services. If reduced downtime is not a concern (or lower complexity is desired), all services may be taken down and restarted at the same time. .. important:: - Nova does not currently support the coexistence of N and N+2 or greater - :program:`nova-compute` or :program:`nova-conductor` services in the same - deployment. The `nova-conductor`` service will fail to start when a - ``nova-compute`` service that is older than the previous release (N-2 or - greater) is detected. Similarly, in a :doc:`deployment with multiple cells + As of OpenStack 2023.1 (Antelope), Nova supports the coexistence of N and + N-2 (Yoga) :program:`nova-compute` or :program:`nova-conductor` services in + the same deployment. The `nova-conductor`` service will fail to start when + a ``nova-compute`` service that is older than the support envelope is + detected. This varies by release and the support envelope will be explained + in the release notes. Similarly, in a :doc:`deployment with multiple cells </admin/cells>`, neither the super conductor service nor any per-cell conductor service will start if any other conductor service in the - deployment is older than the previous release. + deployment is older than the N-2 release. + + Releases older than 2023.1 will only support rolling upgrades for a single + release difference between :program:`nova-compute` and + :program:`nova-conductor` services. #. Before maintenance window: diff --git a/nova/compute/manager.py b/nova/compute/manager.py index efcdece81a..5ea71827fc 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -6874,6 +6874,9 @@ class ComputeManager(manager.Manager): current_power_state = self._get_power_state(instance) network_info = self.network_api.get_instance_nw_info(context, instance) + ports_id = [vif['id'] for vif in network_info] + self.network_api.unbind_ports(context, ports_id, detach=False) + block_device_info = self._get_instance_block_device_info(context, instance, bdms=bdms) diff --git a/nova/network/neutron.py b/nova/network/neutron.py index 27e7d06455..6c9f19f010 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -612,10 +612,22 @@ class API: raise exception.ExternalNetworkAttachForbidden( network_uuid=net['id']) + def unbind_ports(self, context, ports, detach=True): + """Unbind and detach the given ports by clearing their + device_owner and dns_name. + The device_id will also be cleaned if detach=True. + + :param context: The request context. + :param ports: list of port IDs. + """ + neutron = get_client(context) + self._unbind_ports(context, ports, neutron, detach=detach) + def _unbind_ports(self, context, ports, - neutron, port_client=None): - """Unbind the given ports by clearing their device_id, + neutron, port_client=None, detach=True): + """Unbind and detach the given ports by clearing their device_owner and dns_name. + The device_id will also be cleaned if detach=True. :param context: The request context. :param ports: list of port IDs. @@ -638,11 +650,12 @@ class API: port_req_body: ty.Dict[str, ty.Any] = { 'port': { - 'device_id': '', - 'device_owner': '', constants.BINDING_HOST_ID: None, } } + if detach: + port_req_body['port']['device_id'] = '' + port_req_body['port']['device_owner'] = '' try: port = self._show_port( context, port_id, neutron_client=neutron, diff --git a/nova/objects/service.py b/nova/objects/service.py index 0ed443ef17..1a4629cc84 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -237,15 +237,30 @@ SERVICE_VERSION_HISTORY = ( # local node identity for single-node systems. NODE_IDENTITY_VERSION = 65 -# This is used to raise an error at service startup if older than N-1 computes -# are detected. Update this at the beginning of every release cycle to point to -# the smallest service version that was added in N-1. -OLDEST_SUPPORTED_SERVICE_VERSION = 'Yoga' +# This is used to raise an error at service startup if older than supported +# computes are detected. +# NOTE(sbauza) : Please modify it this way : +# * At the beginning of a non-SLURP release (eg. 2023.2 Bobcat) (or just after +# the previous SLURP release RC1, like 2023.1 Antelope), please bump +# OLDEST_SUPPORTED_SERVICE_VERSION to the previous SLURP release (in that +# example, Antelope) +# * At the beginning of a SLURP release (eg. 2024.1 C) (or just after the +# previous non-SLURP release RC1, like 2023.2 Bobcat), please keep the +# OLDEST_SUPPORTED_SERVICE_VERSION value using the previous SLURP release +# (in that example, Antelope) +# * At the end of any release (SLURP or non-SLURP), please modify +# SERVICE_VERSION_ALIASES to add a key/value with key being the release name +# and value be the latest service version that the release supports (for +# example, before Bobcat RC1, please add 'Bobcat': XX where X is the latest +# servion version that was added) +OLDEST_SUPPORTED_SERVICE_VERSION = 'Antelope' SERVICE_VERSION_ALIASES = { 'Victoria': 52, 'Wallaby': 54, 'Xena': 57, 'Yoga': 61, + 'Zed': 64, + 'Antelope': 66, } diff --git a/nova/pci/request.py b/nova/pci/request.py index 27ada6c045..8ae2385549 100644 --- a/nova/pci/request.py +++ b/nova/pci/request.py @@ -168,7 +168,7 @@ def _get_alias_from_config() -> Alias: def _translate_alias_to_requests( - alias_spec: str, affinity_policy: str = None, + alias_spec: str, affinity_policy: ty.Optional[str] = None, ) -> ty.List['objects.InstancePCIRequest']: """Generate complete pci requests from pci aliases in extra_spec.""" pci_aliases = _get_alias_from_config() @@ -255,7 +255,7 @@ def get_instance_pci_request_from_vif( def get_pci_requests_from_flavor( - flavor: 'objects.Flavor', affinity_policy: str = None, + flavor: 'objects.Flavor', affinity_policy: ty.Optional[str] = None, ) -> 'objects.InstancePCIRequests': """Validate and return PCI requests. diff --git a/nova/pci/stats.py b/nova/pci/stats.py index 5c5f7c669c..c6e4844b34 100644 --- a/nova/pci/stats.py +++ b/nova/pci/stats.py @@ -82,7 +82,7 @@ class PciDeviceStats(object): self, numa_topology: 'objects.NUMATopology', stats: 'objects.PCIDevicePoolList' = None, - dev_filter: whitelist.Whitelist = None, + dev_filter: ty.Optional[whitelist.Whitelist] = None, ) -> None: self.numa_topology = numa_topology self.pools = ( diff --git a/nova/pci/whitelist.py b/nova/pci/whitelist.py index 8862a0ef4f..152cc29ca6 100644 --- a/nova/pci/whitelist.py +++ b/nova/pci/whitelist.py @@ -33,7 +33,7 @@ class Whitelist(object): assignable. """ - def __init__(self, whitelist_spec: str = None) -> None: + def __init__(self, whitelist_spec: ty.Optional[str] = None) -> None: """White list constructor For example, the following json string specifies that devices whose diff --git a/nova/scheduler/client/report.py b/nova/scheduler/client/report.py index 1242752be1..7c14f3d7ef 100644 --- a/nova/scheduler/client/report.py +++ b/nova/scheduler/client/report.py @@ -1047,7 +1047,7 @@ class SchedulerReportClient(object): context: nova_context.RequestContext, rp_uuid: str, traits: ty.Iterable[str], - generation: int = None + generation: ty.Optional[int] = None ): """Replace a provider's traits with those specified. diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 11581c4f2d..620519d403 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -23,6 +23,7 @@ import collections import copy import random +from keystoneauth1 import exceptions as ks_exc from oslo_log import log as logging import oslo_messaging as messaging from oslo_serialization import jsonutils @@ -67,10 +68,42 @@ class SchedulerManager(manager.Manager): self.host_manager = host_manager.HostManager() self.servicegroup_api = servicegroup.API() self.notifier = rpc.get_notifier('scheduler') - self.placement_client = report.report_client_singleton() + self._placement_client = None + + try: + # Test our placement client during initialization + self.placement_client + except (ks_exc.EndpointNotFound, + ks_exc.DiscoveryFailure, + ks_exc.RequestTimeout, + ks_exc.GatewayTimeout, + ks_exc.ConnectFailure) as e: + # Non-fatal, likely transient (although not definitely); + # continue startup but log the warning so that when things + # fail later, it will be clear why we can not do certain + # things. + LOG.warning('Unable to initialize placement client (%s); ' + 'Continuing with startup, but scheduling ' + 'will not be possible.', e) + except (ks_exc.MissingAuthPlugin, + ks_exc.Unauthorized) as e: + # This is almost definitely fatal mis-configuration. The + # Unauthorized error might be transient, but it is + # probably reasonable to consider it fatal. + LOG.error('Fatal error initializing placement client; ' + 'config is incorrect or incomplete: %s', e) + raise + except Exception as e: + # Unknown/unexpected errors here are fatal + LOG.error('Fatal error initializing placement client: %s', e) + raise super().__init__(service_name='scheduler', *args, **kwargs) + @property + def placement_client(self): + return report.report_client_singleton() + @periodic_task.periodic_task( spacing=CONF.scheduler.discover_hosts_in_cells_interval, run_immediately=True) diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py index c7e6ffed97..02c44093bd 100644 --- a/nova/scheduler/utils.py +++ b/nova/scheduler/utils.py @@ -1080,6 +1080,17 @@ _SUPPORTS_SOFT_AFFINITY = None _SUPPORTS_SOFT_ANTI_AFFINITY = None +def reset_globals(): + global _SUPPORTS_AFFINITY + _SUPPORTS_AFFINITY = None + global _SUPPORTS_ANTI_AFFINITY + _SUPPORTS_ANTI_AFFINITY = None + global _SUPPORTS_SOFT_AFFINITY + _SUPPORTS_SOFT_AFFINITY = None + global _SUPPORTS_SOFT_ANTI_AFFINITY + _SUPPORTS_SOFT_ANTI_AFFINITY = None + + def _get_group_details(context, instance_uuid, user_group_hosts=None): """Provide group_hosts and group_policies sets related to instances if those instances are belonging to a group and if corresponding filters are diff --git a/nova/test.py b/nova/test.py index 0f7965ea33..e37967b06d 100644 --- a/nova/test.py +++ b/nova/test.py @@ -62,6 +62,7 @@ from nova import objects from nova.objects import base as objects_base from nova import quota from nova.scheduler.client import report +from nova.scheduler import utils as scheduler_utils from nova.tests import fixtures as nova_fixtures from nova.tests.unit import matchers from nova import utils @@ -310,6 +311,12 @@ class TestCase(base.BaseTestCase): if self.STUB_COMPUTE_ID: self.useFixture(nova_fixtures.ComputeNodeIdFixture()) + # Reset globals indicating affinity filter support. Some tests may set + # self.flags(enabled_filters=...) which could make the affinity filter + # support globals get set to a non-default configuration which affects + # all other tests. + scheduler_utils.reset_globals() + def _setup_cells(self): """Setup a normal cellsv2 environment. diff --git a/nova/tests/fixtures/nova.py b/nova/tests/fixtures/nova.py index 9a652c02cb..5fd893e7dc 100644 --- a/nova/tests/fixtures/nova.py +++ b/nova/tests/fixtures/nova.py @@ -1822,24 +1822,6 @@ class ImportModulePoisonFixture(fixtures.Fixture): def find_spec(self, fullname, path, target=None): if fullname in self.modules: - current = eventlet.getcurrent() - # NOTE(gibi) not all eventlet spawn is under our control, so - # there can be senders without test_case_id set, find the first - # ancestor that was spawned from nova.utils.spawn[_n] and - # therefore has the id set. - while ( - current is not None and - not getattr(current, 'test_case_id', None) - ): - current = current.parent - - if current is not None: - self.test.tc_id = current.test_case_id - LOG.warning( - "!!!---!!! TestCase ID %s hit the import poison while " - "importing %s. If you see this in a failed functional " - "test then please let #openstack-nova on IRC know " - "about it. !!!---!!!", current.test_case_id, fullname) self.test.fail_message = ( f"This test imports the '{fullname}' module, which it " f'should not in the test environment. Please add ' @@ -1850,7 +1832,6 @@ class ImportModulePoisonFixture(fixtures.Fixture): def __init__(self, module_names): self.module_names = module_names self.fail_message = '' - self.tc_id = None if isinstance(module_names, str): self.module_names = {module_names} self.meta_path_finder = self.ForbiddenModules(self, self.module_names) @@ -1868,13 +1849,6 @@ class ImportModulePoisonFixture(fixtures.Fixture): # there (which is also what self.assert* and self.fail() do underneath) # will not work to cause a failure in the test. if self.fail_message: - if self.tc_id is not None: - LOG.warning( - "!!!---!!! TestCase ID %s hit the import poison. If you " - "see this in a failed functional test then please let " - "#openstack-nova on IRC know about it. !!!---!!!", - self.tc_id - ) raise ImportError(self.fail_message) diff --git a/nova/tests/functional/libvirt/test_pci_sriov_servers.py b/nova/tests/functional/libvirt/test_pci_sriov_servers.py index 6b8b254af9..098a0e857b 100644 --- a/nova/tests/functional/libvirt/test_pci_sriov_servers.py +++ b/nova/tests/functional/libvirt/test_pci_sriov_servers.py @@ -1549,7 +1549,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): 'not supported for instance with vDPA ports', ex.response.text) + # NOTE(sbauza): Now we're post-Antelope release, we don't need to support + # this test def test_attach_interface_service_version_61(self): + self.flags(disable_compute_service_check_for_ffu=True, + group='workarounds') with mock.patch( "nova.objects.service.get_minimum_version_all_cells", return_value=61 @@ -1578,7 +1582,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): self.assertEqual(hostname, port['binding:host_id']) self.assertEqual(server['id'], port['device_id']) + # NOTE(sbauza): Now we're post-Antelope release, we don't need to support + # this test def test_detach_interface_service_version_61(self): + self.flags(disable_compute_service_check_for_ffu=True, + group='workarounds') with mock.patch( "nova.objects.service.get_minimum_version_all_cells", return_value=61 @@ -1612,10 +1620,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): # to any host but should still be owned by the vm port = self.neutron.show_port(vdpa_port['id'])['port'] self.assertEqual(server['id'], port['device_id']) - # FIXME(sean-k-mooney): we should be unbinding the port from - # the host when we shelve offload but we don't today. - # This is unrelated to vdpa port and is a general issue. - self.assertEqual(hostname, port['binding:host_id']) + self.assertIsNone(port['binding:host_id']) self.assertIn('binding:profile', port) self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname']) self.assertIsNone(server['OS-EXT-SRV-ATTR:host']) @@ -1637,9 +1642,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci) self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname']) port = self.neutron.show_port(vdpa_port['id'])['port'] - # FIXME(sean-k-mooney): shelve offload should unbind the port - # self.assertEqual('', port['binding:host_id']) - self.assertEqual(hostname, port['binding:host_id']) + self.assertIsNone(port['binding:host_id']) server = self._unshelve_server(server) self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci - 2) @@ -1670,9 +1673,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): self.assertPCIDeviceCounts(source, total=num_pci, free=num_pci) self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname']) port = self.neutron.show_port(vdpa_port['id'])['port'] - # FIXME(sean-k-mooney): shelve should unbind the port - # self.assertEqual('', port['binding:host_id']) - self.assertEqual(source, port['binding:host_id']) + self.assertIsNone(port['binding:host_id']) # force the unshelve to the other host self.api.put_service( @@ -1871,7 +1872,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): self.assertEqual( dest, server['OS-EXT-SRV-ATTR:hypervisor_hostname']) + # NOTE(sbauza): Now we're post-Antelope release, we don't need to support + # this test def test_suspend_and_resume_service_version_62(self): + self.flags(disable_compute_service_check_for_ffu=True, + group='workarounds') with mock.patch( "nova.objects.service.get_minimum_version_all_cells", return_value=62 @@ -1890,7 +1895,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase): self.assertPCIDeviceCounts(source, total=num_pci, free=num_pci - 2) self.assertEqual('ACTIVE', server['status']) + # NOTE(sbauza): Now we're post-Antelope release, we don't need to support + # this test def test_live_migrate_service_version_62(self): + self.flags(disable_compute_service_check_for_ffu=True, + group='workarounds') with mock.patch( "nova.objects.service.get_minimum_version_all_cells", return_value=62 @@ -3926,17 +3935,7 @@ class RemoteManagedServersTest(_PCIServersWithMigrationTestBase): port = self.neutron.show_port(uuids.dpu_tunnel_port)['port'] self.assertIn('binding:profile', port) - self.assertEqual( - { - 'pci_vendor_info': '15b3:101e', - 'pci_slot': '0000:82:00.4', - 'physical_network': None, - 'pf_mac_address': '52:54:00:1e:59:02', - 'vf_num': 3, - 'card_serial_number': 'MT0000X00002', - }, - port['binding:profile'], - ) + self.assertEqual({}, port['binding:profile']) def test_suspend(self): self.start_compute() diff --git a/nova/tests/functional/libvirt/test_power_manage.py b/nova/tests/functional/libvirt/test_power_manage.py index fb1ac7d0cd..9f80446bd6 100644 --- a/nova/tests/functional/libvirt/test_power_manage.py +++ b/nova/tests/functional/libvirt/test_power_manage.py @@ -21,7 +21,7 @@ from nova.tests import fixtures as nova_fixtures from nova.tests.fixtures import libvirt as fakelibvirt from nova.tests.functional.libvirt import base from nova.virt import hardware -from nova.virt.libvirt import cpu +from nova.virt.libvirt.cpu import api as cpu_api class PowerManagementTestsBase(base.ServersTestBase): @@ -73,7 +73,7 @@ class PowerManagementTestsBase(base.ServersTestBase): def _assert_cpu_set_state(self, cpu_set, expected='online'): for i in cpu_set: - core = cpu.Core(i) + core = cpu_api.Core(i) if expected == 'online': self.assertTrue(core.online, f'{i} is not online') elif expected == 'offline': @@ -212,7 +212,7 @@ class PowerManagementTestsGovernor(PowerManagementTestsBase): def test_changing_strategy_fails(self): # Arbitratly set a core governor strategy to be performance - cpu.Core(1).set_high_governor() + cpu_api.Core(1).set_high_governor() # and then forget about it while changing the strategy. self.flags(cpu_power_management_strategy='cpu_state', group='libvirt') # This time, this wouldn't be acceptable as some core would have a diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 43208aa812..5887c99081 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -6526,3 +6526,41 @@ class PortAndFlavorAccelsServerCreateTest(AcceleratorServerBase): binding_profile = neutronapi.get_binding_profile(updated_port) self.assertNotIn('arq_uuid', binding_profile) self.assertNotIn('pci_slot', binding_profile) + + +class PortBindingShelvedServerTest(integrated_helpers._IntegratedTestBase): + """Tests for servers with ports.""" + + compute_driver = 'fake.SmallFakeDriver' + + def setUp(self): + super(PortBindingShelvedServerTest, self).setUp() + self.flavor_id = self._create_flavor( + disk=10, ephemeral=20, swap=5 * 1024) + + def test_shelve_offload_with_port(self): + # Do not wait before offloading + self.flags(shelved_offload_time=0) + + server = self._create_server( + flavor_id=self.flavor_id, + networks=[{'port': self.neutron.port_1['id']}]) + + port = self.neutron.show_port(self.neutron.port_1['id'])['port'] + + # Assert that the port is actually associated to the instance + self.assertEqual(port['device_id'], server['id']) + self.assertEqual(port['binding:host_id'], 'compute') + self.assertEqual(port['binding:status'], 'ACTIVE') + + # Do shelve + server = self._shelve_server(server, 'SHELVED_OFFLOADED') + + # Retrieve the updated port + port = self.neutron.show_port(self.neutron.port_1['id'])['port'] + + # Assert that the port is still associated to the instance + # but the binding is not on the compute anymore + self.assertEqual(port['device_id'], server['id']) + self.assertIsNone(port['binding:host_id']) + self.assertNotIn('binding:status', port) diff --git a/nova/tests/unit/compute/test_shelve.py b/nova/tests/unit/compute/test_shelve.py index f95a722ced..0a1e3f54fc 100644 --- a/nova/tests/unit/compute/test_shelve.py +++ b/nova/tests/unit/compute/test_shelve.py @@ -209,6 +209,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase): instance = self._shelve_offload(clean_shutdown=False) mock_power_off.assert_called_once_with(instance, 0, 0) + @mock.patch.object(neutron_api.API, 'unbind_ports') @mock.patch.object(compute_utils, 'EventReporter') @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') @mock.patch.object(nova.compute.manager.ComputeManager, @@ -225,7 +226,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase): def _shelve_offload(self, mock_notify, mock_notify_instance_usage, mock_get_power_state, mock_update_resource_tracker, mock_delete_alloc, mock_terminate, mock_get_bdms, - mock_event, clean_shutdown=True): + mock_event, mock_unbind_ports, clean_shutdown=True): host = 'fake-mini' instance = self._create_fake_instance_obj(params={'host': host}) instance.task_state = task_states.SHELVING @@ -278,6 +279,9 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase): instance.uuid, graceful_exit=False) + mock_unbind_ports.assert_called_once_with( + self.context, mock.ANY, detach=False) + return instance @mock.patch('nova.compute.utils.' diff --git a/nova/tests/unit/scheduler/test_manager.py b/nova/tests/unit/scheduler/test_manager.py index e7866069b3..e992fe6034 100644 --- a/nova/tests/unit/scheduler/test_manager.py +++ b/nova/tests/unit/scheduler/test_manager.py @@ -19,6 +19,7 @@ Tests For Scheduler from unittest import mock +from keystoneauth1 import exceptions as ks_exc import oslo_messaging as messaging from oslo_serialization import jsonutils from oslo_utils.fixture import uuidsentinel as uuids @@ -1688,6 +1689,41 @@ class SchedulerManagerTestCase(test.NoDBTestCase): mock_log_warning.assert_not_called() mock_log_debug.assert_called_once_with(msg) + @mock.patch('nova.scheduler.client.report.report_client_singleton') + @mock.patch.object(manager, 'LOG') + @mock.patch('nova.scheduler.host_manager.HostManager') + @mock.patch('nova.servicegroup.API') + @mock.patch('nova.rpc.get_notifier') + def test_init_lazy_placement_client(self, mock_rpc, mock_sg, mock_hm, + mock_log, mock_report): + # Simulate keytone or placement being offline at startup + mock_report.side_effect = ks_exc.RequestTimeout + mgr = manager.SchedulerManager() + mock_report.assert_called_once_with() + self.assertTrue(mock_log.warning.called) + + # Make sure we're raising the actual error to subsequent callers + self.assertRaises(ks_exc.RequestTimeout, lambda: mgr.placement_client) + + # Simulate recovery of the keystone or placement service + mock_report.reset_mock(side_effect=True) + mgr.placement_client + mock_report.assert_called_once_with() + + @mock.patch('nova.scheduler.client.report.report_client_singleton') + @mock.patch('nova.scheduler.host_manager.HostManager') + @mock.patch('nova.servicegroup.API') + @mock.patch('nova.rpc.get_notifier') + def test_init_lazy_placement_client_failures(self, mock_rpc, mock_sg, + mock_hm, mock_report): + # Certain keystoneclient exceptions are fatal + mock_report.side_effect = ks_exc.Unauthorized + self.assertRaises(ks_exc.Unauthorized, manager.SchedulerManager) + + # Anything else is fatal + mock_report.side_effect = test.TestingException + self.assertRaises(test.TestingException, manager.SchedulerManager) + class SchedulerManagerAllocationCandidateTestCase(test.NoDBTestCase): diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 04c80d662b..2b58c7df8b 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3402,7 +3402,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.assertEqual( "Memory encryption requested by hw:mem_encryption extra spec in " "m1.fake flavor but image fake_image doesn't have " - "'hw_firmware_type' property set to 'uefi'", str(exc)) + "'hw_firmware_type' property set to 'uefi' or volume-backed " + "instance was requested", str(exc)) def test_sev_enabled_host_extra_spec_no_machine_type(self): exc = self.assertRaises(exception.InvalidMachineType, diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 016c478f8c..753ee41550 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -5364,7 +5364,7 @@ class MemEncryptionRequestedWithoutUEFITestCase( expected_error = ( "Memory encryption requested by %(requesters)s but image " "%(image_name)s doesn't have 'hw_firmware_type' property " - "set to 'uefi'" + "set to 'uefi' or volume-backed instance was requested" ) def _test_encrypted_memory_support_no_uefi(self, enc_extra_spec, @@ -5491,6 +5491,25 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase): (self.flavor_name, self.image_id) ) + def test_encrypted_memory_support_flavor_for_volume(self): + extra_specs = {'hw:mem_encryption': True} + + flavor = objects.Flavor(name=self.flavor_name, + extra_specs=extra_specs) + # Following image_meta is typical for root Cinder volume + image_meta = objects.ImageMeta.from_dict({ + 'min_disk': 0, + 'min_ram': 0, + 'properties': {}, + 'size': 0, + 'status': 'active'}) + # Confirm that exception.FlavorImageConflict is raised when + # flavor with hw:mem_encryption flag is used to create + # volume-backed instance + self.assertRaises(exception.FlavorImageConflict, + hw.get_mem_encryption_constraint, flavor, + image_meta) + class PCINUMAAffinityPolicyTest(test.NoDBTestCase): diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index 96a7198db2..c8f8bb2481 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -1213,10 +1213,13 @@ def _check_for_mem_encryption_requirement_conflicts( "image %(image_name)s which has hw_mem_encryption property " "explicitly set to %(image_val)s" ) + # image_meta.name is not set if image object represents root + # Cinder volume. + image_name = (image_meta.name if 'name' in image_meta else None) data = { 'flavor_name': flavor.name, 'flavor_val': flavor_mem_enc_str, - 'image_name': image_meta.name, + 'image_name': image_name, 'image_val': image_mem_enc, } raise exception.FlavorImageConflict(emsg % data) @@ -1228,10 +1231,15 @@ def _check_mem_encryption_uses_uefi_image(requesters, image_meta): emsg = _( "Memory encryption requested by %(requesters)s but image " - "%(image_name)s doesn't have 'hw_firmware_type' property set to 'uefi'" + "%(image_name)s doesn't have 'hw_firmware_type' property set to " + "'uefi' or volume-backed instance was requested" ) + # image_meta.name is not set if image object represents root Cinder + # volume, for this case FlavorImageConflict should be raised, but + # image_meta.name can't be extracted. + image_name = (image_meta.name if 'name' in image_meta else None) data = {'requesters': " and ".join(requesters), - 'image_name': image_meta.name} + 'image_name': image_name} raise exception.FlavorImageConflict(emsg % data) @@ -1260,12 +1268,14 @@ def _check_mem_encryption_machine_type(image_meta, machine_type=None): if mach_type is None: return + # image_meta.name is not set if image object represents root Cinder volume. + image_name = (image_meta.name if 'name' in image_meta else None) # Could be something like pc-q35-2.11 if a specific version of the # machine type is required, so do substring matching. if 'q35' not in mach_type: raise exception.InvalidMachineType( mtype=mach_type, - image_id=image_meta.id, image_name=image_meta.name, + image_id=image_meta.id, image_name=image_name, reason=_("q35 type is required for SEV to work")) diff --git a/nova/virt/libvirt/cpu/__init__.py b/nova/virt/libvirt/cpu/__init__.py index 4410a4e579..e69de29bb2 100644 --- a/nova/virt/libvirt/cpu/__init__.py +++ b/nova/virt/libvirt/cpu/__init__.py @@ -1,22 +0,0 @@ -# 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. - -from nova.virt.libvirt.cpu import api - - -Core = api.Core - - -power_up = api.power_up -power_down = api.power_down -validate_all_dedicated_cpus = api.validate_all_dedicated_cpus -power_down_all_dedicated_cpus = api.power_down_all_dedicated_cpus diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 869996f615..fe48960296 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -114,7 +114,7 @@ from nova.virt.image import model as imgmodel from nova.virt import images from nova.virt.libvirt import blockinfo from nova.virt.libvirt import config as vconfig -from nova.virt.libvirt import cpu as libvirt_cpu +from nova.virt.libvirt.cpu import api as libvirt_cpu from nova.virt.libvirt import designer from nova.virt.libvirt import event as libvirtevent from nova.virt.libvirt import guest as libvirt_guest @@ -7636,7 +7636,7 @@ class LibvirtDriver(driver.ComputeDriver): instance: 'objects.Instance', power_on: bool = True, pause: bool = False, - post_xml_callback: ty.Callable = None, + post_xml_callback: ty.Optional[ty.Callable] = None, ) -> libvirt_guest.Guest: """Create a Guest from XML. @@ -7697,7 +7697,7 @@ class LibvirtDriver(driver.ComputeDriver): block_device_info: ty.Optional[ty.Dict[str, ty.Any]], power_on: bool = True, vifs_already_plugged: bool = False, - post_xml_callback: ty.Callable = None, + post_xml_callback: ty.Optional[ty.Callable] = None, external_events: ty.Optional[ty.List[ty.Tuple[str, str]]] = None, cleanup_instance_dir: bool = False, cleanup_instance_disks: bool = False, @@ -10086,24 +10086,6 @@ class LibvirtDriver(driver.ComputeDriver): :param instance: instance object that is in migration """ - current = eventlet.getcurrent() - # NOTE(gibi) not all eventlet spawn is under our control, so - # there can be senders without test_case_id set, find the first - # ancestor that was spawned from nova.utils.spawn[_n] and - # therefore has the id set. - while ( - current is not None and - not getattr(current, 'test_case_id', None) - ): - current = current.parent - - if current is not None: - LOG.warning( - "!!!---!!! live_migration_abort thread was spawned by " - "TestCase ID: %s. If you see this in a failed functional test " - "then please let #openstack-nova on IRC know about it. " - "!!!---!!!", current.test_case_id - ) guest = self._host.get_guest(instance) dom = guest._domain diff --git a/nova/virt/libvirt/event.py b/nova/virt/libvirt/event.py index a7d2a3624f..56951dc11c 100644 --- a/nova/virt/libvirt/event.py +++ b/nova/virt/libvirt/event.py @@ -9,6 +9,8 @@ # 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 typing as ty + from nova.virt import event @@ -22,7 +24,10 @@ class LibvirtEvent(event.InstanceEvent): class DeviceEvent(LibvirtEvent): """Base class for device related libvirt events""" - def __init__(self, uuid: str, dev: str, timestamp: float = None): + def __init__(self, + uuid: str, + dev: str, + timestamp: ty.Optional[float] = None): super().__init__(uuid, timestamp) self.dev = dev diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index adb2ec45a1..e1298ee5c8 100644 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -261,8 +261,8 @@ def copy_image( dest: str, host: ty.Optional[str] = None, receive: bool = False, - on_execute: ty.Callable = None, - on_completion: ty.Callable = None, + on_execute: ty.Optional[ty.Callable] = None, + on_completion: ty.Optional[ty.Callable] = None, compression: bool = True, ) -> None: """Copy a disk image to an existing directory @@ -639,7 +639,7 @@ def mdev_name2uuid(mdev_name: str) -> str: return str(uuid.UUID(mdev_uuid)) -def mdev_uuid2name(mdev_uuid: str, parent: str = None) -> str: +def mdev_uuid2name(mdev_uuid: str, parent: ty.Optional[str] = None) -> str: """Convert an mdev uuid (of the form 8-4-4-4-12) and optionally its parent device to a name (of the form mdev_<uuid_with_underscores>[_<pciid>]). diff --git a/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml b/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml new file mode 100644 index 0000000000..66890684af --- /dev/null +++ b/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml @@ -0,0 +1,51 @@ +--- +prelude: | + The OpenStack 2023.1 (Nova 27.0.0) release includes many new features and + bug fixes. Please be sure to read the upgrade section which describes the + required actions to upgrade your cloud from 26.0.0 (Zed) to 27.0.0 (2023.1). + As a reminder, OpenStack 2023.1 is our first `Skip-Level-Upgrade Release`__ + (starting from now, we name it a `SLURP release`) where you can + rolling-upgrade your compute services from OpenStack Yoga as an experimental + feature. Next SLURP release will be 2024.1. + + .. __: https://governance.openstack.org/tc/resolutions/20220210-release-cadence-adjustment.html + + There are a few major changes worth mentioning. This is not an exhaustive + list: + + - The latest Compute API microversion supported for 2023.1 is `v2.95`__. + + .. __: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-2023.1 + + - `PCI devices can now be scheduled <https://docs.openstack.org/nova/latest/admin/pci-passthrough.html#pci-tracking-in-placement>`_ + by Nova using the Placement API on a opt-in basis. This will help the + nova-scheduler service to better schedule flavors that use PCI + (non-Neutron related) resources, will generate less reschedules if an + instance cannot be created on a candidate and will help the nova-scheduler + to not miss valid candidates if the list was too large. + + - Operators can now ask Nova to `manage the power consumption of dedicated + CPUs <https://docs.openstack.org/nova/latest/admin/cpu-topologies.html#configuring-cpu-power-management-for-dedicated-cores>`_ + so as to either offline them or change their governor if they're + currently not in use by any instance or if the instance is stopped. + + - Nova will prevent unexpected compute service renames by `persisting a unique + compute UUID on local disk <https://docs.openstack.org/nova/latest/admin/compute-node-identification.html>`_. + This stored UUID will be considered the source of truth for knowing whether + the compute service hostame has been modified or not. As a reminder, + changing a compute hostname is forbidden, particularly when this compute is + currently running instances on top of it. + + - `SPICE consoles <https://docs.openstack.org/nova/latest/admin/remote-console-access.html#spice-console>`_ + can now be configured with compression settings which include choices of the + compression algorithm and the compression mode. + + - Fully-Qualified Domain Names are now considered valid for an instance + hostname if you use the 2.94 API microversion. + + - By opting into 2.95 API microversion, evacuated instances will remain + stopped on the destination host until manually started. + + - Nova APIs now `by default support new RBAC policies <https://docs.openstack.org/nova/latest/configuration/policy.html>` + and scopes. See our `Policy Concepts documention <https://docs.openstack.org/nova/latest/configuration/policy-concepts.html>` + for further details. diff --git a/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml b/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml new file mode 100644 index 0000000000..7e2dccbbf4 --- /dev/null +++ b/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + [`bug 1983471 <https://bugs.launchpad.net/nova/+bug/1983471>`_] + When offloading a shelved instance, the compute will now remove the + binding so instance ports will appear as "unbound" in neutron. diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst new file mode 100644 index 0000000000..d1238479ba --- /dev/null +++ b/releasenotes/source/2023.1.rst @@ -0,0 +1,6 @@ +=========================== +2023.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2023.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 6bff00e25a..ed6f8c2d07 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ Nova Release Notes :maxdepth: 1 unreleased + 2023.1 zed yoga xena diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po index d90391af7c..c0bd8bc9a8 100644 --- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po @@ -2,15 +2,16 @@ # Andi Chandler <andi@gowling.com>, 2018. #zanata # Andi Chandler <andi@gowling.com>, 2020. #zanata # Andi Chandler <andi@gowling.com>, 2022. #zanata +# Andi Chandler <andi@gowling.com>, 2023. #zanata msgid "" msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-16 12:59+0000\n" +"POT-Creation-Date: 2023-03-06 19:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2022-09-15 09:04+0000\n" +"PO-Revision-Date: 2023-01-26 10:17+0000\n" "Last-Translator: Andi Chandler <andi@gowling.com>\n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" @@ -382,9 +383,6 @@ msgstr "20.5.0" msgid "20.6.1" msgstr "20.6.1" -msgid "20.6.1-29" -msgstr "20.6.1-29" - msgid "204 NoContent on success" msgstr "204 NoContent on success" @@ -409,9 +407,6 @@ msgstr "21.2.2" msgid "21.2.3" msgstr "21.2.3" -msgid "21.2.4-12" -msgstr "21.2.4-12" - msgid "22.0.0" msgstr "22.0.0" @@ -433,9 +428,6 @@ msgstr "22.3.0" msgid "22.4.0" msgstr "22.4.0" -msgid "22.4.0-6" -msgstr "22.4.0-6" - msgid "23.0.0" msgstr "23.0.0" @@ -451,8 +443,8 @@ msgstr "23.2.0" msgid "23.2.1" msgstr "23.2.1" -msgid "23.2.1-13" -msgstr "23.2.1-13" +msgid "23.2.2" +msgstr "23.2.2" msgid "24.0.0" msgstr "24.0.0" @@ -463,8 +455,8 @@ msgstr "24.1.0" msgid "24.1.1" msgstr "24.1.1" -msgid "24.1.1-7" -msgstr "24.1.1-7" +msgid "24.2.0" +msgstr "24.2.0" msgid "25.0.0" msgstr "25.0.0" @@ -472,8 +464,14 @@ msgstr "25.0.0" msgid "25.0.1" msgstr "25.0.1" -msgid "25.0.1-5" -msgstr "25.0.1-5" +msgid "25.1.0" +msgstr "25.1.0" + +msgid "26.0.0" +msgstr "26.0.0" + +msgid "26.1.0" +msgstr "26.1.0" msgid "400 for unknown param for query param and for request body." msgstr "400 for unknown param for query param and for request body." @@ -488,6 +486,24 @@ msgid "409 Conflict if inventory in use or if some other request concurrently" msgstr "409 Conflict if inventory in use or if some other request concurrently" msgid "" +"A ``--dry-run`` option has been added to the ``nova-manage placement " +"heal_allocations`` CLI which allows running the command to get output " +"without committing any changes to placement." +msgstr "" +"A ``--dry-run`` option has been added to the ``nova-manage placement " +"heal_allocations`` CLI which allows running the command to get output " +"without committing any changes to placement." + +msgid "" +"A ``--force`` flag is provided to skip the above checks but caution should " +"be taken as this could easily lead to the underlying ABI of the instance " +"changing when moving between machine types." +msgstr "" +"A ``--force`` flag is provided to skip the above checks but caution should " +"be taken as this could easily lead to the underlying ABI of the instance " +"changing when moving between machine types." + +msgid "" "A ``default_floating_pool`` configuration option has been added in the " "``[neutron]`` group. The existing ``default_floating_pool`` option in the " "``[DEFAULT]`` group is retained and should be used by nova-network users. " @@ -571,6 +587,24 @@ msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" msgid "" +"The XenServer configuration option 'iqn_prefix' has been removed. It was not " +"used anywhere and has no effect on any code, so there should be no impact." +msgstr "" +"The XenServer configuration option 'iqn_prefix' has been removed. It was not " +"used anywhere and has no effect on any code, so there should be no impact." + +msgid "" +"The ``api_rate_limit`` configuration option has been removed. The option was " +"disabled by default back in the Havana release since it's effectively broken " +"for more than one API worker. It has been removed because the legacy v2 API " +"code that was using it has also been removed." +msgstr "" +"The ``api_rate_limit`` configuration option has been removed. The option was " +"disabled by default back in the Havana release since it's effectively broken " +"for more than one API worker. It has been removed because the legacy v2 API " +"code that was using it has also been removed." + +msgid "" "The ``nova-manage vm list`` command is deprecated and will be removed in the " "15.0.0 Ocata release. Use the ``nova list`` command from python-novaclient " "instead." @@ -580,6 +614,24 @@ msgstr "" "instead." msgid "" +"The default flavors that nova has previously had are no longer created as " +"part of the first database migration. New deployments will need to create " +"appropriate flavors before first use." +msgstr "" +"The default flavours that Nova previously had are no longer created as part " +"of the first database migration. New deployments will need to create " +"appropriate flavours before first use." + +msgid "" +"The network configuration option 'fake_call' has been removed. It hasn't " +"been used for several cycles, and has no effect on any code, so there should " +"be no impact." +msgstr "" +"The network configuration option 'fake_call' has been removed. It hasn't " +"been used for several cycles, and has no effect on any code, so there should " +"be no impact." + +msgid "" "These commands only work with nova-network which is itself deprecated in " "favor of Neutron." msgstr "" @@ -611,6 +663,9 @@ msgstr "Xena Series Release Notes" msgid "Yoga Series Release Notes" msgstr "Yoga Series Release Notes" +msgid "Zed Series Release Notes" +msgstr "Zed Series Release Notes" + msgid "kernels 3.x: 8" msgstr "kernels 3.x: 8" @@ -42,7 +42,7 @@ commands = env TEST_OSPROFILER=1 stestr run --combine --no-discover 'nova.tests.unit.test_profiler' stestr slowest -[testenv:functional{,-py38,-py39,-py310}] +[testenv:functional{,-py38,-py39,-py310,-py311}] description = Run functional tests. # As nova functional tests import the PlacementFixture from the placement |